python ethereum 程式碼分析《2》

來源:互聯網
上載者:User
python ethereum 程式碼分析 《2》 python 版本以太坊 pyethapp 模組

本章主要介紹pyethapp 模組中的ChainService 和 PoWService 一些關鍵概念

totalDifficulty 總難度:
total difficulty總難度是當前某條鏈所有區塊難度的總和,total difficulty被用來指示最長的那一條鏈,某個節點如果想從其他節點同步資料的話,他會選擇total difficulty最大的那一條鏈來同步資料。值得注意的是uncle block的difficulty也被計算到totalDifficulty中去,以太坊白皮書中的幽靈協議闡述了為什麼這麼設計

uncle block 叔叔區塊:也是礦工挖出來的區塊,它也是合法的,但是發現的稍晚,或者是網路傳輸稍慢,而沒有能成為最長的鏈上的區塊
以太坊十幾秒的出塊間隔,大大增加了孤塊的產生,並且降低了安全性。通過鼓勵引用叔塊,使引用主鏈獲得更多的安全保證(因為孤塊本身也是合法的)
區塊可以不引用,或者最多引用兩個叔塊
叔塊必須是區塊的前2層~前7層的祖先的直接的子塊
被引用過的叔塊不能重複引用
引用叔塊的區塊,可以獲得挖礦報酬的1/32,也就是5*1/32=0.15625 Ether。最多獲得2*0.15625=0.3125 Ether
參考資料http://blog.csdn.net/superswords/article/details/76445278
https://zhuanlan.zhihu.com/p/28928827

head_candidate 候選頭區塊: 礦工本地的候選頭區塊,相當於一個臨時區塊,head_candidate一直存在並且礦工會一直更新。礦工將交易打包進這個區塊,計算出隨機數後將次區塊作為新區塊廣播出去。

contract :合約,本身也是一個賬戶。每當一個交易指向一個合約賬戶時,該交易transaction的data屬性作為該合約的輸入執行該合約。

以太坊基本的一些概念:http://ethdocs.org/en/latest/contracts-and-transactions/index.html ChainService 和 PowService的關係

chain service負責區塊鏈的同步和更新,處理串連的各個節點的eth_protocol資料包,以及交易和區塊的廣播; pow service是礦工挖礦的服務,計算出’幸運值’後告知chain service,chain service 將區塊寫入區塊鏈並廣播出去。

礦工將交易打包,更新head_candidate

    @property    def head_candidate(self):        if self._head_candidate_needs_updating:            self._head_candidate_needs_updating = False            # Make a copy of self.transaction_queue because            # make_head_candidate modifies it.            txqueue = copy.deepcopy(self.transaction_queue)            #將交易打包,引用uncle區塊,執行交易中的合約,更新區塊狀態            self._head_candidate, self._head_candidate_state = make_head_candidate(                self.chain, txqueue, timestamp=int(time.time()))        return self._head_candidate

chain 執行個體的_on_new_head回調

    def _on_new_head(self, block):        log.debug('new head cbs', num=len(self.on_new_head_cbs))        self.transaction_queue = self.transaction_queue.diff(            block.transactions)        self._head_candidate_needs_updating = True        #cb是pow service的回呼函數mine_head_candidate,更新head_candidate並開始挖礦        for cb in self.on_new_head_cbs:            cb(block)

powservice的回調

    def mine_head_candidate(self, _=None):        #打包當前交易隊裡中的交易並更新head_candidate        hc = self.chain.head_candidate        if not self.active or self.chain.is_syncing:            return        elif (hc.transaction_count == 0 and              not self.app.config['pow']['mine_empty_blocks']):            return        log.debug('mining', difficulty=hc.difficulty)        #開始挖礦        self.ppipe.put(('mine', dict(mining_hash=hc.mining_hash,                                     block_number=hc.number,                                     difficulty=hc.difficulty)))

