Exemplo n.º 1
0
    def _new_block_procedure(self, block: BlockContender, txs: List[bytes]):
        self.log.notice("Masternode attempting to store a new block")

        # Attempt to store block
        try:
            block_hash = BlockStorageDriver.store_block(
                block_contender=block,
                raw_transactions=txs,
                publisher_sk=self.parent.signing_key)
            self.log.important2(
                "Masternode successfully stored new block with {} total transactiosn and block hash {}"
                .format(len(txs), block_hash))
        except BlockStorageException as e:
            self.log.error("Error storing block!\nError = {}".format(e))
            self._try_next_block()
            return

        # Notify delegates that a new block was published
        self.log.info(
            "Masternode sending NewBlockNotification to TESTNET_DELEGATES with new block hash {} "
            .format(block_hash))
        notif = NewBlockNotification.create(
            **BlockStorageDriver.get_latest_block(include_number=False))
        self.parent.composer.send_pub_msg(filter=MASTERNODE_DELEGATE_FILTER,
                                          message=notif,
                                          port=MN_NEW_BLOCK_PUB_PORT)
Exemplo n.º 2
0
    def handle_tx_reply(self, reply: TransactionReply, envelope: Envelope):
        assert self.current_request, "Got TransactionReply, but self.current_request is not set!"
        request = self.current_request
        self.log.debugv(
            "Delegate got tx reply {} with original request {}".format(
                reply, request))

        # Verify that the transactions blobs in the reply match the requested hashes in the request
        if not reply.validate_matches_request(request):
            self.log.error(
                "Could not verify transactions with:\nrequest: {}\nreply: {}".
                format(request, reply))
            return

        # Verify that the transactions match the merkle leaves in the block meta
        if request.tx_hashes != self.current_block.merkle_leaves:
            self.log.error(
                "Requested TX hashes\n{}\ndoes not match current block's merkle leaves\n{}"
                .format(request.tx_hashes, self.current_block))
            return

        # Interpret the transactions
        for contract_blob in reply.transactions:
            self.parent.interpreter.interpret(
                ContractTransaction.from_bytes(contract_blob), async=False)
            self.parent.pending_txs.remove(Hasher.hash(contract_blob))
        self.parent.interpret.flush(update_state=True)

        # Finally, store this new block and update our current block hash. Reset self.current_block, update next block
        BlockStorageDriver.store_block_from_meta(self.current_block)
        self.current_block, self.current_request = None, None
        self._update_next_block()
Exemplo n.º 3
0
    def handle_blockmeta_request(self, request: BlockMetaDataRequest):
        self.log.important(
            "Masternode received BlockMetaDataRequest: {}".format(request))

        # Get a list of block hashes up until this most recent block
        # TODO get_child_block_hashes return an error/assertion/something if block cannot be found
        child_hashes = BlockStorageDriver.get_child_block_hashes(
            request.current_block_hash)
        self.log.debugv(
            "Got descended block hashes {} for block hash {}".format(
                child_hashes, request.current_block_hash))

        # If this hash could not be found or if it was the latest hash, no need to lookup any blocks
        if not child_hashes:
            self.log.debug(
                "Requested block hash {} is already up to date".format(
                    request.current_block_hash))
            reply = BlockMetaDataReply.create(block_metas=None)
            return reply

        # Build a BlockMetaData object for each descendant block
        block_metas = []
        for block_hash in child_hashes:
            block_data = BlockStorageDriver.get_block(hash=block_hash,
                                                      include_number=False)
            meta = BlockMetaData.create(**block_data)
            block_metas.append(meta)

        reply = BlockMetaDataReply.create(block_metas=block_metas)
        return reply
