Example #1
0
    def check_block_place(
        cls,
        block: Block,
        active_chain: BlockChain,
        utxo_set: BaseUTXO_Set,
        mempool: BaseMemPool,
        side_branches: Iterable[BlockChain],
    ) -> int:
        if Block.locate_block(block.id, active_chain, side_branches)[0]:
            logger.debug(
                f"[p2p] ignore block that already be seen: {block.id}")
            return None  # already seen block

        try:
            chain_idx = block.validate_block(active_chain, utxo_set, mempool,
                                             side_branches)
        except BlockValidationError as e:
            if e.to_orphan:
                logger.info(
                    f"[p2p]  block {block.id} failed validation as an orphan block"
                )
                return -1  # orphan block
            else:
                logger.exception(
                    f"[p2p] block {block.id} failed validation due to internal error in this block"
                )
                return -2  # Internal error in this block

        if chain_idx != Params.ACTIVE_CHAIN_IDX and len(
                side_branches) < chain_idx:
            logger.info(
                f"[p2p] creating a new side branch (idx {chain_idx}) for block {block.id}"
            )
            side_branches.append(BlockChain(idx=chain_idx, chain=[]))

            prev_block, prev_block_height, prev_block_chain_idx = Block.locate_block(
                block.prev_block_hash, active_chain, side_branches)
            if prev_block_chain_idx != Params.ACTIVE_CHAIN_IDX:  # branch of a branch
                logger.info(
                    f"[p2p] branch (idx {chain_idx}) of an existing side branch (idx {prev_block_chain_idx}) for block {block.id}"
                )
                branch_fork_height = Block.locate_block(
                    block.prev_block_hash,
                    side_branches[prev_block_chain_idx - 1])[1]
                side_branches[chain_idx - 1].chain = list(
                    side_branches[prev_block_chain_idx -
                                  1].chain[:branch_fork_height])

        return chain_idx
Example #2
0
    def handleBlockSyncReq(self, blockid: str, peer: Peer):

        logger.info(f"[p2p] receive BlockSyncReq from peer {peer}")

        #with self.chain_lock:
        height = Block.locate_block(blockid, self.active_chain)[1]
        if height is None:
            logger.info(
                f'[p2p] cannot find blockid {blockid}, and do nothing for this BlockSyncReq from peer {peer}'
            )
            with self.chain_lock:
                block = copy.deepcopy(self.active_chain.chain[-1])

            message = Message(Actions.BlockRev, block, Params.PORT_CURRENT)
            self.request.sendall(Utils.encode_socket_data(message))

            return
        else:
            logger.info(
                f"[p2p] receive BlockSyncReq at height {height} from peer {peer}"
            )

        with self.chain_lock:
            blocks = copy.deepcopy(
                self.active_chain.chain[height:(height + Params.CHUNK_SIZE)])

        logger.info(f"[p2p] sending {len(blocks)} blocks to {peer}")

        message = Message(Actions.BlocksSyncGet, blocks, Params.PORT_CURRENT)
        self.request.sendall(Utils.encode_socket_data(message))

        if (peer not in self.peerManager.getAllPeers()) and not (peer == Peer('127.0.0.1', Params.PORT_CURRENT) or \
                peer == Peer('localhost', Params.PORT_CURRENT) or \
                    peer.ip == '0.0.0.0' or \
                    peer == Peer(Params.PUBLIC_IP, Params.PORT_CURRENT)):

            if Utils.is_peer_valid(peer):
                with self.peers_lock:
                    self.peerManager.add(peer)
                self.sendPeerExtend()
            else:
                self.peerManager.block(peer)