計算出幸運值後,調用chain.add_mined_block寫入區塊鏈並廣播

    #成功找到幸運值    def recv_found_nonce(self, bin_nonce, mixhash, mining_hash):        log.info('nonce found', mining_hash=mining_hash.encode('hex'))        #再次打包交易,更新head_candidate        block = self.chain.head_candidate        if block.mining_hash != mining_hash:            log.warn('mining_hash does not match')            return False        block.header.mixhash = mixhash        block.header.nonce = bin_nonce        #添加新塊並廣播        if self.chain.add_mined_block(block):            log.debug('mined block %d (%s) added to chain' % (                block.number, encode_hex(block.hash[:8])))            return True        else:            log.debug('failed to add mined block %d (%s) to chain' % (                block.number, encode_hex(block.hash[:8])))            return False
    def add_mined_block(self, block):        log.debug('adding mined block', block=block)        assert isinstance(block, Block)        #添加新塊        if self.chain.add_block(block):            log.debug('added', block=block, ts=time.time())            assert block == self.chain.head            self.transaction_queue = self.transaction_queue.diff(block.transactions)            self._head_candidate_needs_updating = True            #廣播新塊            self.broadcast_newblock(block, chain_difficulty=self.chain.get_score(block))            return True        log.debug('failed to add', block=block, ts=time.time())        return False

在寫入新塊完成後,調用new_head_cb回調,再次開始挖礦
至此,整個迴圈的過程就是礦工不斷打包交易挖礦到廣播新塊的過程 ChainService

service start 服務啟動
服務啟動時將eth_protocol(以太坊協議)中command對應的回呼函數添加到eth_protocol執行個體中

先看看eth_protocol協議
以太坊協議定義eth_protocol.py

每個peer對象都有一個eth_protocol執行個體。在p2p協議中,當節點收到hello資料包的時候,便初始化所有協議執行個體,其中包括eth_protocol
peer對象receive hello
初始化eth_protocol

    def connect_service(self, service):        assert isinstance(service, WiredService)        protocol_class = service.wire_protocol        assert issubclass(protocol_class, BaseProtocol)        # create protcol instance which connects peer with serivce        protocol = protocol_class(self, service)        # register protocol        assert protocol_class not in self.protocols        log.debug('registering protocol', protocol=protocol.name, peer=self)        self.protocols[protocol_class] = protocol        self.mux.add_protocol(protocol.protocol_id)        protocol.start()#調用chain_service.on_wire_protocol_start(self)

chain service為該執行個體添加回呼函數,並向對方節點發送status資料包

    def on_wire_protocol_start(self, proto):        log.debug('----------------------------------')        log.debug('on_wire_protocol_start', proto=proto)        assert isinstance(proto, self.wire_protocol)        # register callbacks        proto.receive_status_callbacks.append(self.on_receive_status)#處理status資料包        proto.receive_newblockhashes_callbacks.append(self.on_newblockhashes)        proto.receive_transactions_callbacks.append(self.on_receive_transactions)        proto.receive_getblockheaders_callbacks.append(self.on_receive_getblockheaders)        proto.receive_blockheaders_callbacks.append(self.on_receive_blockheaders)        proto.receive_getblockbodies_callbacks.append(self.on_receive_getblockbodies)        proto.receive_blockbodies_callbacks.append(self.on_receive_blockbodies)        proto.receive_newblock_callbacks.append(self.on_receive_newblock)        # send status  一旦串連就向對方發送自己的區塊鏈狀態status        head = self.chain.head        proto.send_status(chain_difficulty=self.chain.get_score(head), chain_head_hash=head.hash,                          genesis_hash=self.chain.genesis.hash)

eth_protocol協議中包括
Status 與新節點建立串連後,互相發送自己的區塊鏈狀態
NewBlockHashes 向網路中廣播一批新區塊的hash
Transactions 包含一批交易的資料包
GetBlockHashes 從指定hash開始,請求一批BlockHashes
BlockHashes 返回GetBlockHashes請求
GetBlocks 從指定hash開始,請求一批Block
Blocks 返回GetBlocks請求
NewBlock 礦工挖礦後廣播新區塊,節點接到該區塊後驗證後添加到本地

