def test_store_block_inserts_transactions(self): num_txs = 4 with DB() as db: initial_txs = len(db.tables.transactions.select().run(db.ex)) mn_sk = Constants.Testnet.Masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions = [build_test_transaction().serialize() for _ in range(num_txs)] tree = MerkleTree(raw_transactions) bc = build_test_contender(tree=tree) BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_transactions, publisher_sk=mn_sk, timestamp=timestamp) block_hash = BlockStorageDriver._get_latest_block_hash() with DB() as db: transactions = db.tables.transactions all_tx_query = transactions.select().run(db.ex) # Ensure the correct number of transactions was inserted self.assertEquals(len(all_tx_query) - initial_txs, num_txs) # Ensure the transactions were correctly inserted for raw_tx in raw_transactions: tx_hash = Hasher.hash(raw_tx) rows = transactions.select().where(transactions.hash == tx_hash).run(db.ex) self.assertTrue(rows, "Expected there to be a row for inserted tx {}".format(raw_tx)) tx_row = rows[0] self.assertEquals(tx_row['hash'], tx_hash, "Expected fetched tx to have hash equal to its hashed data") self.assertEquals(tx_row['data'], encode_tx(raw_tx), "Expected tx data col to equal encoded raw tx") self.assertEquals(tx_row['block_hash'], block_hash, "Expected inserted tx to reference last block")
def test_get_child_block_hashes(self): mn_sk = masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions1 = [ build_test_transaction().serialize() for _ in range(4) ] raw_transactions2 = [ build_test_transaction().serialize() for _ in range(4) ] new_hashes = [] starting_hash = BlockStorageDriver.get_latest_block_hash() # Store 2 blocks for raw_txs in (raw_transactions1, raw_transactions2): tree = MerkleTree(raw_txs) bc = build_test_contender(tree=tree) h = BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_txs, publisher_sk=mn_sk, timestamp=timestamp) new_hashes.append(h) actual_new_hashes = BlockStorageDriver.get_child_block_hashes( starting_hash) self.assertEquals(actual_new_hashes, new_hashes)
def test_validate_blockchain_invalid(self): reset_db() # Stuff a bunch of valid blocks in for _ in range(4): mn_sk = masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions = [ build_test_transaction().serialize() for _ in range(19) ] tree = MerkleTree(raw_transactions) bc = build_test_contender(tree=tree) BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_transactions, publisher_sk=mn_sk, timestamp=timestamp) # Stuff a sketch block in that doesn't link to the last sketch_block = self._build_block_data( ) # by default this has prev_block_hash = 'AAAAA...' sketch_block['hash'] = BlockStorageDriver.compute_block_hash( sketch_block) with DB() as db: db.tables.blocks.insert( [BlockStorageDriver._encode_block(sketch_block)]).run(db.ex) self.assertRaises(InvalidBlockLinkException, BlockStorageDriver.validate_blockchain)
def test_get_raw_transaction_from_multiple_blocks(self): mn_sk = masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions1 = [ build_test_transaction().serialize() for _ in range(4) ] raw_transactions2 = [ build_test_transaction().serialize() for _ in range(4) ] block_hashes = [] # Store 2 blocks for raw_txs in (raw_transactions1, raw_transactions2): tree = MerkleTree(raw_txs) bc = build_test_contender(tree=tree) h = BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_txs, publisher_sk=mn_sk, timestamp=timestamp) block_hashes.append(h) added_txs = BlockStorageDriver.get_raw_transactions_from_block( block_hashes) for tx in raw_transactions1 + raw_transactions2: self.assertTrue(tx in added_txs)
def _build_valid_block_data(self, num_transactions=4) -> dict: """ Utility method to build a dictionary with all the params needed to invoke store_block :param num_transactions: :return: """ mn_sk = Constants.Testnet.Masternodes[0]['sk'] mn_vk = ED25519Wallet.get_vk(mn_sk) timestamp = 9000 raw_transactions = [build_test_transaction().serialize() for _ in range(num_transactions)] tree = MerkleTree(raw_transactions) merkle_leaves = tree.leaves_as_concat_hex_str merkle_root = tree.root_as_hex bc = build_test_contender(tree=tree) prev_block_hash = '0' * 64 mn_sig = ED25519Wallet.sign(mn_sk, tree.root) return { 'prev_block_hash': prev_block_hash, 'block_contender': bc, 'merkle_leaves': merkle_leaves, 'merkle_root': merkle_root, 'masternode_signature': mn_sig, 'masternode_vk': mn_vk, 'timestamp': timestamp }
def test_store_block_data_invalid_sk(self): raw_txs = [secrets.token_bytes(16) for _ in range(16)] tree = MerkleTree(leaves=raw_txs) bc = build_test_contender(tree=tree) bad_sk = 'X' * 128 timestamp = 9000 self.assertRaises(AssertionError, BlockStorageDriver.store_block, block_contender=bc, raw_transactions=raw_txs, publisher_sk=bad_sk, timestamp=timestamp)
def test_validate_block_data_invalid_contender_leaves_mismatch(self): bd = self._build_valid_block_data(4) # Create a sketch block contender with leaves that are different from the merkle leaves in the block_data raw_transactions = [build_test_transaction().serialize() for _ in range(4)] tree = MerkleTree(raw_transactions) sketch_bc = build_test_contender(tree=tree) bd['block_contender'] = sketch_bc self.assertRaises(InvalidBlockContenderException, BlockStorageDriver._validate_block_data, bd)
def test_input_block_contender(self): """ Tests that receiving a block contender in RunState pushes the SM into NewBlockState """ mock_sm = MagicMock() bc = build_test_contender() state = MNRunState(state_machine=mock_sm) state.call_input_handler(bc, StateInput.REQUEST) mock_sm.transition.assert_called_with('MNNewBlockState', block=bc)
def test_store_block_contender_raw_tx_mismatch(self): mn_sk = Constants.Testnet.Masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions = [build_test_transaction().serialize() for _ in range(8)] tree = MerkleTree(raw_transactions) bc = build_test_contender(tree=tree) # Generate some arbitrary raw transactions that are not the same as the ones signed inside the BlockContender mismatched_transactions = [build_test_transaction().serialize() for _ in range(8)] self.assertRaises(InvalidBlockContenderException, BlockStorageDriver.store_block, block_contender=bc, raw_transactions=mismatched_transactions, publisher_sk=mn_sk, timestamp=timestamp)
def test_get_raw_transaction(self): mn_sk = Constants.Testnet.Masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions = [build_test_transaction().serialize() for _ in range(4)] tree = MerkleTree(raw_transactions) bc = build_test_contender(tree=tree) BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_transactions, publisher_sk=mn_sk, timestamp=timestamp) # Ensure all these transactions are retrievable for raw_tx in raw_transactions: retrieved_tx = BlockStorageDriver.get_raw_transaction(Hasher.hash(raw_tx)) self.assertEquals(raw_tx, retrieved_tx)
def test_get_raw_transaction_from_block(self): mn_sk = Constants.Testnet.Masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions = [build_test_transaction().serialize() for _ in range(4)] tree = MerkleTree(raw_transactions) bc = build_test_contender(tree=tree) BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_transactions, publisher_sk=mn_sk, timestamp=timestamp) latest_hash = BlockStorageDriver._get_latest_block_hash() added_txs = BlockStorageDriver.get_raw_transactions_from_block(block_hash=latest_hash) for tx in raw_transactions: self.assertTrue(tx in added_txs)
def test_get_latest_inserted(self): mn_sk = Constants.Testnet.Masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions = [build_test_transaction().serialize() for _ in range(19)] tree = MerkleTree(raw_transactions) bc = build_test_contender(tree=tree) BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_transactions, publisher_sk=mn_sk, timestamp=timestamp) latest = BlockStorageDriver.get_latest_block() self.assertTrue(latest) self.assertEquals(latest['timestamp'], timestamp) self.assertEquals(latest['block_contender'], bc)
def test_get_raw_transactions_with_multiple_hashes(self): mn_sk = TESTNET_MASTERNODES[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions = [build_test_transaction().serialize() for _ in range(4)] hashes = list(map(Hasher.hash, raw_transactions)) tree = MerkleTree(raw_transactions) bc = build_test_contender(tree=tree) BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_transactions, publisher_sk=mn_sk, timestamp=timestamp) # Ensure all these transactions are retrievable retrieved_txs = BlockStorageDriver.get_raw_transactions(hashes) self.assertEquals(len(retrieved_txs), len(raw_transactions)) for raw_tx in raw_transactions: self.assertTrue(raw_tx in retrieved_txs)
def test_validate_blockchain(self): reset_db() # Stuff a bunch of blocks in for _ in range(4): mn_sk = Constants.Testnet.Masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions = [build_test_transaction().serialize() for _ in range(19)] tree = MerkleTree(raw_transactions) bc = build_test_contender(tree=tree) BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_transactions, publisher_sk=mn_sk, timestamp=timestamp) # This should not blow up BlockStorageDriver.validate_blockchain()
def test_store_block_inserts(self): with DB() as db: initial_num_blocks = len(db.tables.blocks.select().run(db.ex)) mn_sk = Constants.Testnet.Masternodes[0]['sk'] timestamp = random.randint(0, pow(2, 32)) raw_transactions = [build_test_transaction().serialize() for _ in range(19)] tree = MerkleTree(raw_transactions) bc = build_test_contender(tree=tree) BlockStorageDriver.store_block(block_contender=bc, raw_transactions=raw_transactions, publisher_sk=mn_sk, timestamp=timestamp) with DB() as db: blocks = db.tables.blocks.select().run(db.ex) self.assertEquals(len(blocks) - initial_num_blocks, 1) self.assertEquals(blocks[-1]['timestamp'], timestamp)
def _build_block_data(self, num_transactions=4, ref_prev_block=False) -> dict: """ Utility method to build a dictionary with all the params needed to invoke store_block :param num_transactions: Number of raw transactions in the block :param ref_prev_block: True if the block data's prev_block_hash should reference the previous block. Otherwise, prev_block_hash is set to default 000000... :return: """ mn_sk = masternodes[0]['sk'] mn_vk = wallet.get_vk(mn_sk) timestamp = 9000 raw_transactions = [ build_test_transaction().serialize() for _ in range(num_transactions) ] tree = MerkleTree(raw_transactions) merkle_leaves = tree.leaves_as_concat_hex_str merkle_root = tree.root_as_hex bc = build_test_contender(tree=tree) if ref_prev_block: prev_block_hash = BlockStorageDriver.get_latest_block_hash() else: prev_block_hash = '0' * 64 mn_sig = wallet.sign(mn_sk, tree.root) return { 'prev_block_hash': prev_block_hash, 'block_contender': bc, 'merkle_leaves': merkle_leaves, 'merkle_root': merkle_root, 'masternode_signature': mn_sig, 'masternode_vk': mn_vk, 'timestamp': timestamp }
def test_build_test_contender(self): """ Tests build_test_contender. This is used exclusively unit tests, so we basically just want to make sure it doesn't blow up here first. """ bc = build_test_contender()