Example #3
0
        def _reorg_and_succeed(active_chain: BaseBlockChain, side_branches: Iterable[BaseBlockChain], \
                                mempool: MemPool, utxo_set:UTXO_Set, \
                                mine_interrupt: threading.Event) -> bool:


            def _do_reorg(branch_idx: int, side_branches: Iterable[BaseBlockChain], active_chain: BaseBlockChain, \
                           fork_height: int, mempool: MemPool, utxo_set:UTXO_Set, \
                           mine_interrupt: threading.Event) -> bool:

                branch_chain = side_branches[branch_idx - 1]

                fork_block = active_chain.chain[fork_height - 1]

                def disconnect_to_fork(active_chain: BaseBlockChain = active_chain, fork_block: Block = fork_block):
                    while active_chain.chain[-1].id != fork_block.id:
                        yield active_chain.disconnect_block(mempool, utxo_set)

                old_active = list(disconnect_to_fork(active_chain, fork_block))[::-1]

                assert branch_chain.chain[0].prev_block_hash == active_chain.chain[-1].id

                def rollback_reorg():

                    list(disconnect_to_fork(active_chain, fork_block))

                    for block in old_active:
                        assert active_chain.connect_block(block, active_chain, side_branches, mempool, utxo_set, \
                                                          mine_interrupt, \
                                                          doing_reorg=True) == True

                for block in branch_chain.chain:
                    if not active_chain.connect_block(block, active_chain, side_branches, mempool, utxo_set, \
                                                      mine_interrupt, doing_reorg=True):

                        logger.info(f'[ds] reorg of branch {branch_idx} to active_chain failed, decide to rollback')
                        rollback_reorg()
                        return False

                branch_chain.chain = list(old_active)

                logger.info(f'[ds] chain reorg successful with new active_chain height {active_chain.height} and top block id {active_chain.chain[-1].id}')

                return True


            reorged = False
            frozen_side_branches = list(side_branches)

            for _, branch_chain in enumerate(frozen_side_branches):
                branch_idx = branch_chain.idx
                fork_block, fork_height, _ = Block.locate_block(branch_chain.chain[0].prev_block_hash, active_chain)
                active_height = active_chain.height
                branch_height_real = branch_chain.height + fork_height

                if branch_height_real > active_height:
                    logger.info(f'[ds] decide to reorg branch {branch_idx} with height {branch_height_real} to active_chain with real height {active_height}')
                    reorged |= _do_reorg(branch_idx, side_branches, active_chain, fork_height, mempool, \
                                         utxo_set, mine_interrupt)
                    if reorged is True:
                        return reorged

            return reorged