Exemplo n.º 4
0
    def handle_new_block_notif(self, notif: NewBlockNotification):
        self.log.info("Delegate got new block notification: {}".format(notif))

        # DEBUG line -- TODO remove
        if not self.in_consensus:
            self.log.fatal(
                "Received a new block notification, but delegate not in consensus!"
            )

        # If the new block hash is the same as our 'scratch block', then just copy scratch to state
        if notif.prev_block_hash == self.parent.current_hash and self.in_consensus:
            self.log.success("Prev block hash matches ours. We in consensus!")

            # DEBUG LINES TODO remove
            # self.log.critical("delegate current block hash: {}".format(self.parent.current_hash))
            # self.log.critical("database latest block hash: {}".format(BlockStorageDriver.get_latest_block_hash()))
            # self.log.critical("about to store block with prev hash {} and current hash {}".format(notif.prev_block_hash, notif.block_hash))
            # END DEBUG

            BlockStorageDriver.store_block_from_meta(notif)
            self.parent.interpreter.flush(update_state=True)
            self.parent.transition(DelegateInterpretState)
            return

        # Otherwise, our block is out of consensus and we must request the latest from a Masternode!
        else:
            self.log.critical(
                "New block has prev hash {} that does not match our current block hash {} ... :( :("
                .format(notif.prev_block_hash, self.parent.current_hash))
            self.parent.transition(DelegateCatchupState)
            return
Exemplo n.º 5
0
    def handle_new_block_notif(self, notif: NewBlockNotification):
        self.log.info("Delegate got new block notification: {}".format(notif))

        # If we were in consensus and the new block's prev hash matches out current, then commit all interpreted txs
        if notif.prev_block_hash == self.parent.current_hash and self.in_consensus:
            self.log.success("Prev block hash matches ours. Delegate in consensus!")

            BlockStorageDriver.store_block_from_meta(notif)
            self.parent.interpreter.flush(update_state=True)
            self.parent.transition(DelegateInterpretState)
            return

        # Otherwise, our block is out of consensus and we must request the latest from a Masternode
        else:
            # NOTE: the 2 'if' statements below are purely for debugging
            if self.in_consensus:  # TODO should this be an assertion?
                self.log.fatal("Delegate in consensus according to local state, but received new block notification "
                               "that does not match our previous block hash! ")
            if notif.prev_block_hash != self.parent.current_hash:
                self.log.critical("New block has prev hash {} that does not match our current block hash {}"
                                  .format(notif.prev_block_hash, self.parent.current_hash))

            self.log.notice("Delegate transitioning to CatchUpState!\nDelegate's latest block hash {}"
                            "\nNewBlockNotification's prev block hash {}\nDelegate in consensus: {}"
                            .format(self.parent.current_hash, notif.prev_block_hash, self.in_consensus))
            self.parent.transition(DelegateCatchupState)
            return
Exemplo n.º 6
0
    def validate_block_data(self):
        """
        Attempts to validate the block data, raising an Exception if any fields are invalid.
        See BlockStorageDriver.validate_block_data for validation details.
        :return: None
        :raises: An exception if validation fails
        """
        from cilantro.storage.blocks import BlockStorageDriver

        block_data = self.block_dict()
        actual_hash = block_data.pop('hash')

        BlockStorageDriver.validate_block_data(block_data)
        assert actual_hash == self.block_hash, "Computed block hash {} does not match self.block_hash {}"\
                                               .format(actual_hash, self.block_hash)
Exemplo n.º 7
0
    def exit_any(self, next_state):
        assert self.parent.interpreter.queue_size == 0, 'Delegate exiting catchup state with nonempty interpreter queue'

        self.parent.current_hash = BlockStorageDriver.get_latest_block_hash()
        self.log.info(
            "CatchupState exiting. Current block hash set to {}".format(
                self.parent.current_hash))
Exemplo n.º 8
0
 def handle_tx_request(self, request: TransactionRequest):
     self.log.debug(
         "Masternode received TransactionRequest request: {}".format(
             request))
     tx_blobs = BlockStorageDriver.get_raw_transactions(request.tx_hashes)
     reply = TransactionReply.create(raw_transactions=tx_blobs)
     return reply
Exemplo n.º 9
0
    def _new_block_procedure(self, block: BlockContender, txs: List[bytes]):
        self.log.notice("Masternode attempting to store a new block")
        # self.log.debugv("DONE COLLECTING BLOCK DATA FROM LEAVES. Storing new "
        #                 "block with...\ncontender={}\nraw txs={}".format(block, txs))

        # Attempt to store block
        try:
            block_hash = BlockStorageDriver.store_block(block_contender=block, raw_transactions=txs, publisher_sk=self.parent.signing_key)
            self.log.important2("Masternode successfully stored new block with {} total transactiosn and block hash {}"
                                .format(len(txs), block_hash))
        except BlockStorageException as e:
            self.log.error("Error storing block!\nError = {}".format(e))
            self._try_next_block()
            return

        # Notify delegates of new block
        self.log.info("Masternode sending NewBlockNotification to delegates with new block hash {} ".format(block_hash))
        notif = NewBlockNotification.create(**BlockStorageDriver.get_latest_block(include_number=False))
        self.parent.composer.send_pub_msg(filter=masternode_delegate, message=notif)
