def __init__(self, epoch, validators=Validators()): initial_validators = validators.validators if not initial_validators: initial_validators = Validators.read_genesis_validators_from_file() self.epoch = epoch self.stake_manager = StakeManager(epoch) genesis_hash = self.epoch.dag.genesis_block().get_hash() validator_count = len(initial_validators) initial_signers_indexes = validators.signers_order if not initial_signers_indexes: initial_signers_indexes = self.epoch.calculate_validators_indexes( genesis_hash, validator_count, Source.SIGNERS) initial_randomizers_indexes = validators.randomizers_order if not initial_randomizers_indexes: initial_randomizers_indexes = self.epoch.calculate_validators_indexes( genesis_hash, validator_count, Source.RANDOMIZERS) self.log( "Initial signers:", initial_signers_indexes[0:ROUND_DURATION * 1], initial_signers_indexes[ROUND_DURATION * 1:ROUND_DURATION * 2], initial_signers_indexes[ROUND_DURATION * 2:ROUND_DURATION * 3], initial_signers_indexes[ROUND_DURATION * 3:ROUND_DURATION * 4], initial_signers_indexes[ROUND_DURATION * 4:ROUND_DURATION * 5], initial_signers_indexes[ROUND_DURATION * 5:ROUND_DURATION * 6 + 1]) self.log("Initial randomizers:", initial_randomizers_indexes) # init validators list and indexes, so we can build list of future validators based on this self.epoch_validators = {genesis_hash: initial_validators} self.signers_indexes = {genesis_hash: initial_signers_indexes} self.randomizers_indexes = {genesis_hash: initial_randomizers_indexes}
def test_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() network = Network() helper = TestHelper(network) helper.generate_nodes(private_keys, 19) # create validators # generate blocks to new epoch helper.perform_block_steps(22) DagVisualizer.visualize(network.nodes[0].dag) # invalidate that node DO not send negative gossip only if have ZETA negatives from next ZETA validators round_2_signers_order = list( network.nodes[0].permissions.signers_indexes.values())[ 1] # SECOND! EPOCH last_block_signer_id = round_2_signers_order[2] last_signed_block_number = network.nodes[ last_block_signer_id].last_signed_block_number # assert signers order self.assertEqual(last_signed_block_number, 22) expected_node_signer_id = round_2_signers_order[ 3] # already have 22 blocks # maliciously skip block by next signer network.nodes[ expected_node_signer_id].behaviour.transport_cancel_block_broadcast = True # perform in block step Time.advance_to_next_timeslot() # perform step by malicious node (create block and not broadcast) network.nodes[expected_node_signer_id].step() # assume that node has block in local dag for node in network.nodes: if node.node_id == expected_node_signer_id: self.assertEqual(len(node.dag.blocks_by_number), 24) else: self.assertEqual(len(node.dag.blocks_by_number), 23) # perform step in timeslot by all nodes helper.perform_in_block_single_step(3) Time.advance_to_next_timeslot() # move to next timeslot network.nodes[ expected_node_signer_id].behaviour.transport_cancel_block_broadcast = False helper.perform_in_block_single_step(1) # validate send gossips that all nodes receive block by positive gossip helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 25) helper.perform_block_steps(5) helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 30)
def test_two_node_groups(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() network = Network() helper = TestHelper(network) helper.generate_nodes(private_keys, 19) # create validators helper.add_stakeholders(9) # add stakeholders to network # generate blocks to new epoch helper.perform_block_steps(22) DagVisualizer.visualize(network.nodes[0].dag) # divide network into two groups network.move_nodes_to_group_by_id(1, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]) network.move_nodes_to_group_by_id(2, [20, 21, 22, 23, 24, 25, 26, 27]) test_group_1 = network.groups.get(1) test_group_2 = network.groups.get(2) # check nodes count in groups self.assertEqual(len(test_group_1), 20) self.assertEqual(len(test_group_2), 8) helper.perform_block_steps(5) self.assertEqual(len(network.groups.get(1)[0].dag.blocks_by_hash), 28) # group_1 = 28 blocks self.assertEqual(len(network.groups.get(2)[0].dag.blocks_by_hash), 23) # group_2 = 23 blocks network.merge_all_groups() # marge all groups self.assertEqual(len(network.nodes), 28) helper.perform_block_steps(1) # perform sync timeslot steps # !!! on performed step all nodes from second group obtain all missed blocks to last received # [21, 22, 23, 24, 25, 26, 27, 28] # check first node blocks self.assertEqual(len(network.nodes[0].dag.blocks_by_hash), 29) # check last node blocks self.assertEqual(len(network.nodes[27].dag.blocks_by_hash), 29) # nodes tops assert self.assertEqual(network.nodes[27].epoch.tops_and_epochs, network.nodes[0].epoch.tops_and_epochs)
def test_block_getter_by_node_groups(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() network = Network() helper = TestHelper(network) helper.generate_nodes(private_keys, 19) # create validators # add validators for group helper.add_stakeholders(9) # add stakeholders to network # generate blocks to new epoch helper.perform_block_steps(22) # divide network into two groups network.move_nodes_to_group_by_id(1, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]) network.move_nodes_to_group_by_id(2, [20, 21, 22, 23, 24, 25, 26, 27]) # perform step for generate merged block epoch_0_signers_order = list( network.nodes[0].permissions.signers_indexes.values())[ 0] # SECOND! EPOCH next_malicious_signer_node_index = epoch_0_signers_order[3] network.groups[1][ next_malicious_signer_node_index].behaviour.malicious_excessive_block_count = 1 helper.perform_block_steps(1) self.assertEqual(len(network.groups.get(1)[0].dag.blocks_by_hash), 25) # group_1 = 25 blocks self.assertEqual(len(network.groups.get(2)[0].dag.blocks_by_hash), 23) # group_2 = 23 blocks # DagVisualizer.visualize(network.groups.get(1)[0].dag) # DagVisualizer.visualize(network.groups.get(2)[0].dag) network.merge_all_groups() self.assertEqual(len(network.nodes[0].dag.blocks_by_hash), 25) self.assertEqual(len(network.nodes[27].dag.blocks_by_hash), 23) helper.perform_block_steps(1) # all blocks are received and marged by orphan system self.assertEqual(len(network.nodes[0].dag.blocks_by_hash), 26) self.assertEqual(len(network.nodes[27].dag.blocks_by_hash), 26)
def test_more_conflict_blocks(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() network = Network() helper = TestHelper(network) helper.generate_nodes(private_keys, 19) # create validators # generate blocks to new epoch helper.perform_block_steps(5) epoch_0_signers_order = list( network.nodes[0].permissions.signers_indexes.values())[ 0] # SECOND! EPOCH next_malicious_signer_node_index = epoch_0_signers_order[ 5] # block count # set behavior for next validator is_malicious_excessive_block = True network.nodes[ next_malicious_signer_node_index].behaviour.malicious_excessive_block_count = 2 network.nodes[ next_malicious_signer_node_index].tried_to_sign_current_block = False network.nodes[ next_malicious_signer_node_index].last_signed_block_number = 0 helper.perform_block_steps(1) # network.nodes[ next_malicious_signer_node_index].tried_to_sign_current_block = False network.nodes[ next_malicious_signer_node_index].last_signed_block_number = 0 helper.perform_block_steps(1) # # nodes MUST HAVE +2 conflict blocks helper.perform_block_steps(1) helper.perform_block_steps(1) DagVisualizer.visualize(network.nodes[0].dag) helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 10) # + 2 conflict # -- next validator MUST get two block conflicts send transaction helper.perform_block_steps(1) helper.list_validator(self, network.nodes, ['dag.blocks_by_number.system_txs'], 1) # all nodes have all blocks test done helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 11)
def test_negative_gossips_zata_validators(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() network = Network() helper = TestHelper(network) helper.generate_nodes(private_keys, 19) # create validators # generate blocks to new epoch helper.perform_block_steps(22) # DagVisualizer.visualize(network.nodes[0].dag) # invalidate that node DO not send negative gossip only if have ZETA negatives from next ZETA validators round_2_signers_order = list( network.nodes[0].permissions.signers_indexes.values())[ 1] # SECOND! EPOCH expected_node_signer_id = round_2_signers_order[ 2] # already have 22 blocks last_block_signer_index = network.nodes[ expected_node_signer_id].last_signed_block_number # assert signers order self.assertEqual(last_block_signer_index, 22) # get next ZETA SIGNERS by order next_zeta_signers_order = round_2_signers_order[ 3:3 + ZETA + 1] # -20 for first epoch +1 for maliciously skip block # next_zeta_signers_order gossips need to wait # maliciously skip block by next signer network.nodes[ expected_node_signer_id].behaviour.malicious_skip_block = True # perform in block step Time.advance_to_next_timeslot() helper.perform_in_block_single_step(BLOCK_TIME) Time.advance_to_next_timeslot() helper.perform_in_block_single_step(1) # validate send gossips # nodes may contain different count of gossips but MUST contain all ZETA validators keys for stop send -gossip self.assertEqual(len(network.nodes[0].mempool.gossips), 5) helper.list_validator(self, network.nodes, ['mempool.gossips.length'], 5)
def test_conflict_block_validator_processing(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() network = Network() helper = TestHelper(network) helper.generate_nodes(private_keys, 19) # create validators # generate blocks to new epoch helper.perform_block_steps(5) epoch_0_signers_order = list( network.nodes[0].permissions.signers_indexes.values())[ 0] # SECOND! EPOCH next_malicious_signer_node_index = epoch_0_signers_order[ 5] # block count # set behavior for next validator is_malicious_excessive_block = True network.nodes[ next_malicious_signer_node_index].behaviour.malicious_excessive_block_count = 1 # perform next one block step helper.perform_block_steps(1) # nodes MUST HAVE 2 conflict blocks helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 7) # + 1 conflict network.nodes[ next_malicious_signer_node_index].behaviour.malicious_excessive_block = False # -- next validator MUST get two block conflicts helper.perform_block_steps(1) helper.list_validator(self, network.nodes, ['dag.blocks_by_number.system_txs'], 0) # all nodes have all blocks test done helper.list_validator(self, network.nodes, ['dag.blocks_by_number.length'], 8)
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_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 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 __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_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_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)
def test_node_handle_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_input = 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), 1) # ensure that node1 DO NOT receive block node1.step() Time.advance_to_next_timeslot() node0.step() # do nothing node1.step() # send negative gossip (block from node0 not received) self.assertEqual( len(node0.mempool.gossips), 2) # ensure negative gossip received by node0 (+positive gossip) self.assertEqual(len(node1.mempool.gossips), 1) # ensure node1 NOT receive positive gossip node0.step() # provide and block by hash in gossip logic scope node1.step( ) # node1 do not ask block by hash (cant receive positive gossip due behaviour) # for node1 block 1 (created by node 0) is not available due behaviour # node1 produce block by step and sand it by broadcast self.assertEqual(len(node0.dag.blocks_by_number), 3) # NODE_0 have 2 blocks with genesis ancestor self.assertEqual(len(node1.dag.blocks_by_number), 2) # have genesis + self produced block
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_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_network_methods(self): private_keys = BlockSigners() private_keys = private_keys.block_signers validators = Validators() network = Network() node0 = Node(genesis_creation_time=1, node_id=0, network=network, block_signer=private_keys[0], validators=validators, behaviour=Behaviour()) node1 = Node(genesis_creation_time=1, node_id=1, network=network, block_signer=private_keys[1], validators=validators, behaviour=Behaviour()) node2 = Node(genesis_creation_time=1, node_id=2, network=network, block_signer=private_keys[2], validators=validators, behaviour=Behaviour()) node3 = Node(genesis_creation_time=1, node_id=3, network=network, block_signer=private_keys[3], validators=validators, behaviour=Behaviour()) node4 = Node(genesis_creation_time=1, node_id=4, network=network, block_signer=private_keys[4], validators=validators, behaviour=Behaviour()) network.register_node(node0) network.register_node(node1) network.register_node(node2) network.register_node(node3) network.register_node(node4) self.assertEqual(len(network.nodes) == 5, True) network.move_nodes_to_group( 0, [node0, node1]) # create group 0 with nodes 0, 1 network.move_nodes_to_group( 1, [node2, node3, node4]) # create group 1 with nodes 2, 3, 4 self.assertEqual(len(network.groups) == 2, True) self.assertEqual(len(network.groups[0]) == 2, True) self.assertEqual(len(network.groups[1]) == 3, True) network.merge_all_groups() # test marge groups self.assertEqual(len(network.nodes) == 5, True)
def test_node_offline(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) node1 = Node(genesis_creation_time=1, node_id=1, network=network, block_signer=private_keys[1], validators=validators, behaviour=Behaviour()) network.register_node(node1) behaviour = Behaviour() behaviour.transport_node_disable_input = True behaviour.transport_node_disable_output = True node2 = Node(genesis_creation_time=1, node_id=2, network=network, block_signer=private_keys[2], validators=validators, behaviour=behaviour) network.register_node(node2) # emulate node total offline Time.advance_to_next_timeslot() node0.step() # provide block node1.step() node2.step() self.assertEqual(len(node0.dag.blocks_by_number), 2) self.assertEqual(len(node1.dag.blocks_by_number), 2) self.assertEqual(len(node2.dag.blocks_by_number), 1) # steel have 1 block Time.advance_to_next_timeslot() node0.step() node1.step() # provide block node2.step() self.assertEqual(len(node0.dag.blocks_by_number), 3) self.assertEqual(len(node1.dag.blocks_by_number), 3) self.assertEqual(len(node2.dag.blocks_by_number), 1) # steel have 1 block Time.advance_to_next_timeslot() node0.step() node1.step() node2.step() # wit for block and try to broadcast negative gossip self.assertEqual(len(node0.dag.blocks_by_number), 3) self.assertEqual(len(node1.dag.blocks_by_number), 3) self.assertEqual(len(node2.dag.blocks_by_number), 1) node0.step() node1.step() node2.step() # provide block BUT DO NOT BROADCAST self.assertEqual(len(node0.dag.blocks_by_number), 3) self.assertEqual(len(node1.dag.blocks_by_number), 3) self.assertEqual(len(node2.dag.blocks_by_number), 2)
def test_make_node_offline_from_block(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) node1 = Node(genesis_creation_time=1, node_id=1, network=network, block_signer=private_keys[1], validators=validators, behaviour=Behaviour()) network.register_node(node1) behaviour = Behaviour() behaviour.transport_keep_offline = [ 4, 6 ] # keep offline from 4 block till 6 block node2 = Node(genesis_creation_time=1, node_id=2, network=network, block_signer=private_keys[2], validators=validators, behaviour=behaviour) network.register_node( node2) # emulate node total offline from 4 block till 6 block # ------------------------------- block 1 Time.advance_to_next_timeslot() node0.step() # provide block node1.step() node2.step() self.assertEqual(len(node0.dag.blocks_by_number), 2) self.assertEqual(len(node1.dag.blocks_by_number), 2) self.assertEqual(len(node2.dag.blocks_by_number), 2) # ------------------------------- block 2 Time.advance_to_next_timeslot() node0.step() node1.step() # provide block node2.step() self.assertEqual(len(node0.dag.blocks_by_number), 3) self.assertEqual(len(node1.dag.blocks_by_number), 3) self.assertEqual(len(node2.dag.blocks_by_number), 3) # ------------------------------- block 3 Time.advance_to_next_timeslot() node0.step() node1.step() node2.step() # provide block self.assertEqual(len(node0.dag.blocks_by_number), 4) self.assertEqual(len(node1.dag.blocks_by_number), 4) self.assertEqual(len(node2.dag.blocks_by_number), 4) # ------------------------------- block 4 # node 2 must set offline on next block Time.advance_to_next_timeslot() node0.step() # provide block node1.step() node2.step() # AFTER NODE STEP IT MAKES OFFLINE ! self.assertEqual(len(node0.dag.blocks_by_number), 5) self.assertEqual(len(node1.dag.blocks_by_number), 5) self.assertEqual(len(node2.dag.blocks_by_number), 5) # RECEIVE BLOCK BEFORE BEHAVIOUR UPDATES # ------------------------------- block 5 # node 2 offline Time.advance_to_next_timeslot() node0.step() node1.step() # provide block node2.step() self.assertEqual(len(node0.dag.blocks_by_number), 6) self.assertEqual(len(node1.dag.blocks_by_number), 6) self.assertEqual(len(node2.dag.blocks_by_number), 5) # DO NOT RECEIVE BLOCK ! # ------------------------------- block 6 # node 2 offline Time.advance_to_next_timeslot() node0.step() node1.step() node2.step( ) # DO NOT RECEIVE BLOCK wait for nex step # + produce but not broadcast negative gossip self.assertEqual(len(node0.dag.blocks_by_number), 6) self.assertEqual(len(node1.dag.blocks_by_number), 6) self.assertEqual(len(node2.dag.blocks_by_number), 5) # DO NOT RECEIVE BLOCK ! node0.step() node1.step() # node 2 try to sand negative gossip by block 5 (on offline store it in local mempool and add to block !!!!!!!) # NODE_0 AND NODE_1 DO NOT RECEIVE NEGATIVE GOSSIP BY BLOCK 5 node2.step() # create and store block localy (steel offline) self.assertEqual(len(node0.dag.blocks_by_number), 6) self.assertEqual(len(node1.dag.blocks_by_number), 6) self.assertEqual(len(node2.dag.blocks_by_number), 6) # node 2 forks chain # ------------------------------- block 7 (timeslot) # node 2 make online again on step Time.advance_to_next_timeslot() node0.step( ) # provide negative gossip for block 6 before creating and broadcasting block node1.step() # provide negative gossip for block 6 node2.step( ) # current step makes node online (its do not receive gossips from node0 and node1) (variant A) self.assertEqual(len(node0.dag.blocks_by_number), 6) self.assertEqual(len(node1.dag.blocks_by_number), 6) self.assertEqual(len(node2.dag.blocks_by_number), 6) # visualization and description block =========================================== # DagVisualizer.visualize(node0.dag) # DagVisualizer.visualize(node2.dag) # on current time nodes have such blocks # timeslot[0, 1, 2, 3, 4, 5, 6, 7] # ==================================== # node0 - [0, 1, 2, 3, 4, 5, <>, node0] # node1 - [0, 1, 2, 3, 4, 5, <>, ] # node2 - [0, 1, 2, 3, 4, <>, 6, ] # ancessor for block 7 is block 5 # ancessor for block 5 is block 4 # node2 request block 5 as ancestor for block 7 (block 6 was skipped till node was offline) # node2 received and insert block 5 as conflict to empty timeslot till offline to block 6 # block 6 was created offline, need to skip all its tx's and softly drop # visualization and description block =========================================== # node0 - provide block 7 node0.step() # create and broadcast block number 7 # visualization and description block =========================================== # DagVisualizer.visualize(node0.dag) # [0,1,2,3,4,5,6,<>,7] # by timeslots [ 5,<>, 6] # DagVisualizer.visualize(node2.dag) # [0,1,2,3,4,<>, 6, -] # visualization and description block =========================================== node1.step() # handle and add block normaly node2.step( ) # handled all ancestor blocks and inset it to dag with processing included transactions self.assertEqual(len(node0.dag.blocks_by_number), 7) self.assertEqual(len(node1.dag.blocks_by_number), 7) self.assertEqual(len(node2.dag.blocks_by_number), 8) # steel have redundant block 6 # ------------------------------- block 8 (timeslot) # all nodes online Time.advance_to_next_timeslot() node0.step() node1.step() node2.step() self.assertEqual(len(node0.dag.blocks_by_number), 8) self.assertEqual(len(node1.dag.blocks_by_number), 8) self.assertEqual(len(node2.dag.blocks_by_number), 9) # steel have redundant block 6