Example #4
0
    def do_connect_block_and_after(cls, block: Block, chain_idx, active_chain: BlockChain, side_branches: Iterable[BlockChain], \
                                mempool: BaseMemPool, utxo_set: BaseUTXO_Set, mine_interrupt: threading.Event) -> bool:
        if int(chain_idx) == int(Params.ACTIVE_CHAIN_IDX):
            if block.block_subsidy_fees != Block.get_block_subsidy(
                    active_chain) + block.calculate_fees(utxo_set):
                #logger.info(f'{block.block_subsidy_fees} != {Block.get_block_subsidy(active_chain)} + {block.calculate_fees(utxo_set)}')
                logger.info(
                    f'[p2p] subsidy and fees of this block are not right, so discard this block and return.'
                )
                #logger.info(f'after check subsid_fees, and give out a logger.exception')
                return False
            else:
                #logger.info(f'[p2p] subsidy and fees of this block are right.')
                pass
            connect_block_success = active_chain.connect_block(block, active_chain, \
                                                    side_branches, \
                                    mempool, utxo_set, mine_interrupt)
        else:
            connect_block_success = side_branches[chain_idx-1].connect_block(block, \
                                             active_chain, side_branches, \
                                    mempool, utxo_set, mine_interrupt)

        if connect_block_success is not False:

            if connect_block_success is not True:  # -1, success and reorg
                #logger.info(f'[p2p] a successful reorg is found, begin to deal with {len(side_branches)} side branches')

                for branch_chain in side_branches:
                    #logger.info(f'[p2p] number of blocks before slim side branch: {len(branch_chain.chain)}')

                    #TCPHandler.printBlockchainIDs(branch_chain, '[p2p] side branch removed from active chain ')

                    fork_height_from_end = 0
                    for block in branch_chain.chain[::-1]:
                        if not Block.locate_block(block.id, active_chain)[0]:
                            if not Block.locate_block(block.prev_block_hash,
                                                      active_chain)[0]:
                                fork_height_from_end += 1
                            else:
                                break
                        else:
                            branch_chain.chain = []
                            logger.info(
                                f'[p2p] the whole body of this branch chain is in active chain'
                            )
                            break
                    if fork_height_from_end >= branch_chain.height and branch_chain.height != 0:
                        branch_chain.chain = []
                        logger.info(
                            f'[p2p] all blocks are orphans to the current active chain'
                        )

                    else:
                        for num_to_pop in range(
                                1, branch_chain.height - fork_height_from_end):
                            branch_chain.chain.pop(0)
                    #logger.info(f'[p2p] number of blocks after slim side branch: {len(branch_chain.chain)}')

            side_branches_to_discard = []
            for branch_chain in side_branches:
                if branch_chain.chain == []:
                    side_branches_to_discard.append(branch_chain)
                    continue
                fork_block, fork_height, _ = Block.locate_block(
                    branch_chain.chain[0].prev_block_hash, active_chain)
                if fork_block is None:
                    side_branches_to_discard.append(branch_chain)

                branch_height_real = branch_chain.height + fork_height
                if active_chain.height - branch_height_real > Params.MAXIMUM_ALLOWABLE_HEIGHT_DIFF:
                    side_branches_to_discard.append(branch_chain)
            if len(side_branches_to_discard) > 0:
                logger.info(
                    f'[p2p] ## delete {len(side_branches_to_discard)} side branches beyond MAXIMUM_ALLOWABLE_HEIGHT_DIFF'
                )
                for branch_chain in side_branches_to_discard:
                    side_branches.remove(branch_chain)
            for index, branch_chain in enumerate(side_branches, 1):
                branch_chain.index = index
        else:
            logger.exception(f'[p2p] connect_block returned a False value')
        return True
