def test_deserialize_valid_object(self): """ Tests that the same object is recovered when serialized and deserialized """ msg = b'payload' nodes = ['A' * 64, 'B' * 64, 'C' * 64, 'D' * 64] sig1, sk1, vk1 = self._create_merkle_sig(msg) sig2, sk2, vk2 = self._create_merkle_sig(msg) sig3, sk3, vk3 = self._create_merkle_sig(msg) sig4, sk4, vk4 = self._create_merkle_sig(msg) signatures = [sig1, sig2, sig3, sig4] bc = BlockContender.create(signatures, nodes, 'A' * 64) bc_ser = bc.serialize() clone = BlockContender.from_bytes(bc_ser) for i in range(len(signatures)): self.assertEqual(bc.signatures[i], clone.signatures[i]) for i in range(len(nodes)): self.assertEqual(bc.merkle_leaves[i], clone.merkle_leaves[i])
def test_create_bc_normal_fields(self): msg = b'payload' nodes = ['A' * 64, 'B' * 64, 'C' * 64, 'D' * 64] sig1, sk1, vk1 = self._create_merkle_sig(msg) sig2, sk2, vk2 = self._create_merkle_sig(msg) sig3, sk3, vk3 = self._create_merkle_sig(msg) sig4, sk4, vk4 = self._create_merkle_sig(msg) signatures = [sig1, sig2, sig3, sig4] BlockContender.create(signatures, nodes, 'A' * 64) # should not throw an error
def test_eq(self): nodes = [secrets.token_bytes(8) for _ in range(4)] tree = MerkleTree.from_raw_transactions(nodes) msg = tree.root sig1, sk1, vk1 = self._create_merkle_sig(msg) sig2, sk2, vk2 = self._create_merkle_sig(msg) sig3, sk3, vk3 = self._create_merkle_sig(msg) sig4, sk4, vk4 = self._create_merkle_sig(msg) signatures = [sig1, sig2, sig3, sig4] bc1 = BlockContender.create(signatures, merkle_leaves=tree.leaves_as_hex, prev_block_hash='A' * 64) bc2 = BlockContender.create(signatures, merkle_leaves=tree.leaves_as_hex, prev_block_hash='A' * 64) self.assertEquals(bc1, bc2)
def test_bc_creation_from_bytes(self): msg = b'DEADBEEF' nodes = ['A' * 64, 'B' * 64] sig1, sk1, vk1 = self._create_merkle_sig(msg) sig2, sk2, vk2 = self._create_merkle_sig(msg) signatures = [sig1, sig2] bc = BlockContender.create(signatures, nodes, 'A' * 64) clone = BlockContender.from_bytes(bc.serialize()) # assert bc.signatures = signature over all signatures for i in range(len(signatures)): self.assertTrue(clone.signatures[i] == (signatures[i])) for i in range(len(nodes)): self.assertTrue(clone.merkle_leaves[i] == (nodes[i]))
def create(cls, hash: str, merkle_root: str, merkle_leaves: str, prev_block_hash: str, timestamp: int, masternode_signature: str, masternode_vk: str, block_contender: BlockContender): struct = blockdata_capnp.BlockMetaData.new_message() struct.hash = hash struct.merkleRoot = merkle_root struct.merkleLeaves = merkle_leaves struct.prevBlockHash = prev_block_hash struct.timestamp = timestamp struct.masternodeSignature = masternode_signature struct.masternodeVk = masternode_vk assert type(block_contender) == BlockContender, 'Not a block contender' struct.blockContender = block_contender.serialize() return cls.from_data(struct)
def check_majority(self): self.log.debug("delegate has {} signatures out of {} total TESTNET_DELEGATES" .format(len(self.signatures), self.NUM_DELEGATES)) if len(self.signatures) >= MAJORITY and not self.in_consensus: self.log.important("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.leaves_as_hex, prev_block_hash=self.parent.current_hash) for mn_vk in VKBook.get_masternodes(): self.log.debug("Delegate sending block contender to masternode with VK {}".format(mn_vk)) self.parent.composer.send_request_msg(message=bc, vk=mn_vk)
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) >= majority: self.log.important("Delegate in consensus!") self.in_consensus = True # DEBUG LINE -- todo remove later # self.log.notice("Delegate creating contender with merk leaves {}".format(self.merkle.leaves_as_hex)) # Create BlockContender and send it to all Masternode(s) bc = BlockContender.create(signatures=self.signatures, merkle_leaves=self.merkle.leaves_as_hex) for mn_vk in VKBook.get_masternodes(): self.parent.composer.send_request_msg(message=bc, vk=mn_vk)
def test_validate_signatures_invalid(self): nodes = [secrets.token_bytes(8) for _ in range(4)] tree = MerkleTree.from_raw_transactions(nodes) msg = tree.root bad_msg = b'lol this is def not a merkle root' sig1, sk1, vk1 = self._create_merkle_sig(msg) sig2, sk2, vk2 = self._create_merkle_sig(msg) sig3, sk3, vk3 = self._create_merkle_sig(msg) sig4, sk4, vk4 = self._create_merkle_sig(bad_msg) signatures = [sig1, sig2, sig3, sig4] bc = BlockContender.create(signatures, merkle_leaves=tree.leaves_as_hex, prev_block_hash="A" * 64) is_valid = bc.validate_signatures() self.assertFalse(is_valid)
def test_bc_creation(self): """ Tests that a created BlockContender has the expected properties """ msg = b'DEADBEEF' nodes = ['A' * 64, 'B' * 64] sig1, sk1, vk1 = self._create_merkle_sig(msg) sig2, sk2, vk2 = self._create_merkle_sig(msg) signatures = [sig1, sig2] prev_b_hash = 'A' * 64 bc = BlockContender.create(signatures, nodes, prev_b_hash) # assert bc.signatures = signature over all signatures for i in range(len(signatures)): self.assertTrue(bc.signatures[i] == (signatures[i])) for i in range(len(nodes)): self.assertTrue(bc.merkle_leaves[i] == (nodes[i])) self.assertEquals(bc.prev_block_hash, prev_b_hash)
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 :param block_contender: The BlockContender to validate :return: True if the BlockContender is valid, false otherwise """ # Development sanity checks (these should be removed in production) 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) == max_queue_size, \ "Block contender has {} merkle leaves, but MaxQueueSize is {}!!!\nmerkle_leaves={}"\ .format(len(block.merkle_leaves), max_queue_size, block.merkle_leaves) # TODO validate the sigs are actually from the top N delegates # TODO -- ensure that this block contender's previous block is this Masternode's current block... return block.validate_signatures()
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()
def block_contender(self) -> BlockContender: return BlockContender.from_bytes(self._data.blockContender)
def _deserialize_contender(block_contender: str) -> BlockContender: # Genesis Block Contender is None/Empty and thus should not be deserialized if block_contender == GENESIS_BLOCK_CONTENDER: return block_contender return BlockContender.from_bytes(bytes.fromhex(block_contender))
def _serialize_contender(block_contender: BlockContender) -> str: hex_str = block_contender.serialize().hex() return hex_str