def test_valid_merkle_one_leaf(self): tx1 = Transaction([], [TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1)]) transactions = [tx1] block = TestBlock(0, transactions, "genesis", is_genesis=True) expected_value = sha256_2_string(str(tx1)) self.assertEqual(block.calculate_merkle_root(), expected_value)
def test_input_txs_in_block(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) # create chain of transactions; tx2 spends tx1, tx3 spends tx2 tx2 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", .9), TransactionOutput("Alice", "Carol", 0) ]) tx3 = Transaction([tx2.hash + ":0"], [TransactionOutput("Bob", "Bob", .8)]) block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) block2 = TestBlock(1, [tx2], block.hash) self.assertTrue(block2.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block2)) block3 = TestBlock(1, [tx3], block.hash) self.assertEqual(block3.is_valid(), (False, "Input transaction not found")) block3 = TestBlock(1, [tx2, tx3], block.hash) self.assertTrue(block3.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block3))
def test_rejects_invalid_hash(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", .4), TransactionOutput("Alice", "Carol", .4) ]) # test an invalid hash on genesis block is rejected, and valid hash is accepted block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.test_chain.add_block(block) old_hash = block.hash block.hash = "fff" self.assertEqual(block.is_valid(), (False, "Hash failed to match")) block.hash = old_hash # test an invalid hash on non-genesis block is rejected, and valid hash is accepted block2 = TestBlock(1, [tx2], block.hash) self.assertTrue(block2.is_valid()[0]) old_hash = block2.hash block2.hash = "fff" self.assertEqual(block2.is_valid(), (False, "Hash failed to match")) block2.hash = old_hash self.assertTrue(block2.is_valid()[0])
def test_input_txs_on_chain(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) # next two transactions spend same input twice (double spend) tx2 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", .9), TransactionOutput("Alice", "Carol", 0) ]) tx3 = Transaction([tx2.hash + ":0"], [TransactionOutput("Bob", "Bob", .8)]) block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) block2 = TestBlock(1, [tx2], block.hash) self.assertTrue(block2.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block2)) block3 = TestBlock(1, [tx3], block.hash) self.assertEqual(block3.is_valid(), (False, "Input transaction not found")) block3 = TestBlock(2, [tx3], block2.hash) self.assertTrue(block3.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block3))
def test_valid_merkle_three_leafs(self): tx1 = Transaction([], [TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1)]) tx2 = Transaction([tx1.hash + ":1"], [TransactionOutput("Alice", "Bob", .9), TransactionOutput("Alice", "Carol", 0)]) tx3 = Transaction([tx2.hash + ":0"], [TransactionOutput("Bob", "Bob", .8)]) transactions = [tx1, tx2, tx3] block = TestBlock(0, transactions, "genesis", is_genesis=True) expected_value = sha256_2_string(sha256_2_string(str(tx1)) + sha256_2_string(str(tx2))) expected_value = sha256_2_string(expected_value + sha256_2_string(str(tx3))) self.assertEqual(block.calculate_merkle_root(), expected_value)
def test_blockchain_hash(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2 = Transaction([tx1.hash + ":0"], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Carol", 1) ]) block = TestBlock(0, [tx1, tx2], "genesis", is_genesis=True) block.set_dummy_vals() self.assertEqual( sha256_2_string(block.header()), "b0d35a2ffb46c6a8f48658d77f990656f7a9ec753b77342eb3b0e6a1d7acf934")
def test_merkle_root(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 3) ]) tx2 = Transaction([tx1.hash + ":0"], [ TransactionOutput("Alice", "Bob", 2), TransactionOutput("Alice", "Carol", 1) ]) tx3 = Transaction([tx2.hash + ":0"], [ TransactionOutput("Bob", "Carol", 1), TransactionOutput("Bob", "Bob", 1) ]) tx4 = Transaction([tx3.hash + ":0"], [ TransactionOutput("Carol", "Alice", 0.5), TransactionOutput("Carol", "Carol", 0.5) ]) block1 = TestBlock(0, [tx1], "genesis", is_genesis=True) block2 = TestBlock(0, [tx1, tx2], "genesis", is_genesis=True) block3 = TestBlock(0, [tx1, tx2, tx3, tx4], "genesis", is_genesis=True) for block in [block1, block2, block3]: block.set_dummy_timestamp() self.assertEqual( block1.merkle, "70dd3a969ee311d9749d8f1c2f03ab1dc4930ca0be58d231a73dfeeb85f5b68d") self.assertEqual( block2.merkle, "e0559c662f64c8fd1b638384ecbb1104335445a07114fd7d197316402e2f6f4c") self.assertEqual( block3.merkle, "039eceb401b485400f19bca99158b9dd2fcd13e9e4f287fde16f81fa58074a51")
def test_blockchain_hash(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2 = Transaction([tx1.hash + ":0"], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Carol", 1) ]) block = TestBlock(0, [tx1, tx2], "genesis", is_genesis=True) block.set_dummy_timestamp() self.assertEqual( sha256_2_string(block.header()), "9fc4ae4f2e6a68a0e79a57c4491b03a72f9a4bcdbc6ab7213e0f9334d800c57d")
def test_double_tx_inclusion_same_block(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", .4), TransactionOutput("Alice", "Carol", .4) ]) block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) block2 = TestBlock(1, [tx2, tx2], block.hash) self.assertEqual(block2.is_valid(), (False, "Double transaction inclusion"))
def test_doublespent_input_same_chain(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) # next two transactions spend same input twice (double spend) tx2 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", 0), TransactionOutput("Alice", "Carol", 0) ]) tx3 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Carol", 0), TransactionOutput("Alice", "Carol", 0) ]) block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) block2 = TestBlock(1, [tx2], block.hash) self.assertTrue(block2.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block2)) block3 = TestBlock(2, [tx3], block2.hash) self.assertEqual(block3.is_valid(), (False, "Double-spent input")) block3 = TestBlock(1, [tx3], block.hash) self.assertTrue( block3.is_valid()[0]) # doublespend should be allowed across forks
def test_failed_input_lookup(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2 = Transaction([tx1.hash + ":2"], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Carol", 1) ]) # good tx id, bad input location tx3 = Transaction(["fakehash" + ":2"], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Carol", 1) ]) # bad tx id block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) block2 = TestBlock(1, [tx2], block.hash) self.assertEqual(block2.is_valid(), (False, "Required output not found")) block2 = TestBlock(1, [tx3], block.hash) self.assertEqual(block2.is_valid(), (False, "Required output not found"))
def test_rejects_too_many_txs(self): txs = [] for i in range(901): txs.append( Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ])) # test too many txs in genesis block = TestBlock(0, txs, "genesis", is_genesis=True) self.assertEqual(block.is_valid(), (False, "Too many transactions")) # 900 txs should be fine block = TestBlock(0, txs[1:], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) # test too many txs outside genesis block = TestBlock(1, txs, block.hash) self.assertEqual(block.is_valid(), (False, "Too many transactions"))
def test_user_consistency(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2 = Transaction( [tx1.hash + ":1"], [ TransactionOutput("Carol", "Bob", 0), TransactionOutput("Carol", "Carol", 0) ] ) # outputs created from wrong user (different than one that received inputs) tx3 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", 0), TransactionOutput("Carol", "Carol", 0) ]) # two outputs from different users tx4 = Transaction([tx1.hash + ":0", tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", 0), TransactionOutput("Alice", "Carol", 0) ]) # two inputs to different users tx5 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", 0), TransactionOutput("Alice", "Carol", 0) ]) # this one is valid block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) block2 = TestBlock(1, [tx2], block.hash) self.assertEqual(block2.is_valid(), (False, "User inconsistencies")) block2 = TestBlock(1, [tx3], block.hash) self.assertEqual(block2.is_valid(), (False, "User inconsistencies")) block2 = TestBlock(1, [tx4], block.hash) self.assertEqual(block2.is_valid(), (False, "User inconsistencies")) block2 = TestBlock(1, [tx5], block.hash) self.assertTrue(block2.is_valid()[0])
def test_failed_input_lookup(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2 = Transaction([tx1.hash + ":2"], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Carol", 1) ]) # good tx id, bad input location tx3 = Transaction(["fakehash" + ":2"], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Carol", 1) ]) # bad tx id # good txs tx4 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", .2), TransactionOutput("Alice", "Carol", .2) ]) tx5 = Transaction([tx4.hash + ":0"], [TransactionOutput("Bob", "Bob", 0)]) block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) block2 = TestBlock(1, [tx2], block.hash) self.assertEqual(block2.is_valid(), (False, "Required output not found")) block2 = TestBlock(1, [tx3], block.hash) self.assertEqual(block2.is_valid(), (False, "Required output not found")) block2 = TestBlock( 1, [tx4, tx5], block.hash) # tx exists, but is in same block; this should work self.assertTrue(block2.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block2))
def string_to_output(outputstring): """ Takes a string as input and deserializes it into a transaction object for receipt over network. !!! WARNING !!! (don't ever do anything like this in production, it's not secure). Args: outputstring (str): Transaction output represented as a string. Returns: :obj:`TransactionOutput`: Parsed transaction output, False or exception thrown on failure. """ output_parts = outputstring.split("~") if len(output_parts) != 3: return False return TransactionOutput(output_parts[0], output_parts[1], int(output_parts[2]))
def test_no_money_creation(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", 3), TransactionOutput("Alice", "Carol", 0) ]) # single output creates money tx3 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", .9), TransactionOutput("Alice", "Carol", .9) ]) # sum of outputs creates money block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) block2 = TestBlock(1, [tx2], block.hash) self.assertEqual(block2.is_valid(), (False, "Creating money")) block2 = TestBlock(1, [tx3], block.hash) self.assertEqual(block2.is_valid(), (False, "Creating money"))
def test_malformed_txs(self): tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2_evil = BadTX([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", .4), TransactionOutput("Alice", "Carol", .4) ]) tx2 = Transaction([tx1.hash + ":1"], [ TransactionOutput("Alice", "Bob", .4), TransactionOutput("Alice", "Carol", .4) ]) block = TestBlock(0, [tx1], "genesis", is_genesis=True) self.assertTrue(block.is_valid()[0]) self.assertTrue(self.test_chain.add_block(block)) block2 = TestBlock(1, [tx2_evil], block.hash) self.assertEqual(block2.is_valid(), (False, "Malformed transaction included")) block2 = TestBlock(1, [tx2], block.hash) self.assertTrue(block2.is_valid()[0])
from p2p import gossip USERS = ["Alice", "Bob", "Charlie", "Dave", "Errol", "Frank"] HEIGHT_TO_REACH = 100 MAX_TXS_PER_BLOCK = 50 FORK_PROBABILITY = .3 # basic wallet functionality; track UTXOs for users user_utxos = {} for user in USERS: user_utxos[user] = [] # insert genesis block; populate all users w huge balance outputs = [] for user in USERS: genesis_utxo = TransactionOutput("Genesis", user, 100000000) outputs.append(genesis_utxo) genesis_tx = Transaction([], outputs) for user_num in range(len(USERS)): user = USERS[user_num] user_utxos[user].append((genesis_tx.hash + ":" + str(user_num), 100000000)) genesis_block = PoWBlock(0, [genesis_tx], "genesis", is_genesis=True) chaindb.chain.add_block(genesis_block) gossip.gossip_message("addblock", genesis_block) curr_height = 1 parent = genesis_block while curr_height <= HEIGHT_TO_REACH: chain = chaindb.chain
import blockchain from blockchain.transaction import Transaction, TransactionOutput from blockchain.pow_block import PoWBlock import transaction # create some transactions tx1 = Transaction([], [ TransactionOutput("Alice", "Bob", 1), TransactionOutput("Alice", "Alice", 1) ]) tx2 = Transaction([tx1.hash + ":0"], [ TransactionOutput("Alice", "Bob", .4), TransactionOutput("Alice", "Carol", .4) ]) # create an unsealed block block = PoWBlock(0, [tx1, tx2], "genesis", is_genesis=True) # run the mining loop until a valid PoW seal is created (final hash should have 2 leading 0s) block.mine() # add the block to the blockchain assert (blockchain.chain.add_block(block)) # display the block print(block.header()) print(block.hash)