Exemplo n.º 10
0
 def _bad_block(self):
     block_data = build_valid_block_data()
     hash = BlockStorageDriver.compute_block_hash(block_data)
     bmd = BlockMetaData.create(
         hash=hash,
         merkle_root=block_data['merkle_root'],
         merkle_leaves=block_data['merkle_leaves'],
         prev_block_hash=block_data['prev_block_hash'],
         timestamp=block_data['timestamp'],
         masternode_signature=block_data['masternode_signature'],
         masternode_vk=block_data['masternode_vk'],
         block_contender=block_data['block_contender'])
     return bmd
Exemplo n.º 11
0
    def validate_block_contender(self, block: BlockContender) -> bool:
        """
        Helper method to validate a block contender. For a block contender to be valid it must:
        1) Have a provable merkle tree, ie. all nodes must be hash of (left child + right child)
        2) Be signed by at least 2/3 of the top 32 delegates
        3) Have the correct number of transactions
        4) Be a proposed child of the latest block in the database
        :param block_contender: The BlockContender to validate
        :return: True if the BlockContender is valid, false otherwise
        """
        # Development sanity checks
        # TODO -- in production these assertions should return False instead of raising an Exception
        assert len(
            block.merkle_leaves
        ) >= 1, "Masternode got block contender with no nodes! {}".format(
            block)
        assert len(block.signatures) >= MAJORITY, \
            "Received a block contender with only {} signatures (which is less than a MAJORITY of {}"\
            .format(len(block.signatures), MAJORITY)

        assert len(block.merkle_leaves) == BLOCK_SIZE, \
            "Block contender has {} merkle leaves, but block size is {}!!!\nmerkle_leaves={}"\
            .format(len(block.merkle_leaves), BLOCK_SIZE, block.merkle_leaves)

        # TODO validate the sigs are actually from the top N delegates

        if not block.prev_block_hash == BlockStorageDriver.get_latest_block_hash(
        ):
            self.log.warning(
                "BlockContender validation failed. Block contender's previous block hash {} does not "
                "match DB's latest block hash {}".format(
                    block.prev_block_hash,
                    BlockStorageDriver.get_latest_block_hash()))
            return False

        return block.validate_signatures()
Exemplo n.º 12
0
    def test_serialize_deserialize(self):
        block_data = build_valid_block_data()
        hash = BlockStorageDriver.compute_block_hash(block_data)
        req = BlockMetaData.create(
            hash=hash,
            merkle_root=block_data['merkle_root'],
            merkle_leaves=block_data['merkle_leaves'],
            prev_block_hash=block_data['prev_block_hash'],
            timestamp=block_data['timestamp'],
            masternode_signature=block_data['masternode_signature'],
            masternode_vk=block_data['masternode_vk'],
            block_contender=block_data['block_contender'])
        clone = BlockMetaData.from_bytes(req.serialize())

        self.assertEqual(clone, req)
Exemplo n.º 13
0
 def setUpClass(cls):
     cls.block_metas = []
     for i in range(5):
         block_data = build_valid_block_data()
         hash = BlockStorageDriver.compute_block_hash(block_data)
         bmd = BlockMetaData.create(
             hash=hash,
             merkle_root=block_data['merkle_root'],
             merkle_leaves=block_data['merkle_leaves'],
             prev_block_hash=block_data['prev_block_hash'],
             timestamp=block_data['timestamp'],
             masternode_signature=block_data['masternode_signature'],
             masternode_vk=block_data['masternode_vk'],
             block_contender=block_data['block_contender'])
         cls.block_metas.append(bmd)
