class AnnouncerNode: def __init__(self, genesis_creation_time, logger): self.logger = logger self.dag = Dag(genesis_creation_time) self.epoch = Epoch(self.dag) self.epoch.set_logger(self.logger) self.logger.info("Starting announcer node") self.last_announced_round = None self.terminated = False def step(self): current_block_number = self.epoch.get_current_timeframe_block_number() if self.epoch.is_new_epoch_upcoming(current_block_number): self.logger.info("New epoch upcoming!") current_round = self.epoch.get_round_by_block_number( current_block_number) if current_round != self.last_announced_round: self.logger.info("Starting %s", str(current_round)) self.last_announced_round = current_round async def run(self): while True: self.step() await asyncio.sleep(1)
def __init__(self, genesis_creation_time, logger): self.logger = logger self.dag = Dag(genesis_creation_time) self.epoch = Epoch(self.dag) self.epoch.set_logger(self.logger) self.logger.info("Starting announcer node") self.last_announced_round = None self.terminated = False
def test_node_broadcast_unavailable(self): Time.use_test_time() Time.set_current_time(1) private_keys = BlockSigners() private_keys = private_keys.block_signers validators = Validators() validators.validators = Validators.read_genesis_validators_from_file() validators.signers_order = [0] + [1] * Epoch.get_duration() validators.randomizers_order = [0] * Epoch.get_duration() network = Network() node0 = Node(genesis_creation_time=1, node_id=0, network=network, block_signer=private_keys[0], validators=validators, behaviour=Behaviour()) network.register_node(node0) # behaviour flag for disabling node to broadcast behaviour = Behaviour() behaviour.transport_node_disable_output = True node1 = Node(genesis_creation_time=1, node_id=1, network=network, block_signer=private_keys[1], validators=validators, behaviour=behaviour) network.register_node(node1) Time.advance_to_next_timeslot() node0.step() # provide block self.assertEqual(len(node0.dag.blocks_by_number), 2) # ensure that node0 provide block to chain self.assertEqual(len(node1.dag.blocks_by_number), 2) # ensure that node1 receive block node1.step() # do nothing self.assertEqual(len(node0.dag.blocks_by_number), 2) self.assertEqual(len(node1.dag.blocks_by_number), 2) Time.advance_to_next_timeslot() node0.step() # do nothing node1.step( ) # node1 must provide block (and public key tx) but unable to broadcast it by network self.assertEqual(len(node0.dag.blocks_by_number), 2) self.assertEqual(len(node1.dag.blocks_by_number), 3)
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 get_stake_actions(self, epoch_hash): epoch_iter = ChainIter(self.epoch.dag, epoch_hash) stake_actions = [] count = 0 for block in epoch_iter: if epoch_iter.block_number == 0: break if block: for tx in block.block.system_txs: if isinstance(tx, StakeHoldTransaction) \ or isinstance(tx, StakeReleaseTransaction) \ or isinstance(tx, PenaltyTransaction) \ or isinstance(tx, PenaltyGossipTransaction): stake_actions.append(tx) count += 1 if count == Epoch.get_duration(): break stake_actions = list(reversed(stake_actions)) return stake_actions
def get_allowed_signers_for_block_number(self, block_number): # TODO take cached epoch hashes if block is of lastest epoch prev_epoch_number = self.epoch.get_epoch_number(block_number) - 1 prev_epoch_start = self.epoch.get_epoch_start_block_number( prev_epoch_number) prev_epoch_end = self.epoch.get_epoch_end_block_number( prev_epoch_number) # this will extract every unconnected block in epoch, which is practically epoch hash # TODO maybe consider blocks to be epoch hashes if they are in final round and consider everything else is orphan epoch_hashes = self.dag.get_branches_for_timeslot_range( prev_epoch_start, prev_epoch_end + 1) if prev_epoch_number == 0: epoch_hashes = [self.dag.genesis_block().get_hash()] allowed_signers = [] for epoch_hash in epoch_hashes: epoch_block_number = Epoch.convert_to_epoch_block_number( block_number) allowed_pubkey = self.permissions.get_sign_permission( epoch_hash, epoch_block_number).public_key allowed_signers.append(allowed_pubkey) assert len(allowed_signers) > 0, "No signers allowed to sign block" return allowed_signers
def try_to_sign_block(self, current_block_number): epoch_block_number = Epoch.convert_to_epoch_block_number( current_block_number) allowed_to_sign = False epoch_hashes = self.epoch.get_epoch_hashes() for top, epoch_hash in epoch_hashes.items(): permission = self.permissions.get_sign_permission( epoch_hash, epoch_block_number) if permission.public_key == self.node_pubkey: allowed_to_sign = True break if allowed_to_sign: should_skip_maliciously = self.behaviour.is_malicious_skip_block() # first_epoch_ever = self.epoch.get_epoch_number(current_block_number) == 1 if should_skip_maliciously: # and not first_epoch_ever: # skip first epoch check self.epoch_private_keys.clear() self.logger.info("Maliciously skiped block") else: if self.last_signed_block_number < current_block_number: self.last_signed_block_number = current_block_number self.sign_block(current_block_number) else: # skip once more block broadcast in same timeslot pass
def step(self): current_block_number = self.epoch.get_current_timeframe_block_number() if self.epoch.is_new_epoch_upcoming(current_block_number): self.epoch.accept_tops_as_epoch_hashes() # service method for update node behavior (if behavior is temporary) self.behaviour.update(Epoch.get_epoch_number(current_block_number)) # service method for update transport behavior (if behavior is temporary) self.behaviour.update_transport(current_block_number) current_round = self.epoch.get_round_by_block_number( current_block_number) if current_round == Round.PUBLIC: self.try_to_publish_public_key(current_block_number) elif current_round == Round.SECRETSHARE: self.try_to_share_random() # elif current_round == Round.PRIVATE: # do nothing as private key should be included to block by block signer elif current_round == Round.COMMIT: self.try_to_commit_random() elif current_round == Round.REVEAL: self.try_to_reveal_random() elif current_round == Round.FINAL: # at this point we may remove everything systemic from mempool, # so it does not interfere with pubkeys for next epoch self.mempool.remove_all_systemic_transactions() if self.behaviour.wants_to_hold_stake: self.broadcast_stakehold_transaction() self.behaviour.wants_to_hold_stake = False if self.behaviour.wants_to_release_stake: self.broadcast_stakerelease_transaction() self.behaviour.wants_to_release_stake = False if self.behaviour.malicious_send_negative_gossip_count > 0: self.broadcast_gossip_negative(self.last_expected_timeslot) self.behaviour.malicious_send_negative_gossip_count -= 1 if self.behaviour.malicious_send_positive_gossip_count > 0: zero_block = self.dag.blocks_by_number[0][ 0].block # send genesis block malicious self.broadcast_gossip_positive(zero_block.get_hash()) self.behaviour.malicious_send_positive_gossip_count -= 1 if self.owned_utxos: self.broadcast_payments() if current_block_number != self.last_expected_timeslot: self.tried_to_sign_current_block = False should_wait = self.handle_timeslot_changed( previous_timeslot_number=self.last_expected_timeslot, current_timeslot_number=current_block_number) if should_wait: return if not self.tried_to_sign_current_block: self.try_to_sign_block(current_block_number) self.tried_to_sign_current_block = True # will reset in next timeslot
def get_random_senders_pubkeys(self, epoch_hash): selected_epoch_validators = self.get_validators(epoch_hash) epoch_random_indexes = self.get_randomizers_indexes(epoch_hash) validators = [] for i in Epoch.get_round_range(1, Round.SECRETSHARE): index = epoch_random_indexes[i - 1] validators.append(selected_epoch_validators[index]) return validators
def get_ordered_signers_pubkeys_for_round(self, epoch_hash, round_type): selected_epoch_validators = self.get_validators(epoch_hash) epoch_signers_indexes = self.get_signers_indexes(epoch_hash) validators = [] for i in Epoch.get_round_range(1, round_type): index = epoch_signers_indexes[i - 1] validators.append(selected_epoch_validators[index]) return validators
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 insert_verified_block(self, signed_block, allowed_pubkey): block = signed_block.block block_number = self.epoch.get_block_number_from_timestamp( block.timestamp) epoch_number = Epoch.get_epoch_number(block_number) self.dag.add_signed_block(block_number, signed_block) self.mempool.remove_transactions(block.system_txs) self.mempool.remove_transactions(block.payment_txs) self.utxo.apply_payments(block.payment_txs) self.conflict_watcher.on_new_block_by_validator( block.get_hash(), epoch_number, allowed_pubkey)
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 __init__(self, genesis_creation_time, node_id, network, block_signer=BlockSigner(Private.generate()), validators=Validators(), behaviour=Behaviour(), logger=DummyLogger()): self.logger = logger self.dag = Dag(genesis_creation_time) self.epoch = Epoch(self.dag) self.epoch.set_logger(self.logger) self.permissions = Permissions(self.epoch, validators) self.mempool = Mempool() self.utxo = Utxo(self.logger) self.conflict_watcher = ConflictWatcher(self.dag) self.behaviour = behaviour self.block_signer = block_signer self.node_pubkey = Private.publickey(block_signer.private_key) self.logger.info("Public key is %s", Keys.to_visual_string(self.node_pubkey)) self.network = network self.node_id = node_id self.epoch_private_keys = [] # TODO make this single element # self.epoch_private_keys where first element is era number, and second is key to reveal commited random self.reveals_to_send = {} self.sent_shares_epochs = [] # epoch hashes of secret shares self.last_expected_timeslot = 0 self.last_signed_block_number = 0 self.tried_to_sign_current_block = False self.owned_utxos = [] self.terminated = False self.blocks_buffer = [ ] # uses while receive block and do not have its ancestor in local dag (before verify)
def test_sane_prev_hashes_found(self): dag = Dag(0) epoch = Epoch(dag) genesis_hash = dag.genesis_block().get_hash() block_hash1 = ChainGenerator.insert_dummy(dag, [genesis_hash], 1) block_hash2 = ChainGenerator.insert_dummy(dag, [block_hash1], 2) _block_hash3 = ChainGenerator.insert_dummy(dag, [genesis_hash, block_hash2], 3) verifier = BlockAcceptor(epoch, None) with self.assertRaises(AcceptionException): verifier.validate_non_ancestor_prev_hashes( [genesis_hash, block_hash2])
def try_to_send_negative_gossip(self, previous_timeslot_number): if previous_timeslot_number not in self.dag.blocks_by_number: epoch_block_number = Epoch.convert_to_epoch_block_number( previous_timeslot_number) allowed_to_send_negative_gossip = False epoch_hashes = self.epoch.get_epoch_hashes() for _, epoch_hash in epoch_hashes.items(): permissions = self.permissions.get_gossip_permission( epoch_hash, epoch_block_number) for permission in permissions: if permission.public_key == self.node_pubkey: allowed_to_send_negative_gossip = True break if allowed_to_send_negative_gossip: self.broadcast_gossip_negative(previous_timeslot_number) return True return False
def test_sane_prev_hashes_not_found(self): dag = Dag(0) epoch = Epoch(dag) genesis_hash = dag.genesis_block().get_hash() block_hash1 = ChainGenerator.insert_dummy(dag, [genesis_hash], 1) block_hash2 = ChainGenerator.insert_dummy(dag, [block_hash1], 2) block_hash3 = ChainGenerator.insert_dummy(dag, [genesis_hash], 3) _block_hash4 = ChainGenerator.insert_dummy(dag, [block_hash2, block_hash3], 4) verifier = BlockAcceptor(epoch, None) try: verifier.validate_non_ancestor_prev_hashes( [block_hash2, block_hash3]) except: self.fail("Prev hashes should not be self referential")
def get_ordered_randomizers_pubkeys_for_round(self, epoch_hash, round_type): selected_epoch_validators = self.get_validators(epoch_hash) if round_type == Round.PUBLIC or round_type == Round.PRIVATE: epoch_random_indexes = self.get_signers_indexes(epoch_hash) round_type = Round.PRIVATE validators = [] for i in Epoch.get_round_range(1, round_type): index = epoch_random_indexes[i - 1] validators.append(selected_epoch_validators[index]) return validators elif round_type == Round.SECRETSHARE: return self.get_secret_sharers(epoch_hash) elif round_type == Round.COMMIT or round_type == Round.REVEAL: return self.get_commiters(epoch_hash) assert False, "No randomizers exist for this round type!"
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_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 get_allowed_signers_for_next_block(self, block): current_block_number = self.epoch.get_current_timeframe_block_number() epoch_block_number = Epoch.convert_to_epoch_block_number( current_block_number) if self.epoch.is_new_epoch_upcoming(current_block_number): self.epoch.accept_tops_as_epoch_hashes() epoch_hashes = self.epoch.get_epoch_hashes() allowed_signers = [] for prev_hash in block.prev_hashes: epoch_hash = None if prev_hash in epoch_hashes: epoch_hash = epoch_hashes[prev_hash] else: epoch_hash = self.epoch.find_epoch_hash_for_block(prev_hash) if epoch_hash: # self.logger.info("Calculating permissions from epoch_hash %s", epoch_hash.hex()) allowed_pubkey = self.permissions.get_sign_permission( epoch_hash, epoch_block_number) allowed_signers.append(allowed_pubkey) assert len( allowed_signers) > 0, "No signers allowed to sign next block" return allowed_signers
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_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_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 test_maliciously_send_negative_and_positive_gossip(self): Time.use_test_time() Time.set_current_time(1) private_keys = BlockSigners() private_keys = private_keys.block_signers validators = Validators() validators.validators = Validators.read_genesis_validators_from_file() validators.signers_order = [0] + [1] + [2] + [3] + [ 4 ] + [5] * Epoch.get_duration() validators.randomizers_order = [0] * Epoch.get_duration() signer_index = 0 for i in Epoch.get_round_range(1, Round.PRIVATE): validators.signers_order[i] = signer_index signer_index += 1 network = Network() node0 = Node(genesis_creation_time=1, node_id=0, network=network, block_signer=private_keys[0], validators=validators, behaviour=Behaviour()) network.register_node(node0) behavior = Behaviour( ) # this node maliciously send positive and negative gossip behavior.malicious_send_negative_gossip_count = 1 behavior.malicious_send_positive_gossip_count = 1 node1 = Node(genesis_creation_time=1, node_id=1, network=network, block_signer=private_keys[1], validators=validators, behaviour=behavior) network.register_node(node1) node2 = Node(genesis_creation_time=1, node_id=2, network=network, block_signer=private_keys[2], validators=validators, behaviour=Behaviour()) network.register_node(node2) node3 = Node(genesis_creation_time=1, node_id=3, network=network, block_signer=private_keys[3], validators=validators, behaviour=Behaviour()) network.register_node(node3) node4 = Node(genesis_creation_time=1, node_id=4, network=network, block_signer=private_keys[4], validators=validators, behaviour=Behaviour()) network.register_node(node4) node5 = Node(genesis_creation_time=1, node_id=5, network=network, block_signer=private_keys[5], validators=validators, behaviour=Behaviour()) network.register_node(node5) helper = TestHelper(network) Time.advance_to_next_timeslot() # current block number 1 node0.step() # create and sign block # validate block created and broadcasted # validate mempool is empty # validate tx by hash is empty helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 2) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 0) # validate 2 public key tx helper.list_validator(self, network.nodes, ['dag.transactions_by_hash.length'], 1) # on one step sends +and- (add test for different steps ?) node1.step( ) # ! maliciously sand positive and negative gossip (request by genesis 0 block) # all node receive positive gossip # txs for now only in mempool (not in block) helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 2) # all nodes has 1-gossip and 6+gossips (1-gossip and 6+gossip from (0,1,2,3,4,5)) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 7) helper.list_validator(self, network.nodes, ['dag.transactions_by_hash.length'], 1) node2.step() node3.step() node4.step() node5.step() # after all steps situation same helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 2) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 7) helper.list_validator(self, network.nodes, ['dag.transactions_by_hash.length'], 1) Time.advance_to_next_timeslot() # current block number 2 node0.step() # do nothing node1.step( ) # is validator by order (need to marge mempool and provide block) # in current case node will penaltize SELF !!! helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 3) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 0) # tx_s # 3 - public key tx # 1 - negative gossip tx # 6 - positive gossip txs # 1 - penalty tx # total = 11 txs if ROUND_DURATION > 6: # total 6 nodes in test public_key_tx_count = 6 else: public_key_tx_count = ROUND_DURATION negative_gossip_tx_count = 1 positive_gossips_tx_count = 6 penalty_tx_count = 1 tx_total_count = public_key_tx_count + negative_gossip_tx_count + positive_gossips_tx_count + penalty_tx_count helper.list_validator(self, network.nodes, ['dag.transactions_by_hash.length'], tx_total_count) node2.step() node3.step() node4.step() node5.step() # validate that all keeps the same helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 3) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 0) helper.list_validator(self, network.nodes, ['dag.transactions_by_hash.length'], tx_total_count) # verify that node1 is steel in validators list helper.list_validator(self, network.nodes, ['permissions.epoch_validators.length'], GENESIS_VALIDATORS_COUNT) Time.advance_to_next_timeslot() # current block number 3 node0.step() # do nothing node1.step() # do nothing node2.step() # provide block node3.step() node4.step() node5.step() # validate new block by node2 helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 4) # verify that node1 is steel in validators list until epoch end helper.list_validator(self, network.nodes, ['permissions.epoch_validators.length'], GENESIS_VALIDATORS_COUNT) for i in range(5, ROUND_DURATION * 6 + 1): Time.advance_to_next_timeslot() if i == ROUND_DURATION * 6 + 1: node0.step() node0.step() node1.step() node2.step() node3.step() node4.step() node5.step() if i == ROUND_DURATION * 6 + 1: # ! chek up validators list on new epoch upcoming # TODO sometimes fall for unknoun reason # self.list_validator(network.nodes, ['dag.blocks_by_number.length'], i) for node in network.nodes: if len(node.dag.blocks_by_number) != i - 1: print('BLOCK_NUMBER : ' + str(i)) print('node id:' + str(node.node_id) + " dag.block_by_number:" + str(len(node1.dag.blocks_by_number))) helper.list_validator(self, network.nodes, ['permissions.epoch_validators.length'], GENESIS_VALIDATORS_COUNT - 1) # TODO nodes recalculates 2 times ? helper.list_validator( self, network.nodes, ['permissions.epoch_validators.epoch0.length'], GENESIS_VALIDATORS_COUNT - 1) # maybe 20 (on default block time and round duration) helper.list_validator( self, network.nodes, ['permissions.epoch_validators.epoch1.length'], GENESIS_VALIDATORS_COUNT - 1)
def test_maliciously_send_positive_gossip(self): Time.use_test_time() Time.set_current_time(1) private_keys = BlockSigners() private_keys = private_keys.block_signers validators = Validators() validators.validators = Validators.read_genesis_validators_from_file() validators.signers_order = [0, 1, 2, 3, 4, 5] * ROUND_DURATION * 6 validators.randomizers_order = [0] * Epoch.get_duration() network = Network() node0 = Node(genesis_creation_time=1, node_id=0, network=network, block_signer=private_keys[0], validators=validators, behaviour=Behaviour()) network.register_node(node0) behavior = Behaviour() # this node maliciously send positive gossip behavior.malicious_send_positive_gossip_count = 1 node1 = Node(genesis_creation_time=1, node_id=1, network=network, block_signer=private_keys[1], validators=validators, behaviour=behavior) network.register_node(node1) node2 = Node(genesis_creation_time=1, node_id=2, network=network, block_signer=private_keys[2], validators=validators, behaviour=Behaviour()) network.register_node(node2) node3 = Node(genesis_creation_time=1, node_id=3, network=network, block_signer=private_keys[3], validators=validators, behaviour=Behaviour()) network.register_node(node3) node4 = Node(genesis_creation_time=1, node_id=4, network=network, block_signer=private_keys[4], validators=validators, behaviour=Behaviour()) network.register_node(node4) node5 = Node(genesis_creation_time=1, node_id=5, network=network, block_signer=private_keys[5], validators=validators, behaviour=Behaviour()) network.register_node(node5) helper = TestHelper(network) Time.advance_to_next_timeslot() # current block number 1 node0.step() # create and sign block # validate block created and broadcasted # validate mempool is empty helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 2) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 0) node1.step( ) # ! maliciously sand positive gossip (request by genesis 0 block) # all node receive positive gossip # txs for now only in mempool (not in block) helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 2) # all nodes has 1+gossips helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 1) node2.step() node3.step() node4.step() node5.step() # after all steps situation same helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 2) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 1) Time.advance_to_next_timeslot() # current block number 2 node0.step() # do nothing node1.step( ) # is validator by order (need to marge mempool and provide block) # возможно добавить проверку на малишес скип блок в добавок ? # (по идеи все должны еще раз обменятся госипами и уже не найти блок 2) # (в таком случае следующий валидатор должен смерджить все и отправить блок) # (нода 1 должна быть исключена из списка валидаторов ?) # after node create and sign block all node clean its mem pool # here we have 3 blocks, empty mem pools, and transaction in dag.transaction_by_hash helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 3) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 0) node2.step() node3.step() node4.step() node5.step() # validate that all keeps the same helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 3) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 0) Time.advance_to_next_timeslot() # current block number 3 node0.step() # do nothing node1.step() # do nothing node2.step() # provide block node3.step() node4.step() node5.step() # validate new block by node2 helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 4)
def test_negative_gossip_by_zeta(self): Time.use_test_time() Time.set_current_time(1) private_keys = BlockSigners() private_keys = private_keys.block_signers validators = Validators() validators.validators = Validators.read_genesis_validators_from_file() validators.signers_order = [0] + [1] + [2] + [3] + [ 4 ] + [5] * Epoch.get_duration() validators.randomizers_order = [0] * Epoch.get_duration() network = Network() node0 = Node(genesis_creation_time=1, node_id=0, network=network, block_signer=private_keys[0], validators=validators, behaviour=Behaviour()) network.register_node(node0) behavior = Behaviour() # this node malicious skip block behavior.malicious_skip_block = True node1 = Node(genesis_creation_time=1, node_id=1, network=network, block_signer=private_keys[1], validators=validators, behaviour=behavior) network.register_node(node1) node2 = Node(genesis_creation_time=1, node_id=2, network=network, block_signer=private_keys[2], validators=validators, behaviour=Behaviour()) network.register_node(node2) node3 = Node(genesis_creation_time=1, node_id=3, network=network, block_signer=private_keys[3], validators=validators, behaviour=Behaviour()) network.register_node(node3) node4 = Node(genesis_creation_time=1, node_id=4, network=network, block_signer=private_keys[4], validators=validators, behaviour=Behaviour()) network.register_node(node4) node5 = Node(genesis_creation_time=1, node_id=5, network=network, block_signer=private_keys[5], validators=validators, behaviour=Behaviour()) network.register_node(node5) helper = TestHelper(network) Time.advance_to_next_timeslot() # current block number 1 node0.step() # create and sign block node1.step() node2.step() node3.step() node4.step() node5.step() # validate block created and broadcasted helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 2) Time.advance_to_next_timeslot() # current block number 1 node0.step() node1.step() # skip block creation node2.step() node3.step() node4.step() node5.step() # validate block NOT created and NOT broadcasted helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 2) Time.advance_to_next_timeslot() # current block number 2 # for now all chain do not have block from previous timeslot node0.step() # broadcast negative gossip # all nodes handle negative gossips by node0 # not broadcast to self (ADD TO MEMPOOL before broadcast) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 0) # not permited for gossip send node1.step() # broadcast negative gossip helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 1) node2.step( ) # broadcast negative gossip AND skip block signing for current step !!! node3.step() # broadcast negative gossip node4.step() # broadcast negative gossip node5.step( ) # VALIDATE 5 NEGATIVE GOSSIPS AND DO NOT BROADCAST ANOTHER ONE (current ZETA == 5) # GOSSIPS may be more - see test_negative_gossips_zata_validators helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 5) # duplicate gossips tx will NOT include to mempool ! # if node already send negative gossip IT NOT broadcast it again ! # if node already have x < ZETA (x - different negative gossips by block count) IT NOT broadcast it again ! Time.advance_time(1) # advance time by one second in current timeslot # make steps by nodes node0.step() # node1.step() # # steel 5 negative gossips (from 0,1,2,3,4) on all nodes (add validation ?) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 5) node2.step( ) # CREATE, SIGN, BROADCAST block (block by node1 not exist) # all nodes handle new block helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 3) # gossips cleaned from mem pool by block handling helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 0) node3.step() # node4.step() # node5.step() # # provide validation for next normal block and FOR GOSSIPS is NOT in mempool after next block Time.advance_to_next_timeslot() # current block number 3 node0.step() # node1.step() # node2.step() # node3.step( ) # must create and sign and broadcast block (all gossips MUST be mined and erased from mempool) node4.step() # node5.step() # # after node2 step helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 4) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 0)
def test_send_negative_gossip(self): Time.use_test_time() Time.set_current_time(1) private_keys = BlockSigners() private_keys = private_keys.block_signers validators = Validators() validators.validators = Validators.read_genesis_validators_from_file() validators.signers_order = [0, 1] * (Epoch.get_duration() // 2) validators.randomizers_order = [0] * Epoch.get_duration() network = Network() behavior = Behaviour() behavior.malicious_skip_block = True node0 = Node(genesis_creation_time=1, node_id=0, network=network, block_signer=private_keys[0], validators=validators, behaviour=behavior) network.register_node(node0) node1 = Node(genesis_creation_time=1, node_id=1, network=network, block_signer=private_keys[1], validators=validators, behaviour=Behaviour()) network.register_node(node1) Time.advance_to_next_timeslot() node0.step() self.assertEqual(len(node0.dag.blocks_by_number), 1) # ensure that block skipped by node0 node1.step() self.assertEqual(len(node0.dag.blocks_by_number), 1) # ensure that block not received by node1 Time.advance_to_next_timeslot() # on next step node0 broadcast negative gossip node0.step() # and include! it to (node0) self.mempool self.assertEqual(len(node0.mempool.gossips), 1) # assume that negative gossip broadcasted and placed to node1 mempool self.assertEqual(len(node1.mempool.gossips), 1) # ----------------------------------- # on next step node 1 will send negative gossip # node1 MUST create and sign block which contain negative gossip and broadcast it node1.step() # node1 in it's step broadcast(GOSSIP-) and at the same time SKIP!!! method # self.try_to_sign_block(current_block_number) # A second step is needed to create and sign a block within the same time slot Time.advance_time( 1 ) # !!! -----> advance time by 1 second (DO NOT CHANGE TIMESLOT) !!! node1.step() # ----------------------------------- # verify that node1 make block broadcast self.assertEqual(len(node1.dag.blocks_by_number), 2) # verify that node0 receive new block self.assertEqual(len(node0.dag.blocks_by_number), 2) # verify that negative gossip transaction is in block system_txs = node0.dag.blocks_by_number[2][0].block.system_txs self.assertTrue(NegativeGossipTransaction.__class__, system_txs[3].__class__)
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)
def test_send_negative_gossip_by_validator(self): Time.use_test_time() Time.set_current_time(1) private_keys = BlockSigners() private_keys = private_keys.block_signers validators = Validators() validators.validators = Validators.read_genesis_validators_from_file() validators.signers_order = [0] + [1] + [2] * Epoch.get_duration() validators.randomizers_order = [0] * Epoch.get_duration() network = Network() node0 = Node(genesis_creation_time=1, node_id=0, network=network, block_signer=private_keys[0], validators=validators, behaviour=Behaviour()) network.register_node(node0) behavior = Behaviour() behavior.transport_cancel_block_broadcast = True node1 = Node(genesis_creation_time=1, node_id=1, network=network, block_signer=private_keys[1], validators=validators, behaviour=behavior) network.register_node(node1) node2 = Node(genesis_creation_time=1, node_id=2, network=network, block_signer=private_keys[2], validators=validators, behaviour=Behaviour()) network.register_node(node2) # same config from prev. test Time.advance_to_next_timeslot() # current block number 1 node0.step() # create and sign block node1.step() node2.step() self.assertTrue(len(node0.dag.blocks_by_number) == 2, True) self.assertTrue(len(node1.dag.blocks_by_number) == 2, True) self.assertTrue(len(node2.dag.blocks_by_number) == 2, True) # asset that node0 create block number 2 # Time.advance_to_next_timeslot() # current block number 2 node0.step() node1.step() # skip broadcasting block node2.step() self.assertTrue(len(node0.dag.blocks_by_number) == 2, True) self.assertTrue(len(node1.dag.blocks_by_number) == 3, True) self.assertTrue(len(node2.dag.blocks_by_number) == 2, True) # assert that block 3 created on node1 but not broadcasted to node0 and node2 # Time.advance_to_next_timeslot() # current block number 3 node2.step( ) # MAKE FIRST STEP BY CURRENT TIMESLOT VALIDATOR (BLOCK SIGNER) self.assertTrue(len(node0.dag.blocks_by_number) == 3, True) self.assertTrue(len(node1.dag.blocks_by_number) == 3, True) self.assertTrue(len(node2.dag.blocks_by_number) == 3, True) # assert that all listeners nodes receive missed block`s Time.advance_time(1) # ADVANCE TIME BY ONE SECOND TIMESLOT SAME node2.step() self.assertTrue(len(node0.dag.blocks_by_number) == 4, True) self.assertTrue(len(node1.dag.blocks_by_number) == 4, True) self.assertTrue(len(node2.dag.blocks_by_number) == 4, True) # assert that node 2 create, sign, broadcast and deliver block number 4 # for certainty we will make some more steps by NOT VALIDATOR nodes's node0.step() node1.step() node1.step() node0.step() node0.step() Time.advance_to_next_timeslot() # current block number 4 node0.step() node1.step() node2.step() # step by all and validate block 5 self.assertTrue(len(node0.dag.blocks_by_number) == 5, True) self.assertTrue(len(node1.dag.blocks_by_number) == 5, True) self.assertTrue(len(node2.dag.blocks_by_number) == 5, True)