def test_ancestry(self): dag = Dag(0) private = Private.generate() block1 = BlockFactory.create_block_with_timestamp( [dag.genesis_block().get_hash()], BLOCK_TIME) signed_block1 = BlockFactory.sign_block(block1, private) dag.add_signed_block(1, signed_block1) block2 = BlockFactory.create_block_with_timestamp([block1.get_hash()], BLOCK_TIME * 2) signed_block2 = BlockFactory.sign_block(block2, private) dag.add_signed_block(2, signed_block2) block3 = BlockFactory.create_block_with_timestamp([block2.get_hash()], BLOCK_TIME * 3) signed_block3 = BlockFactory.sign_block(block3, private) dag.add_signed_block(3, signed_block3) # alternative chain other_block2 = BlockFactory.create_block_with_timestamp( [block1.get_hash()], BLOCK_TIME * 2 + 1) other_signed_block2 = BlockFactory.sign_block(other_block2, private) dag.add_signed_block(2, other_signed_block2) # alternative chain other_block3 = BlockFactory.create_block_with_timestamp( [other_block2.get_hash()], BLOCK_TIME * 3 + 1) other_signed_block3 = BlockFactory.sign_block(other_block3, private) dag.add_signed_block(3, other_signed_block3) self.assertEqual( dag.is_ancestor(other_block3.get_hash(), other_block2.get_hash()), True) self.assertEqual( dag.is_ancestor(other_block3.get_hash(), block2.get_hash()), False)
def insert_dummy(dag, prev_hashes, position): dummy_private = Private.generate() dummy_time_offset = len(dag.blocks_by_number.get(position, [])) assert dummy_time_offset <= BLOCK_TIME, "This much blocks in one timeslot may lead to problems" block = BlockFactory.create_block_with_timestamp( prev_hashes, BLOCK_TIME * position + dummy_time_offset) signed_block = BlockFactory.sign_block(block, dummy_private) dag.add_signed_block(position, signed_block) return block.get_hash()
def insert_dummy_with_payments(dag, prev_hashes, payments, position): dummy_private = Private.generate() dummy_time_offset = len(dag.blocks_by_number.get(position, [])) assert dummy_time_offset <= BLOCK_TIME, "This much blocks in one timeslot may lead to problems" block = BlockFactory.create_block_with_timestamp( prev_hashes, BLOCK_TIME * position + dummy_time_offset) block_reward = TransactionFactory.create_block_reward( urandom(32), position) block.payment_txs = [block_reward] + payments signed_block = BlockFactory.sign_block(block, dummy_private) dag.add_signed_block(position, signed_block) return block.get_hash(), block_reward.get_hash()
def test_chain_length(self): dag = Dag(0) private = Private.generate() block1 = BlockFactory.create_block_with_timestamp( [dag.genesis_block().get_hash()], BLOCK_TIME) signed_block1 = BlockFactory.sign_block(block1, private) dag.add_signed_block(1, signed_block1) block2 = BlockFactory.create_block_with_timestamp([block1.get_hash()], BLOCK_TIME * 2) signed_block2 = BlockFactory.sign_block(block2, private) dag.add_signed_block(2, signed_block2) block3 = BlockFactory.create_block_with_timestamp([block2.get_hash()], BLOCK_TIME * 3) signed_block3 = BlockFactory.sign_block(block3, private) dag.add_signed_block(3, signed_block3) # alternative chain other_block2 = BlockFactory.create_block_with_timestamp( [block1.get_hash()], BLOCK_TIME * 2 + 1) other_signed_block2 = BlockFactory.sign_block(other_block2, private) dag.add_signed_block(2, other_signed_block2) self.assertEqual( dag.calculate_chain_length(other_block2.get_hash(), dag.genesis_hash()), 3) self.assertEqual( dag.calculate_chain_length(block3.get_hash(), dag.genesis_hash()), 4)
def test_find_epoch_hash_for_block(self): dag = Dag(0) epoch = Epoch(dag) genesis_hash = dag.genesis_block().get_hash() genesis_epoch_hash = epoch.find_epoch_hash_for_block(genesis_hash) self.assertEqual(genesis_hash, genesis_epoch_hash) block = BlockFactory.create_block_with_timestamp([genesis_hash], BLOCK_TIME) signed_block = BlockFactory.sign_block(block, Private.generate()) dag.add_signed_block(1, signed_block) first_block_hash = block.get_hash() first_epoch_hash = epoch.find_epoch_hash_for_block(first_block_hash) self.assertEqual(genesis_hash, first_epoch_hash)
def test_merge_in_merge(self): dag = Dag(0) genesis_hash = dag.genesis_block().get_hash() ChainGenerator.fill_with_dummies_and_skips(dag, genesis_hash, range(1, 5), [1, 3]) second_block = dag.blocks_by_number[2][0].get_hash() ChainGenerator.fill_with_dummies_and_skips(dag, second_block, range(3, 4), []) tops = dag.get_top_hashes() merging_block = BlockFactory.create_block_with_timestamp( tops, BLOCK_TIME * 5) merging_signed_block = BlockFactory.sign_block(merging_block, Private.generate()) dag.add_signed_block(5, merging_signed_block) ChainGenerator.fill_with_dummies_and_skips(dag, genesis_hash, range(1, 7), [2, 3, 4, 5]) tops = dag.get_top_hashes() merging_block = BlockFactory.create_block_with_timestamp( tops, BLOCK_TIME * 7) merging_signed_block = BlockFactory.sign_block(merging_block, Private.generate()) dag.add_signed_block(7, merging_signed_block) # DagVisualizer.visualize(dag, True) iterator = MergingIter(dag, merging_block.get_hash()) #shortest chain goes last # 3 and 4 are swapped because 4 has a priority #TODO But is it okay? Maybe we should sometimes give priority to earlier blocks if equal?1 self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[7][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[6][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[1][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[5][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[3][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[4][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[2][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[0][0].get_hash())
def test_simple_merge(self): dag = Dag(0) genesis_hash = dag.genesis_block().get_hash() ChainGenerator.fill_with_dummies_and_skips(dag, genesis_hash, range(1, 10), [2, 5, 7, 8]) first_block = dag.blocks_by_number[1][0].get_hash() ChainGenerator.fill_with_dummies_and_skips(dag, first_block, range(2, 10), [3, 4, 6, 7, 8, 9]) second_block = dag.blocks_by_number[2][0].get_hash() ChainGenerator.fill_with_dummies_and_skips(dag, second_block, range(3, 10), [3, 4, 5, 6, 9]) hanging_tips = dag.get_top_hashes() merging_block = BlockFactory.create_block_with_timestamp( hanging_tips, BLOCK_TIME * 10) merging_signed_block = BlockFactory.sign_block(merging_block, Private.generate()) dag.add_signed_block(10, merging_signed_block) # DagVisualizer.visualize(dag, True) iterator = MergingIter(dag, merging_block.get_hash()) self.assertEqual(iterator.next().get_hash(), merging_block.get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[5][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[8][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[7][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[2] [0].get_hash()) #TODO find out why is this 2 here self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[9][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[6][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[4][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[3][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[1][0].get_hash()) self.assertEqual(iterator.next().get_hash(), dag.blocks_by_number[0][0].get_hash())
def test_parse_pack_gossip_positive(self): private = Private.generate() original = PositiveGossipTransaction() original.pubkey = Private.publickey(private) original.timestamp = Time.get_current_time() block = BlockFactory.create_block_with_timestamp( [], timestamp=original.timestamp) original.block_hash = BlockFactory.sign_block(block, private).get_hash() original.signature = Private.sign(original.get_hash(), private) raw = original.pack() restored = PositiveGossipTransaction() restored.parse(raw) self.assertEqual(original.get_hash(), restored.get_hash())
def test_iterator(self): dag = Dag(0) private = Private.generate() block1 = BlockFactory.create_block_with_timestamp( [dag.genesis_block().get_hash()], BLOCK_TIME) signed_block1 = BlockFactory.sign_block(block1, private) dag.add_signed_block(1, signed_block1) block2 = BlockFactory.create_block_with_timestamp([block1.get_hash()], BLOCK_TIME * 2) signed_block2 = BlockFactory.sign_block(block2, private) dag.add_signed_block(2, signed_block2) block3 = BlockFactory.create_block_with_timestamp([block2.get_hash()], BLOCK_TIME * 3) signed_block3 = BlockFactory.sign_block(block3, private) dag.add_signed_block(3, signed_block3) # alternative chain other_block2 = BlockFactory.create_block_with_timestamp( [block1.get_hash()], BLOCK_TIME * 2 + 1) other_signed_block2 = BlockFactory.sign_block(other_block2, private) dag.add_signed_block(2, other_signed_block2) # intentionally skipped block # alternative chain other_block4 = BlockFactory.create_block_with_timestamp( [other_block2.get_hash()], BLOCK_TIME * 3 + 1) other_signed_block4 = BlockFactory.sign_block(other_block4, private) dag.add_signed_block(4, other_signed_block4) chain_iter = ChainIter(dag, block3.get_hash()) self.assertEqual(chain_iter.next().block.get_hash(), block3.get_hash()) self.assertEqual(chain_iter.next().block.get_hash(), block2.get_hash()) self.assertEqual(chain_iter.next().block.get_hash(), block1.get_hash()) chain_iter = ChainIter(dag, other_block4.get_hash()) self.assertEqual(chain_iter.next().block.get_hash(), other_block4.get_hash()) self.assertEqual(chain_iter.next(), None) # detect intentionally skipped block self.assertEqual(chain_iter.next().block.get_hash(), other_block2.get_hash()) self.assertEqual(chain_iter.next().block.get_hash(), block1.get_hash())
def fill_with_dummies_and_skips(dag, prev_hash, range, indices_to_skip, dummy_private=Private.generate()): prev_hash_number = dag.get_block_number(prev_hash) assert range.start > prev_hash_number, "First block timeslot should be later than anchoring previous block" # dummy_private = Private.generate() for i in range: if i in indices_to_skip: continue dummy_time_offset = len(dag.blocks_by_number.get(i, [])) block = BlockFactory.create_block_with_timestamp( [prev_hash], BLOCK_TIME * i + dummy_time_offset) signed_block = BlockFactory.sign_block(block, dummy_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() return prev_hash
def test_penalty(self): dag = Dag(0) epoch = Epoch(dag) permissions = Permissions(epoch) node_private = Private.generate() initial_validators = Validators.read_genesis_validators_from_file() genesis_hash = dag.genesis_block().get_hash() last_block_number = Epoch.get_epoch_end_block_number(1) prev_hash = ChainGenerator.fill_with_dummies( dag, genesis_hash, range(1, last_block_number)) block = BlockFactory.create_block_with_timestamp( [prev_hash], BLOCK_TIME * last_block_number) tx = PenaltyTransaction() tx.conflicts = [prev_hash] tx.signature = Private.sign(tx.get_hash(), node_private) block.system_txs = [tx] signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(last_block_number, signed_block) initial_validators_order = permissions.get_signers_indexes( genesis_hash) # we substract two here: one because it is last but one block # and one, because epoch starts from 1 validator_index_to_penalize = initial_validators_order[ last_block_number - 2] resulting_validators = permissions.get_validators(block.get_hash()) self.assertNotEqual(len(initial_validators), len(resulting_validators)) initial_validators.pop(validator_index_to_penalize) init_pubkeys = list( map(lambda validator: validator.public_key, initial_validators)) result_pubkeys = list( map(lambda validator: validator.public_key, resulting_validators)) self.assertEqual(init_pubkeys, result_pubkeys)
def generate_two_chains(length): dag = Dag(0) private = Private.generate() prev_hash = dag.genesis_block().get_hash() for i in range(1, length + 1): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() prev_hash = dag.blocks_by_number[1][0].get_hash() for i in range(2, length + 1): # intentionally one block less if i == 4: continue # intentionally skipped block block = BlockFactory.create_block_with_timestamp( [prev_hash], BLOCK_TIME * i + 1) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() return dag
def test_stake_release_by_genesis_validator(self): # base initialization dag = Dag(0) epoch = Epoch(dag) permissions = Permissions(epoch) node_private = Private.generate() initial_validators = Validators.read_genesis_validators_from_file() genesis_hash = dag.genesis_block().get_hash() prev_hash = genesis_hash for i in range(1, 9): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() # get one of validators genesis_validator = initial_validators[9] # create stake release transaction for new stakeholder tx_release = StakeReleaseTransaction() tx_release.pubkey = Keys.to_bytes(genesis_validator.public_key) tx_release.signature = Private.sign(tx_release.get_hash(), node_private) # append signed stake release transaction block.system_txs.append(tx_release) # sign block by one of validators signed_block = BlockFactory.sign_block(block, node_private) # add signed block to DAG dag.add_signed_block(19, signed_block) resulting_validators = permissions.get_validators(block.get_hash()) pub_keys = [] for validator in resulting_validators: pub_keys.append(validator.public_key) self.assertNotIn(genesis_validator.public_key, pub_keys)
def test_top_blocks(self): dag = Dag(0) epoch = Epoch(dag) private = Private.generate() epoch_hash = dag.genesis_block().get_hash() self.assertEqual(dag.genesis_block().get_hash(), list(epoch.get_epoch_hashes().keys())[0]) self.assertEqual(dag.genesis_block().get_hash(), list(epoch.get_epoch_hashes().values())[0]) block1 = BlockFactory.create_block_with_timestamp( [dag.genesis_block().get_hash()], BLOCK_TIME) signed_block1 = BlockFactory.sign_block(block1, private) dag.add_signed_block(1, signed_block1) self.assertEqual(block1.get_hash(), list(epoch.get_epoch_hashes().keys())[0]) self.assertEqual(epoch_hash, list(epoch.get_epoch_hashes().values())[0]) prev_hash = block1.get_hash() epoch_length = ROUND_DURATION * 6 + 1 for i in range(2, epoch_length + 1): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() if epoch.is_new_epoch_upcoming(epoch_length + 1): epoch.accept_tops_as_epoch_hashes() top_block_hash = dag.blocks_by_number[epoch_length][0].get_hash() epoch_hash = dag.blocks_by_number[epoch_length][0].get_hash() self.assertEqual(top_block_hash, list(epoch.get_epoch_hashes().keys())[0]) self.assertEqual(epoch_hash, list(epoch.get_epoch_hashes().values())[0]) epoch2 = epoch_length * 2 + 1 for i in range(epoch_length + 1, epoch2): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() if epoch.is_new_epoch_upcoming(epoch2): epoch.accept_tops_as_epoch_hashes() top_block_hash = dag.blocks_by_number[epoch2 - 1][0].get_hash() epoch_hash = dag.blocks_by_number[epoch2 - 1][0].get_hash() self.assertEqual(top_block_hash, list(epoch.get_epoch_hashes().keys())[0]) self.assertEqual(epoch_hash, list(epoch.get_epoch_hashes().values())[0])
def test_private_keys_extraction(self): dag = Dag(0) epoch = Epoch(dag) node_private = Private.generate() prev_hash = dag.genesis_block().get_hash() round_start, round_end = Epoch.get_round_bounds(1, Round.PRIVATE) for i in range(1, round_start): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() generated_private_keys = [] for i in range(round_start, round_end): # intentionally skip last block of round generated_private = Private.generate() generated_private_keys.append(Keys.to_bytes(generated_private)) private_key_tx = PrivateKeyTransaction() private_key_tx.key = Keys.to_bytes(generated_private) block = Block() block.system_txs = [private_key_tx] block.prev_hashes = dag.get_top_blocks_hashes() block.timestamp = i * BLOCK_TIME signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() ChainGenerator.fill_with_dummies(dag, prev_hash, Epoch.get_round_range(1, Round.FINAL)) epoch_hash = dag.blocks_by_number[ROUND_DURATION * 6 + 1][0].get_hash() extracted_privates = epoch.get_private_keys_for_epoch(epoch_hash) for i in range(0, ROUND_DURATION - 1): self.assertEqual(extracted_privates[i], generated_private_keys[i])
def test_getting_tx_by_hash(self): dag = Dag(0) private = Private.generate() block1 = BlockFactory.create_block_with_timestamp( [dag.genesis_block().get_hash()], BLOCK_TIME) tx1 = TransactionFactory.create_negative_gossip_transaction(1, private) tx2 = TransactionFactory.create_positive_gossip_transaction( block1.get_hash(), private) tx3 = TransactionFactory.create_penalty_gossip_transaction( {tx1.get_hash(): tx2.get_hash()}, private) not_appended_tx = TransactionFactory.create_public_key_transaction( generated_private=Private.generate(), epoch_hash=sha256(b'epoch_hash').digest(), validator_index=1, node_private=private) block1.system_txs.append(tx1) block1.system_txs.append(tx2) block1.system_txs.append(tx3) signed_block1 = BlockFactory.sign_block(block1, private) dag.add_signed_block(1, signed_block1) self.assertTrue( set(dag.transactions_by_hash).issuperset({tx1.get_hash(): tx1})) self.assertTrue( set(dag.transactions_by_hash).issuperset({tx2.get_hash(): tx2})) self.assertTrue( set(dag.transactions_by_hash).issuperset({tx3.get_hash(): tx3})) self.assertFalse( set(dag.transactions_by_hash).issuperset( {not_appended_tx.get_hash(): not_appended_tx})) # test dag.tx_by_hash getter self.assertTrue(dag.get_tx_by_hash(tx1.get_hash()) == tx1) self.assertTrue(dag.get_tx_by_hash(tx2.get_hash()) == tx2) self.assertTrue(dag.get_tx_by_hash(tx3.get_hash()) == tx3)
def test_hold_stake(self): dag = Dag(0) epoch = Epoch(dag) permissions = Permissions(epoch) node_private = Private.generate() initial_validators = Validators.read_genesis_validators_from_file() genesis_hash = dag.genesis_block().get_hash() prev_hash = genesis_hash for i in range(1, 9): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * 9) tx = StakeHoldTransaction() tx.amount = 1000 node_new_private = Private.generate() tx.pubkey = Private.publickey(node_new_private) tx.signature = Private.sign(tx.get_hash(), node_new_private) block.system_txs.append(tx) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(9, signed_block) resulting_validators = permissions.get_validators(block.get_hash()) pub_keys = [] for validator in resulting_validators: pub_keys.append(validator.public_key) self.assertIn(Private.publickey(node_new_private), pub_keys)
def test_pack_parse_penalty_gossip_transaction(self): private = Private.generate() original = PenaltyGossipTransaction() original.timestamp = Time.get_current_time() block = BlockFactory.create_block_with_timestamp( [], timestamp=original.timestamp) gossip_positive_tx = PositiveGossipTransaction() gossip_positive_tx.pubkey = Private.publickey(private) gossip_positive_tx.timestamp = Time.get_current_time() gossip_positive_tx.block_hash = BlockFactory.sign_block( block, private).get_hash() gossip_positive_tx.signature = Private.sign(original.get_hash(), private) gossip_negative_tx = NegativeGossipTransaction() gossip_negative_tx.pubkey = Private.publickey(private) gossip_negative_tx.timestamp = Time.get_current_time() gossip_negative_tx.number_of_block = 47 gossip_negative_tx.signature = Private.sign(original.get_hash(), private) original.conflicts = [ gossip_positive_tx.get_hash(), gossip_negative_tx.get_hash() ] original.signature = Private.sign(original.get_hash(), private) original.block_hash = BlockFactory.sign_block(block, private).get_hash() raw = original.pack() restored = PenaltyGossipTransaction() restored.parse(raw) self.assertEqual(original.get_hash(), restored.get_hash())
def test_zeta_calculation(self): dag = Dag(0) private = Private.generate() prev_hash = dag.genesis_block().get_hash() for i in range(1, 3): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() #skip 3 blocks for i in range(6, 7): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() for i in range(10, 12): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() prev_hash = dag.blocks_by_number[1][0].get_hash() for i in range(2, 12): if i == 3: continue block = BlockFactory.create_block_with_timestamp( [prev_hash], BLOCK_TIME * i + 1) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() immutability = Immutability(dag) # zeta = immutability.calculate_zeta(dag.blocks_by_number[2][0].get_hash()) # self.assertEqual(zeta, -2) zeta = immutability.calculate_zeta( dag.blocks_by_number[6][1].get_hash()) self.assertEqual(zeta, 1)
def test_storing_tx_by_hash(self): dag = Dag(0) private0 = Private.generate() private1 = Private.generate() private2 = Private.generate() # add block 1 block1 = BlockFactory.create_block_with_timestamp( [dag.genesis_block().get_hash()], BLOCK_TIME) signed_block1 = BlockFactory.sign_block(block1, private0) dag.add_signed_block(1, signed_block1) # check transactions in dag.transactions_by_hash for empty self.assertTrue(len(dag.transactions_by_hash) == 0) # add block 2 block2 = BlockFactory.create_block_with_timestamp([block1.get_hash()], BLOCK_TIME) # add penalty gossip case by tx in block tx1 = TransactionFactory.create_negative_gossip_transaction( 1, private1) tx2 = TransactionFactory.create_positive_gossip_transaction( block2.get_hash(), private1) block2.system_txs.append(tx1) block2.system_txs.append(tx2) # -------------------------------------- signed_block2 = BlockFactory.sign_block(block2, private1) dag.add_signed_block(2, signed_block2) # check transactions in dag.transactions_by_hash self.assertTrue( set(dag.transactions_by_hash).issuperset({tx1.get_hash(): tx1})) self.assertTrue( set(dag.transactions_by_hash).issuperset({tx2.get_hash(): tx2})) block3 = BlockFactory.create_block_with_timestamp([block2.get_hash()], BLOCK_TIME) signed_block3 = BlockFactory.sign_block(block3, private2) dag.add_signed_block(3, signed_block3) # check transactions in dag.transactions_by_hash self.assertTrue( set(dag.transactions_by_hash).issuperset({tx1.get_hash(): tx1})) self.assertTrue( set(dag.transactions_by_hash).issuperset({tx2.get_hash(): tx2}))
def test_top_blocks(self): dag = Dag(0) private = Private.generate() block1 = BlockFactory.create_block_with_timestamp( [dag.genesis_block().get_hash()], BLOCK_TIME) signed_block1 = BlockFactory.sign_block(block1, private) dag.add_signed_block(1, signed_block1) block2 = BlockFactory.create_block_with_timestamp([block1.get_hash()], BLOCK_TIME * 2) signed_block2 = BlockFactory.sign_block(block2, private) dag.add_signed_block(2, signed_block2) block3 = BlockFactory.create_block_with_timestamp([block1.get_hash()], BLOCK_TIME * 3) signed_block3 = BlockFactory.sign_block(block3, private) dag.add_signed_block(3, signed_block3) top_hashes = dag.get_top_blocks_hashes() self.assertEqual(top_hashes[0], block2.get_hash()) self.assertEqual(top_hashes[1], block3.get_hash())
def test_remove_from_validators_by_penalty_gossip(self): # base initialization dag = Dag(0) epoch = Epoch(dag) permissions = Permissions(epoch) node_private = Private.generate() initial_validators = Validators.read_genesis_validators_from_file() genesis_hash = dag.genesis_block().get_hash() prev_hash = genesis_hash for i in range(1, 9): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() # get one of validators genesis_validator_private = Private.generate() genesis_validator_public = initial_validators[9].public_key # put to 10 block gossip+ AND gossip- by one node block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * 10) gossip_negative_tx = NegativeGossipTransaction() gossip_negative_tx.pubkey = genesis_validator_public gossip_negative_tx.timestamp = Time.get_current_time() gossip_negative_tx.number_of_block = 5 gossip_negative_tx.signature = Private.sign( gossip_negative_tx.get_hash(), genesis_validator_private) # create and add to block negative gossip block.system_txs.append(gossip_negative_tx) gossip_positive_tx = PositiveGossipTransaction() gossip_positive_tx.pubkey = genesis_validator_public gossip_positive_tx.timestamp = Time.get_current_time() gossip_positive_tx.block_hash = dag.blocks_by_number[5][0].get_hash() gossip_positive_tx.signature = Private.sign( gossip_positive_tx.get_hash(), genesis_validator_private) # create and add to block positive gossip for same number 5 block block.system_txs.append(gossip_positive_tx) signed_block = BlockFactory.sign_block(block, genesis_validator_private) dag.add_signed_block(10, signed_block) prev_hash = block.get_hash() # -------------------------------------------------- # put to 11 block penalty gossip block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * 11) penalty_gossip_tx = PenaltyGossipTransaction() penalty_gossip_tx.timestamp = Time.get_current_time() penalty_gossip_tx.conflicts = [ gossip_positive_tx.get_hash(), gossip_negative_tx.get_hash() ] # set genesis validator for sign penalty gossip penalty_gossip_tx.signature = Private.sign( penalty_gossip_tx.get_hash(), genesis_validator_private) block.system_txs.append(penalty_gossip_tx) signed_block = BlockFactory.sign_block(block, genesis_validator_private) dag.add_signed_block(11, signed_block) prev_hash = block.get_hash() # -------------------------------------------------- # verify that genesis node is steel in validators list current_epoch_hash = epoch.get_epoch_hashes() # for now we DO NOT NEED to recalculate validators (send genesis block hash) resulting_validators = permissions.get_validators( current_epoch_hash.get(prev_hash)) pub_keys = [] for validator in resulting_validators: pub_keys.append(validator.public_key) self.assertIn(genesis_validator_public, pub_keys) # produce epoch till end from chain.params import ROUND_DURATION for i in range(12, (ROUND_DURATION * 6 + 4)): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() # check for new epoch self.assertTrue(epoch.is_new_epoch_upcoming(i)) self.assertTrue(epoch.current_epoch == 2) # recalculate validators for last block hash resulting_validators = permissions.get_validators(prev_hash) pub_keys = [] for validator in resulting_validators: pub_keys.append(validator.public_key) self.assertNotIn(genesis_validator_public, pub_keys)
def test_secret_sharing_rounds(self): dag = Dag(0) epoch = Epoch(dag) dummy_private = Private.generate() signers = [] for i in range(0, ROUND_DURATION + 1): signers.append(Private.generate()) private_keys = [] block_number = 1 genesis_hash = dag.genesis_block().get_hash() prev_hash = genesis_hash signer_index = 0 for i in Epoch.get_round_range(1, Round.PUBLIC): private = Private.generate() private_keys.append(private) signer = signers[signer_index] pubkey_tx = PublicKeyTransaction() pubkey_tx.generated_pubkey = Private.publickey(private) pubkey_tx.pubkey_index = signer_index pubkey_tx.signature = Private.sign( pubkey_tx.get_signing_hash(genesis_hash), signer) block = Block() block.timestamp = i * BLOCK_TIME block.prev_hashes = [prev_hash] block.system_txs = [pubkey_tx] signed_block = BlockFactory.sign_block(block, signer) dag.add_signed_block(i, signed_block) signer_index += 1 prev_hash = block.get_hash() prev_hash = ChainGenerator.fill_with_dummies( dag, prev_hash, Epoch.get_round_range(1, Round.COMMIT)) public_keys = [] for private in private_keys: public_keys.append(Private.publickey(private)) randoms_list = [] expected_random_pieces = [] for i in Epoch.get_round_range(1, Round.SECRETSHARE): random_bytes = os.urandom(32) random_value = int.from_bytes(random_bytes, byteorder='big') split_random_tx = SplitRandomTransaction() splits = split_secret(random_bytes, 2, 3) encoded_splits = encode_splits(splits, public_keys) split_random_tx.pieces = encoded_splits split_random_tx.pubkey_index = 0 expected_random_pieces.append(split_random_tx.pieces) split_random_tx.signature = Private.sign(pubkey_tx.get_hash(), dummy_private) block = Block() block.timestamp = i * BLOCK_TIME block.prev_hashes = [prev_hash] block.system_txs = [split_random_tx] signed_block = BlockFactory.sign_block(block, dummy_private) dag.add_signed_block(i, signed_block) randoms_list.append(random_value) prev_hash = block.get_hash() expected_seed = sum_random(randoms_list) prev_hash = ChainGenerator.fill_with_dummies( dag, prev_hash, Epoch.get_round_range(1, Round.REVEAL)) signer_index = 0 private_key_index = 0 raw_private_keys = [] for i in Epoch.get_round_range(1, Round.PRIVATE): private_key_tx = PrivateKeyTransaction() private_key_tx.key = Keys.to_bytes(private_keys[private_key_index]) raw_private_keys.append(private_key_tx.key) signer = signers[signer_index] block = Block() block.system_txs = [private_key_tx] block.prev_hashes = [prev_hash] block.timestamp = block_number * BLOCK_TIME signed_block = BlockFactory.sign_block(block, signer) dag.add_signed_block(i, signed_block) signer_index += 1 private_key_index += 1 prev_hash = block.get_hash() prev_hash = ChainGenerator.fill_with_dummies( dag, prev_hash, Epoch.get_round_range(1, Round.FINAL)) top_block_hash = dag.get_top_blocks_hashes()[0] random_splits = epoch.get_random_splits_for_epoch(top_block_hash) self.assertEqual(expected_random_pieces, random_splits) restored_randoms = [] for i in range(0, len(random_splits)): random = decode_random(random_splits[i], Keys.list_from_bytes(raw_private_keys)) restored_randoms.append(random) self.assertEqual(randoms_list, restored_randoms) seed = epoch.extract_shared_random(top_block_hash) self.assertEqual(expected_seed, seed)
def test_confirmations_calculation(self): dag = Dag(0) private = Private.generate() prev_hash = dag.genesis_block().get_hash() for i in range(1, 9): if i == 5 or i == 7: continue block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() prev_hash = dag.blocks_by_number[1][0].get_hash() for i in range(2, 5): if i == 3: continue block = BlockFactory.create_block_with_timestamp( [prev_hash], BLOCK_TIME * i + 1) signed_block = BlockFactory.sign_block(block, private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() immutability = Immutability(dag) # first branch check confirmations = immutability.calculate_confirmations( dag.blocks_by_number[8][0].get_hash()) self.assertEqual(confirmations, 0) skipped_block = SkippedBlock(dag.blocks_by_number[8][0].get_hash(), backstep_count=1) confirmations = immutability.calculate_skipped_block_confirmations( skipped_block) self.assertEqual(confirmations, 1) confirmations = immutability.calculate_confirmations( dag.blocks_by_number[6][0].get_hash()) self.assertEqual(confirmations, 1) skipped_block = SkippedBlock(dag.blocks_by_number[6][0].get_hash(), backstep_count=1) confirmations = immutability.calculate_skipped_block_confirmations( skipped_block) self.assertEqual(confirmations, 2) confirmations = immutability.calculate_confirmations( dag.blocks_by_number[4][0].get_hash()) self.assertEqual(confirmations, 2) # second branch check confirmations = immutability.calculate_confirmations( dag.blocks_by_number[4][1].get_hash()) self.assertEqual(confirmations, 0) skipped_block = SkippedBlock(dag.blocks_by_number[4][1].get_hash(), backstep_count=1) confirmations = immutability.calculate_skipped_block_confirmations( skipped_block) self.assertEqual(confirmations, 1) confirmations = immutability.calculate_confirmations( dag.blocks_by_number[2][1].get_hash()) self.assertEqual(confirmations, 1) # common ancestor # four existing blocks in the following five slots confirmations = immutability.calculate_confirmations( dag.blocks_by_number[1][0].get_hash()) self.assertEqual(confirmations, 4)
def test_commit_reveal(self): dag = Dag(0) epoch = Epoch(dag) private = Private.generate() prev_hash = ChainGenerator.fill_with_dummies( dag, dag.genesis_block().get_hash(), Epoch.get_round_range(1, Round.PUBLIC)) randoms_list = [] for i in Epoch.get_round_range(1, Round.COMMIT): random_value = int.from_bytes(os.urandom(32), byteorder='big') randoms_list.append(random_value) expected_seed = sum_random(randoms_list) reveals = [] epoch_hash = dag.genesis_block().get_hash() for i in Epoch.get_round_range(1, Round.COMMIT): rand = randoms_list.pop() random_bytes = rand.to_bytes(32, byteorder='big') commit, reveal = TestEpoch.create_dummy_commit_reveal( random_bytes, epoch_hash) commit_block = BlockFactory.create_block_with_timestamp( [prev_hash], i * BLOCK_TIME) commit_block.system_txs = [commit] signed_block = BlockFactory.sign_block(commit_block, private) dag.add_signed_block(i, signed_block) prev_hash = commit_block.get_hash() reveals.append(reveal) revealing_key = Keys.from_bytes(reveal.key) encrypted_bytes = Public.encrypt(random_bytes, Private.publickey(revealing_key)) decrypted_bytes = Private.decrypt(encrypted_bytes, revealing_key) # TODO check if encryption decryption can work million times in a row self.assertEqual(decrypted_bytes, random_bytes) revealed_value = Private.decrypt(commit.rand, revealing_key) self.assertEqual(revealed_value, random_bytes) # self.assertEqual(len(reveals), ROUND_DURATION) prev_hash = ChainGenerator.fill_with_dummies( dag, prev_hash, Epoch.get_round_range(1, Round.SECRETSHARE)) for i in Epoch.get_round_range(1, Round.REVEAL): reveal_block = BlockFactory.create_block_with_timestamp( [prev_hash], i * BLOCK_TIME) reveal_block.system_txs = [reveals.pop()] signed_block = BlockFactory.sign_block(reveal_block, private) dag.add_signed_block(i, signed_block) prev_hash = reveal_block.get_hash() prev_hash = ChainGenerator.fill_with_dummies( dag, prev_hash, Epoch.get_round_range(1, Round.PRIVATE)) prev_hash = ChainGenerator.fill_with_dummies( dag, prev_hash, Epoch.get_round_range(1, Round.FINAL)) seed = epoch.reveal_commited_random(prev_hash) self.assertEqual(expected_seed, seed)
def sign_block(self, current_block_number): current_round_type = self.epoch.get_round_by_block_number( current_block_number) epoch_number = Epoch.get_epoch_number(current_block_number) system_txs = self.get_system_transactions_for_signing( current_round_type) payment_txs = self.get_payment_transactions_for_signing( current_block_number) tops = self.dag.get_top_blocks_hashes() chosen_top = self.dag.get_longest_chain_top(tops) conflicting_tops = [top for top in tops if top != chosen_top] current_top_blocks = [ chosen_top ] + conflicting_tops # first link in dag is not considered conflict, the rest is. if self.behaviour.off_malicious_links_to_wrong_blocks: current_top_blocks = [] all_hashes = list(self.dag.blocks_by_hash.keys()) for _ in range(random.randint(1, 3)): block_hash = random.choice(all_hashes) current_top_blocks.append(block_hash) self.logger.info( "Maliciously connecting block at slot %s to random hashes", current_block_number) block = BlockFactory.create_block_dummy(current_top_blocks) block.system_txs = system_txs block.payment_txs = payment_txs signed_block = BlockFactory.sign_block(block, self.block_signer.private_key) if self.behaviour.malicious_block_broadcast_delay > 0: self.behaviour.block_to_delay_broadcasting = signed_block return # don't do broadcasting, wait a few timeslots1 self.dag.add_signed_block(current_block_number, signed_block) self.utxo.apply_payments(payment_txs) self.conflict_watcher.on_new_block_by_validator( block.get_hash(), epoch_number, self.node_pubkey) if not self.behaviour.transport_cancel_block_broadcast: # behaviour flag for cancel block broadcast self.logger.debug("Broadcasting signed block number %s", current_block_number) self.network.broadcast_block(self.node_id, signed_block.pack()) else: self.logger.info( "Created but maliciously skipped broadcasted block") if self.behaviour.malicious_excessive_block_count > 0: additional_block_timestamp = block.timestamp + 1 additional_block = BlockFactory.create_block_with_timestamp( current_top_blocks, additional_block_timestamp) additional_block.system_txs = block.system_txs additional_block.payment_txs = block.payment_txs signed_add_block = BlockFactory.sign_block( additional_block, self.block_signer.private_key) self.dag.add_signed_block(current_block_number, signed_add_block) self.conflict_watcher.on_new_block_by_validator( signed_add_block.get_hash(), epoch_number, self.node_pubkey) #mark our own conflict for consistency self.logger.info("Sending additional block") self.network.broadcast_block(self.node_id, signed_add_block.pack()) self.behaviour.malicious_excessive_block_count -= 1
def test_release_stake(self): # base initialization dag = Dag(0) epoch = Epoch(dag) permissions = Permissions(epoch) node_private = Private.generate() initial_validators = Validators.read_genesis_validators_from_file() genesis_hash = dag.genesis_block().get_hash() prev_hash = genesis_hash for i in range(1, 9): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * 9) # create new node for stake hold new_node_private = Private.generate() new_node_public = Private.publickey(new_node_private) # create transaction for stake hold for new node tx_hold = StakeHoldTransaction() tx_hold.amount = 2000 tx_hold.pubkey = Keys.to_bytes(new_node_public) tx_hold.signature = Private.sign(tx_hold.get_hash(), new_node_private) # append signed stake hold transaction block.system_txs.append(tx_hold) # sign block by one of validators signed_block = BlockFactory.sign_block(block, node_private) # add signed block to DAG dag.add_signed_block(9, signed_block) resulting_validators = permissions.get_validators(block.get_hash()) pub_keys = [] for validator in resulting_validators: pub_keys.append(validator.public_key) self.assertIn(new_node_public, pub_keys) # add blocks for new epoch for i in range(10, 18): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() # create stake release transaction for new stakeholder tx_release = StakeReleaseTransaction() tx_release.pubkey = Keys.to_bytes(new_node_public) tx_release.signature = Private.sign(tx_hold.get_hash(), new_node_private) # append signed stake release transaction block.system_txs.append(tx_release) # sign block by one of validators signed_block = BlockFactory.sign_block(block, node_private) # add signed block to DAG dag.add_signed_block(19, signed_block) # verify that new stake holder now is NOT in validators list (after stake release transaction signed by holder) resulting_validators = permissions.get_validators(block.get_hash()) pub_keys = [] for validator in resulting_validators: pub_keys.append(validator.public_key) self.assertNotIn(new_node_public, pub_keys)