Example #5
0
    def handleBlockRev(self, block: Block, peer: Peer):
        if not isinstance(block, Block):
            logger.info(f'[p2p] {block} is not a Block')
            return
        else:
            if peer != Peer('127.0.0.1', Params.PORT_CURRENT):
                logger.info(
                    f"[p2p] received block {block.id} from peer {peer}")
            with self.chain_lock:

                chain_idx  = TCPHandler.check_block_place(block, self.active_chain, self.utxo_set, self.mempool, \
                                                          self.side_branches)
                ret_outside_lock = False
                if chain_idx is not None and chain_idx >= 0:
                    ret_outside_lock = TCPHandler.do_connect_block_and_after(block, chain_idx, self.active_chain, self.side_branches, \
                                                       self.mempool, self.utxo_set, self.mine_interrupt)
                    if not ret_outside_lock:
                        #logger.info(f'####### out of chain_lock: {chain_use_id} of handleBlockRev')
                        return
                    if not Block.locate_block(block.id, self.active_chain,
                                              self.side_branches)[0]:
                        return

                #logger.info(f'####### out of chain_lock: {chain_use_id} of handleBlockRev')

            if ret_outside_lock is True:
                if len(self.active_chain.chain
                       ) % Params.SAVE_PER_SIZE == 0 or len(
                           self.active_chain.chain) <= 5:
                    Persistence.save_to_disk(self.active_chain)

            if chain_idx is not None and chain_idx >= 0:
                peers = self.peerManager.getPeers()
                if len(peers) > 0:
                    for _peer in random.sample(peers, min(len(peers), 5)):
                        if _peer != peer:
                            ret = Utils.send_to_peer(
                                Message(Actions.BlockRev, block,
                                        Params.PORT_CURRENT), _peer)
                            if ret == 1:
                                if _peer in peers:
                                    try:
                                        with self.peers_lock:
                                            self.peerManager.block(_peer)
                                    except:
                                        pass
                            elif ret != 0:
                                with self.peers_lock:
                                    self.peerManager.addLog(_peer, 1)
                            else:
                                with self.peers_lock:
                                    self.peerManager.addLog(_peer, 0)

                self.sendPeerExtend()

            elif chain_idx is None:
                logger.info(
                    f'[p2p] already seen block {block.id}, and do nothing')
            elif chain_idx == -1:
                # case of orphan block
                message = Message(Actions.TopBlocksSyncReq, 50,
                                  Params.PORT_CURRENT)
                if peer == Peer('127.0.0.1', Params.PORT_CURRENT):
                    getpeers = self.peerManager.getPeers(2)
                    if len(getpeers) > 0:
                        peer = random.sample(getpeers, 1)[0]
                    else:
                        return

                try:
                    with socket.socket(
                            socket.AF_INET, socket.SOCK_STREAM
                    ) as s:  #socket.create_connection(peer(), timeout=25) as s:
                        s.connect(peer())
                        s.sendall(Utils.encode_socket_data(message))
                        logger.info(
                            f'[p2p] succeed to send TopBlocksSyncReq to {peer}'
                        )
                        msg_len = int(binascii.hexlify(s.recv(4) or b'\x00'),
                                      16)
                        data = b''
                        while msg_len > 0:
                            tdat = s.recv(1024)
                            data += tdat
                            msg_len -= len(tdat)
                    s.close()
                except ConnectionRefusedError:
                    self.peerManager.block(peer)
                except:
                    self.peerManager.addLog(peer, 1)
                else:
                    self.peerManager.addLog(peer, 0)

                    message = Utils.deserialize(data.decode(),
                                                self.gs) if data else None
                    if message:
                        blocks = message.data
                        if blocks[0].prev_block_hash:
                            if not Block.locate_block(
                                    blocks[0].prev_block_hash,
                                    self.active_chain, self.side_branches)[0]:
                                logger.info(
                                    f"received sync blocks for the orphan block in handleBlockRev, but the first blocks's pre_block_hash cannot be seen on the chains"
                                )
                                self.peerManager.block(peer)
                                return
                        else:
                            blocks.pop(0)
                            if not Block.locate_block(
                                    blocks[0].prev_block_hash,
                                    self.active_chain, self.side_branches)[0]:
                                logger.info(
                                    f"received sync blocks for the orphan block in handleBlockRev, but the first blocks's pre_block_hash cannot be seen on the chains"
                                )
                                self.peerManager.block(peer)
                                return

                        message = Message(message.action, message.data,
                                          Params.PORT_CURRENT, peer)
                        ret = Utils.send_to_peer(message,
                                                 Peer('127.0.0.1',
                                                      Params.PORT_CURRENT),
                                                 itself=True)

                        if ret != 0:
                            logger.info(
                                f'[p2p] cannot send data to itself, and its current port is {Params.PORT_CURRENT}'
                            )
                        else:
                            #logger.info(f'[p2p] send BlocksSyncGet to itself')
                            pass
                    else:
                        logger.info(f'[p2p] recv nothing from peer {peer}')
