Example #1
0
    def test_verify_tree(self):
        raw_leaves = [secrets.token_bytes(16) for _ in range(16)]

        m = MerkleTree(raw_leaves)

        tree_nodes = m.nodes[len(m.nodes) // 2:]
        tree_hash = m.hash_of_nodes()

        self.assertTrue(MerkleTree.verify_tree(tree_nodes, tree_hash))
Example #2
0
class DelegateConsensusState(DelegateBaseState):
    """Consensus state is where delegates pass around a merkelized version of their transaction queues, publish them to
    one another, confirm the signature is valid, and then vote/tally the results"""
    NUM_DELEGATES = len(Constants.Testnet.Delegates)
    """
    TODO -- move this 'variable setting' logic outside of init. States should have their own constructor, which init
    will call in the superclass. Optionally, states should be able to set a variable if they want all their properties
    flushed each time.
    """
    def reset_attrs(self):
        self.signatures = []
        self.signature = None
        self.merkle = None
        self.merkle_hash = None
        self.in_consensus = False

    # TODO -- i think this should only occur when entering from Interpretting state yea?
    @enter_from_any
    def enter_any(self, prev_state):
        assert self.parent.interpreter.queue_size > 0, "Entered consensus state, but interpreter queue is empty!"

        # Merkle-ize transaction queue and create signed merkle hash
        all_tx = self.parent.interpreter.queue_binary
        self.log.info(
            "Delegate got tx from interpreter queue: {}".format(all_tx))
        self.merkle = MerkleTree(all_tx)
        self.merkle_hash = self.merkle.hash_of_nodes()
        self.log.info("Delegate got merkle hash {}".format(self.merkle_hash))
        self.signature = ED25519Wallet.sign(self.parent.signing_key,
                                            self.merkle_hash)

        # Create merkle signature message and publish it
        merkle_sig = MerkleSignature.create(sig_hex=self.signature,
                                            timestamp='now',
                                            sender=self.parent.verifying_key)
        self.log.info("Broadcasting signature {}".format(self.signature))
        self.parent.composer.send_pub_msg(
            filter=Constants.ZmqFilters.DelegateDelegate, message=merkle_sig)

        # Now that we've computed/composed the merkle tree hash, validate all our pending signatures
        for sig in [
                s for s in self.parent.pending_sigs if self.validate_sig(s)
        ]:
            self.signatures.append(sig)

        self.check_majority()

    @exit_to_any
    def exit_any(self, next_state):
        self.reset_attrs()

    def validate_sig(self, sig: MerkleSignature) -> bool:
        assert self.merkle_hash is not None, "Cannot validate signature without our merkle hash set"
        self.log.debug("Validating signature: {}".format(sig))

        # Verify sender's vk exists in the state
        if sig.sender not in VKBook.get_delegates():
            self.log.debug(
                "Received merkle sig from sender {} who was not registered nodes {}"
                .format(sig.sender, VKBook.get_delegates()))
            return False
        # Verify we havne't received this signature already
        if sig in self.signatures:
            self.log.debug(
                "Already received a signature from sender {}".format(
                    sig.sender))
            return False

        # Below is just for debugging, so we can see if a signature cannot be verified
        if not sig.verify(self.merkle_hash):
            self.log.warning("Delegate could not verify signature")
            self.log.debug("Signature: {}".format(sig))

        return sig.verify(self.merkle_hash)

    def check_majority(self):
        self.log.debug(
            "delegate has {} signatures out of {} total delegates".format(
                len(self.signatures), self.NUM_DELEGATES))

        if len(self.signatures) >= Constants.Testnet.Majority:
            self.log.info("Delegate in consensus!")
            self.in_consensus = True

            # Create BlockContender and send it to all Masternode(s)
            bc = BlockContender.create(signatures=self.signatures,
                                       merkle_leaves=self.merkle.nodes)
            for mn_vk in VKBook.get_masternodes():
                self.parent.composer.send_request_msg(message=bc, vk=mn_vk)

    @input(MerkleSignature)
    def handle_sig(self, sig: MerkleSignature):
        if self.validate_sig(sig):
            self.signatures.append(sig)
            self.check_majority()

    @input_request(BlockDataRequest)
    def handle_blockdata_req(self, block_data: BlockDataRequest):
        # Development check -- should be removed in production
        assert block_data.tx_hash in self.merkle.leaves, "Block hash {} not found in leaves {}"\
            .format(block_data.tx_hash, self.merkle.leaves)

        # import time
        # self.log.debug("sleeping...")
        # time.sleep(1.2)
        # self.log.debug("done sleeping")

        tx_binary = self.merkle.data_for_hash(block_data.tx_hash)
        self.log.info(
            "Replying to tx hash request {} with tx binary: {}".format(
                block_data.tx_hash, tx_binary))
        reply = BlockDataReply.create(tx_binary)
        return reply

    @input(NewBlockNotification)
    def handle_new_block_notif(self, notif: NewBlockNotification):
        self.log.info("Delegate got new block notification: {}".format(notif))

        # If the new block hash is the same as our 'scratch block', then just copy scratch to state
        if bytes.fromhex(notif.block_hash) == self.merkle_hash:
            self.log.debug("New block hash is the same as ours!!!")
            self.update_from_scratch(new_block_hash=notif.block_hash,
                                     new_block_num=notif.block_num)
            self.parent.transition(DelegateInterpretState)
        # Otherwise, our block is out of consensus and we must request the latest from a Masternode
        else:
            self.log.warning(
                "New block hash {} does not match out own merkle_hash {}".
                format(notif.block_hash, self.merkle_hash))
            # self.parent.transition(DelegateOutConsensusUpdateState)

    def update_from_scratch(self, new_block_hash, new_block_num):
        self.log.info("Copying Scratch to State")
        self.parent.interpreter.flush(update_state=True)

        self.log.info(
            "Updating state_meta with new hash {} and block num {}".format(
                new_block_hash, new_block_num))
        with DB() as db:
            db.execute('delete from state_meta')
            q = insert(db.tables.state_meta).values(number=new_block_num,
                                                    hash=new_block_hash)
            db.execute(q)