1.收到status:
某個已經完成握手的peer節點發送他當前的區塊鏈網路狀態ethereum state,status資料包是節點之間建立串連後收到的第一個資料包。
通過status資料包來擷取網路中最新的區塊並更新本地的區塊鏈

    class status(BaseProtocol.command):        """        protocolVersion: The version of the Ethereum protocol this peer implements. 30 at present.        networkID: The network version of Ethereum for this peer. 0 for the official testnet.        totalDifficulty: Total Difficulty of the best chain. Integer, as found in block header.        latestHash: The hash of the block with the highest validated total difficulty.        GenesisHash: The hash of the Genesis block.        """        cmd_id = 0        sent = False        structure = [            ('eth_version', rlp.sedes.big_endian_int),            ('network_id', rlp.sedes.big_endian_int),            ('chain_difficulty', rlp.sedes.big_endian_int),#totalDifficulty,該鏈的總難度            ('chain_head_hash', rlp.sedes.binary),#latestHash,頭區塊hash            ('genesis_hash', rlp.sedes.binary)]#初始區塊hash        def create(self, proto, chain_difficulty, chain_head_hash, genesis_hash):            self.sent = True            network_id = proto.service.app.config['eth'].get('network_id', proto.network_id)            return [proto.version, network_id, chain_difficulty, chain_head_hash, genesis_hash]

receive packet收到資料包
receive status解析資料包定位到status處理函數
之前登入回調eth_protocol執行個體初始化時登入
on receive status receive status處理函數

這裡注意一點,這裡看的是DAO事件之前的版本,不是分叉過後的版本(還是先按正常邏輯來。。)

    def on_receive_status(self, proto, eth_version, network_id, chain_difficulty, chain_head_hash,                          genesis_hash):        log.debug('----------------------------------')        log.debug('status received', proto=proto, eth_version=eth_version)        assert eth_version == proto.version, (eth_version, proto.version)        #必須是同一個networkid        if network_id != self.config['eth'].get('network_id', proto.network_id):            log.warn("invalid network id", remote_network_id=network_id,                     expected_network_id=self.config['eth'].get('network_id', proto.network_id))            raise eth_protocol.ETHProtocolError('wrong network_id')        # check genesis        #初始區塊hash必須一致        if genesis_hash != self.chain.genesis.hash:            log.warn("invalid genesis hash", remote_id=proto, genesis=genesis_hash.encode('hex'))            raise eth_protocol.ETHProtocolError('wrong genesis block')        # request chain        #調用同步器同步資料        self.synchronizer.receive_status(proto, chain_head_hash, chain_difficulty)        # send transactions        #擷取網路中已知的但尚未被計入區塊的交易transactions        transactions = self.chain.get_transactions()        if transactions:            log.debug("sending transactions", remote_id=proto)            #將這些交易告知對方            proto.send_transactions(*transactions)

synchronizer同步器同步資料receive_status:

    def receive_status(self, proto, blockhash, chain_difficulty):        "called if a new peer is connected"        log.debug('status received', proto=proto, chain_difficulty=chain_difficulty)        # memorize proto with difficulty        #將改節點區塊鏈總難度記下        self._protocols[proto] = chain_difficulty        #對方頭區塊hash已經在本地存在,則忽略        if self.chainservice.knows_block(blockhash) or self.synctask:            log.debug('existing task or known hash, discarding')            return        if self.force_sync:            blockhash, chain_difficulty = self.force_sync            log.debug('starting forced syctask', blockhash=blockhash.encode('hex'))            self.synctask = SyncTask(self, proto, blockhash, chain_difficulty)        #如果這條鏈總難度比自己的大,則認為這條鏈狀態更新一點,並從他給出的頭區塊hash開始同步區塊,同步的時候從已串連節點中總難度chain_difficulty最大的那個開始同步(但是這並不能保證同步的鏈就一定是主鏈,後面會分析如果同步的鏈不是主鏈的情況)        elif chain_difficulty > self.chain.head.chain_difficulty():            log.debug('sufficient difficulty')            self.synctask = SyncTask(self, proto, blockhash, chain_difficulty)

fetch_hashchain 向網路請求以指定hash作為頭區塊的一批區塊hash

    def fetch_hashchain(self):        log_st.debug('fetching hashchain')        blockhashes_chain = [self.blockhash]  # youngest to oldest        # For testing purposes: skip the hash downoading stage        # import ast        # blockhashes_chain = ast.literal_eval(open('/home/vub/blockhashes.pyast').read())[:299000]        blockhash = self.blockhash = blockhashes_chain[-1]        assert blockhash not in self.chain        # get block hashes until we found a known one        max_blockhashes_per_request = self.initial_blockhashes_per_request        while blockhash not in self.chain:            # proto with highest_difficulty should be the proto we got the newblock from            blockhashes_batch = []            # try with protos            protocols = self.protocols            if not protocols:                log_st.warn('no protocols available')                return self.exit(success=False)            #這裡的protocols是各個已串連節點peer對象的eth_protocol執行個體,按鏈總難度大小排序,難度最大在前面            for proto in protocols:                log.debug('syncing with', proto=proto)                if proto.is_stopped:                    continue                # request                assert proto not in self.requests                deferred = AsyncResult()                self.requests[proto] = deferred                #從指定hash開始,請求一批BlockHashes                proto.send_getblockhashes(blockhash, max_blockhashes_per_request)                try:                    #擷取到這批BlockHashes                    blockhashes_batch = deferred.get(block=True,                                                     timeout=self.blockhashes_request_timeout)                except gevent.Timeout:                    log_st.warn('syncing hashchain timed out')                    continue                finally:                    # is also executed 'on the way out' when any other clause of the try statement                    # is left via a break, continue or return statement.                    del self.requests[proto]                if not blockhashes_batch:                    log_st.warn('empty getblockhashes result')                    continue                if not all(isinstance(bh, bytes) for bh in blockhashes_batch):                    log_st.warn('got wrong data type', expected='bytes',                                received=type(blockhashes_batch[0]))                    continue                break            if not blockhashes_batch:                log_st.warn('syncing failed with all peers', num_protos=len(protocols))                return self.exit(success=False)            #在擷取到的這批blockhashes中直到找到一個blockhash是自己資料庫中有的,注意這裡只要找到一個自己有的blockhash就可以了,而且這個blockhash不一定是自己本地chain的頭區塊,因為本地的頭區塊有可能不是在主鏈上的區塊,上面已經提到過這點。節點總是會去同步最長的那條鏈,而且可能是從自己本地chain的頭區塊的父輩區塊的某個區塊開始同步,這樣就提供了一定的錯誤修正機制,讓節點可以糾正到主鏈上去。            for blockhash in blockhashes_batch:  # youngest to oldest                assert utils.is_string(blockhash)                if blockhash not in self.chain:                    blockhashes_chain.append(blockhash)                else:                    log_st.debug('found known blockhash', blockhash=utils.encode_hex(blockhash),                                 is_genesis=bool(blockhash == self.chain.genesis.hash))                    break            log_st.debug('downloaded ' + str(len(blockhashes_chain)) + ' block hashes, ending with %s' % utils.encode_hex(blockhashes_chain[-1]))            max_blockhashes_per_request = self.max_blockhashes_per_request        #取到最長鏈的這批blockhash後,開始同步區塊        self.fetch_blocks(blockhashes_chain)

fetch blocks 向網路請求同步這批區塊

    def fetch_blocks(self, blockhashes_chain):        # fetch blocks (no parallelism here)        log_st.debug('fetching blocks', num=len(blockhashes_chain))        assert blockhashes_chain        blockhashes_chain.reverse()  # oldest to youngest        num_blocks = len(blockhashes_chain)        num_fetched = 0        while blockhashes_chain:            blockhashes_batch = blockhashes_chain[:self.max_blocks_per_request]            t_blocks = []            # try with protos            protocols = self.protocols            if not protocols:                log_st.warn('no protocols available')                return self.exit(success=False)            #向每個節點請求            for proto in protocols:                if proto.is_stopped:                    continue                assert proto not in self.requests                # request                log_st.debug('requesting blocks', num=len(blockhashes_batch))                deferred = AsyncResult()                self.requests[proto] = deferred                #向網路請求這批hash值對應的區塊                proto.send_getblocks(*blockhashes_batch)                try:                    t_blocks = deferred.get(block=True, timeout=self.blocks_request_timeout)                except gevent.Timeout:                    log_st.warn('getblocks timed out, trying next proto')                    continue                finally:                    del self.requests[proto]                if not t_blocks:                    log_st.warn('empty getblocks reply, trying next proto')                    continue                elif not isinstance(t_blocks[0], TransientBlock):                    log_st.warn('received unexpected data', data=repr(t_blocks))                    t_blocks = []                    continue                # we have results                if not [b.header.hash for b in t_blocks] == blockhashes_batch[:len(t_blocks)]:                    log_st.warn('received wrong blocks, should ban peer')                    t_blocks = []                    continue                break            # add received t_blocks            num_fetched += len(t_blocks)            log_st.debug('received blocks', num=len(t_blocks), num_fetched=num_fetched,                         total=num_blocks, missing=num_blocks - num_fetched)            if not t_blocks:                log_st.warn('failed to fetch blocks', missing=len(blockhashes_chain))                return self.exit(success=False)            ts = time.time()            log_st.debug('adding blocks', qsize=self.chainservice.block_queue.qsize())            for t_block in t_blocks:                b = blockhashes_chain.pop(0)                assert t_block.header.hash == b                assert t_block.header.hash not in blockhashes_chain                #將擷取的block添加到隊列,最後添加到本機資料庫                self.chainservice.add_block(t_block, proto)  # this blocks if the queue is full            log_st.debug('adding blocks done', took=time.time() - ts)        # done        last_block = t_block        assert 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.