Example #6
0
    def handleBlockSyncGet(self, blocks: Iterable[Block], peer: Peer):
        if peer != Peer('127.0.0.1', Params.PORT_CURRENT):
            logger.info(
                f"[p2p] receive {len(blocks)} blocks for BlockSyncGet from {peer}"
            )
        new_blocks = [
            block for block in blocks if not Block.locate_block(
                block.id, self.active_chain, self.side_branches)[0]
        ]
        logger.info(
            f'[p2p] {len(new_blocks)} of {len(blocks)} blocks from {peer} is new'
        )

        #if not new_blocks:
        #    logger.info('[p2p] initial block download complete')
        #    self.ibd_done.set()
        #    return
        #else:
        #    self.ibd_done.clear()

        if not TCPHandler.check_blocks_headers(new_blocks):
            return

        with self.chain_lock:

            for block in new_blocks:
                if Block.locate_block(block.id, self.active_chain,
                                      self.side_branches)[0]:
                    new_blocks.pop(0)
                else:
                    break
            if not new_blocks:
                return

            chain_idx  = TCPHandler.check_block_place(new_blocks[0], self.active_chain, self.utxo_set, \
                                                          self.mempool, self.side_branches)
            if chain_idx is None:
                logger.info(
                    f'received blocks have been seen in BlockSyncGet, do nothing and return'
                )
                return
            if chain_idx <= -1:
                logger.info(f'[p2p] orphan or wrong blocks')
                if peer != Peer('127.0.0.1', Params.PORT_CURRENT):
                    with self.peers_lock:
                        self.peerManager.block(peer)
                return

            if chain_idx >= 1:
                # if is side branches, append the blocks (one block left) to the side branches directly
                logger.info(
                    f'[p2p] just append {len(new_blocks)-1} blocks to side branch {chain_idx}, leaving one block to '
                    f'be coped with method do_connect_block_and_after')
                while len(new_blocks) >= 2:
                    self.side_branches[chain_idx - 1].chain.append(
                        new_blocks.pop(0))

            for block in new_blocks:
                if not TCPHandler.do_connect_block_and_after(block, chain_idx, self.active_chain, self.side_branches, \
                                                self.mempool, self.utxo_set, self.mine_interrupt):
                    return

        if chain_idx == Params.ACTIVE_CHAIN_IDX:
            if len(self.active_chain.chain) % Params.SAVE_PER_SIZE == 0 or len(
                    self.active_chain.chain) <= 5:
                Persistence.save_to_disk(self.active_chain)

            logger.info(
                f'[p2p] current chain height {self.active_chain.height}, and continue initial block download ... '
            )

        if peer not in self.peerManager.getAllPeers():
            if peer== Peer('127.0.0.1', Params.PORT_CURRENT) or \
                            peer == Peer('localhost', Params.PORT_CURRENT) or \
                                peer.ip == '0.0.0.0' or \
                                peer == Peer(Params.PUBLIC_IP, Params.PORT_CURRENT):
                return
            if Utils.is_peer_valid(peer):
                with self.peers_lock:
                    self.peerManager.add(peer)  #self.peers.append(peer)
                self.sendPeerExtend()
            else:
                self.peerManager.block(peer)

        if peer == Peer('127.0.0.1', Params.PORT_CURRENT):
            logger.info(f'peer in handleBlockSyncGet cannot be itself')
            return

        top_id_chain_idx = new_blocks[-1].id
        message = Message(Actions.BlocksSyncReq, top_id_chain_idx,
                          Params.PORT_CURRENT)
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.connect(peer())
                s.sendall(Utils.encode_socket_data(message))
                logger.info(f'[p2p] succeed to send BlocksSyncReq to {peer}')
                msg_len = int(binascii.hexlify(s.recv(4) or b'\x00'), 16)
                data = b''
                while msg_len > 0:
                    tdat = s.recv(1024)
                    data += tdat
                    msg_len -= len(tdat)
            s.close()
        except ConnectionRefusedError:
            self.peerManager.block(peer)
        except:
            self.peerManager.addLog(peer, 1)
        else:
            self.peerManager.addLog(peer, 0)

            message = Utils.deserialize(data.decode(),
                                        self.gs) if data else None
            if message:
                logger.info(
                    f'[p2p] received blocks for sync blocks from peer {peer}')
                message = Message(message.action, message.data,
                                  Params.PORT_CURRENT, peer)
                ret = Utils.send_to_peer(message,
                                         Peer('127.0.0.1',
                                              Params.PORT_CURRENT),
                                         itself=True)
                if ret != 0:
                    logger.info(
                        f'[p2p] cannot send data to itself, and its current port is {Params.PORT_CURRENT}'
                    )

            else:
                logger.info(f'[p2p] recv nothing from peer {peer}')