Exemplo n.º 14
0
    def _request_update(self):
        """
        Makes a BlockMetaDataRequest to a Masternode. This gives the delegate the block meta data for all new blocks
        that this delegate needs to fetch
        """
        self.parent.current_hash = BlockStorageDriver.get_latest_block_hash()

        self.log.notice(
            "Requesting updates from Masternode with current block hash {}".
            format(self.parent.current_hash))
        request = BlockMetaDataRequest.create(
            current_block_hash=self.parent.current_hash)
        self.parent.composer.send_request_msg(message=request,
                                              vk=VKBook.get_masternodes()[0],
                                              timeout=BLOCK_REQ_TIMEOUT)
Exemplo n.º 15
0
    def handle_blockmeta_request(self, request: BlockMetaDataRequest,
                                 envelope: Envelope):
        vk = envelope.seal.verifying_key
        assert vk in VKBook.get_delegates(
        ), "Got BlockMetaDataRequest from VK {} not in delegate VKBook!".format(
            vk)
        self.log.notice(
            "Masternode received BlockMetaDataRequest from delegate {}\n...request={}"
            .format(vk, request))

        # Get a list of block hashes up until this most recent block
        # TODO get_child_block_hashes return an error/assertion/something if block cannot be found
        child_hashes = BlockStorageDriver.get_child_block_hashes(
            request.current_block_hash)
        self.log.debugv(
            "Got descendant block hashes {} for block hash {}".format(
                child_hashes, request.current_block_hash))

        # If this hash could not be found or if it was the latest hash, no need to lookup any blocks
        if not child_hashes:
            self.log.debug(
                "Requested block hash {} is already up to date".format(
                    request.current_block_hash))
            reply = BlockMetaDataReply.create(block_metas=None)
            return reply

        # Build a BlockMetaData object for each descendant block
        block_metas = []
        for block_hash in child_hashes:
            block_data = BlockStorageDriver.get_block(hash=block_hash,
                                                      include_number=False)
            meta = BlockMetaData.create(**block_data)
            block_metas.append(meta)

        reply = BlockMetaDataReply.create(block_metas=block_metas)
        return reply
Exemplo n.º 16
0
def build_test_contender(tree: MerkleTree = None, prev_block_hash=''):
    """
    Method to build a 'test' block contender. Used exclusively in unit tests.
    """
    from cilantro.storage.blocks import BlockStorageDriver
    from cilantro.constants.nodes import BLOCK_SIZE

    if not tree:
        nodes = [str(i).encode() for i in range(BLOCK_SIZE)]
        tree = MerkleTree(leaves=nodes)

    if not prev_block_hash:
        prev_block_hash = BlockStorageDriver.get_latest_block_hash()

    sigs = [build_test_merkle_sig(msg=tree.root) for _ in range(8)]
    return BlockContender.create(signatures=sigs,
                                 merkle_leaves=tree.leaves_as_hex,
                                 prev_block_hash=prev_block_hash)
Exemplo n.º 17
0
    def block_dict(self):
        """
        A utility property for building a dictionary with keys for each column in the 'blocks' table. This is used to
        facilitate interaction with BlockStorageDriver
        :param include_hash: True if the 'hash' key should be included in the block dictionary.
        :return: A dictionary, containing a key for each column in the blocks table. The 'hash' column can be omitted
        by passing include_hash=False
        """

        from cilantro.storage.blocks import BlockStorageDriver

        block_data = {
            'block_contender': self.block_contender,
            'timestamp': self.timestamp,
            'merkle_root': self.merkle_root,
            'merkle_leaves': self._data.merkleLeaves.decode(),
            'prev_block_hash': self.prev_block_hash,
            'masternode_signature': self.masternode_signature,
            'masternode_vk': self.masternode_vk,
        }
        block_data['hash'] = BlockStorageDriver.compute_block_hash(block_data)

        return block_data
Exemplo n.º 18
0
 def test_fn(self):
     block_data = build_valid_block_data()
     hash = BlockStorageDriver.compute_block_hash(block_data)
     return fn(self, block_data, hash)
Exemplo n.º 19
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     # Properties shared among all states (ie via self.parent.some_prop)
     self.pending_sigs, self.pending_txs = deque(), deque()
     self.interpreter = SenecaInterpreter()
     self.current_hash = BlockStorageDriver.get_latest_block_hash()
Exemplo n.º 20
0
 def _general_entry(self):
     self.parent.current_hash = BlockStorageDriver.get_latest_block_hash()
     self.log.notice(
         "Delegate entering interpret state with current block hash {}".
         format(self.parent.current_hash))