def test_shard_genesis_fork_fork(self): """ Test shard forks at genesis blocks due to root chain fork at GENESIS.ROOT_HEIGHT""" acc1 = Address.create_random_account(0) acc2 = Address.create_random_account(1) with ClusterContext(2, acc1, genesis_root_heights=[0, 1]) as clusters: # shutdown cluster connection clusters[1].peer.close() master0 = clusters[0].master is_root, root0 = call_async(master0.get_next_block_to_mine(acc1)) self.assertTrue(is_root) call_async(master0.add_root_block(root0)) genesis0 = clusters[0].get_shard_state(1).db.get_minor_block_by_height(0) self.assertEqual( genesis0.header.hash_prev_root_block, root0.header.get_hash() ) master1 = clusters[1].master is_root, root1 = call_async(master1.get_next_block_to_mine(acc2)) self.assertTrue(is_root) self.assertNotEqual(root0.header.get_hash(), root1.header.get_hash()) call_async(master1.add_root_block(root1)) genesis1 = clusters[1].get_shard_state(1).db.get_minor_block_by_height(0) self.assertEqual( genesis1.header.hash_prev_root_block, root1.header.get_hash() ) # let's make cluster1's root chain longer than cluster0's # expect to mine shard 0 due to proof-of-progress is_root, block1 = call_async(master1.get_next_block_to_mine(acc2)) self.assertFalse(is_root) self.assertEqual(block1.header.branch.get_shard_id(), 0) result = call_async( master1.add_raw_minor_block(block1.header.branch, block1.serialize()) ) self.assertTrue(result) is_root, root2 = call_async(master1.get_next_block_to_mine(acc2)) self.assertTrue(is_root) call_async(master1.add_root_block(root2)) self.assertEqual(master1.root_state.tip.height, 2) # reestablish cluster connection call_async( clusters[1].network.connect( "127.0.0.1", clusters[0].master.env.cluster_config.SIMPLE_NETWORK.BOOTSTRAP_PORT, ) ) # Expect cluster0's genesis change to genesis1 assert_true_with_timeout( lambda: clusters[0] .get_shard_state(1) .db.get_minor_block_by_height(0) .header.get_hash() == genesis1.header.get_hash() ) self.assertTrue(clusters[0].get_shard_state(1).root_tip == root2.header)
def test_shard_genesis_fork_fork(self): """ Test shard forks at genesis blocks due to root chain fork at GENESIS.ROOT_HEIGHT""" acc1 = Address.create_random_account(0) acc2 = Address.create_random_account(1) genesis_root_heights = {2: 0, 3: 1} with ClusterContext( 2, acc1, chain_size=1, shard_size=2, genesis_root_heights=genesis_root_heights, ) as clusters: # shutdown cluster connection clusters[1].peer.close() master0 = clusters[0].master root0 = call_async( master0.get_next_block_to_mine(acc1, branch_value=None)) call_async(master0.add_root_block(root0)) genesis0 = (clusters[0].get_shard_state( 2 | 1).db.get_minor_block_by_height(0)) self.assertEqual(genesis0.header.hash_prev_root_block, root0.header.get_hash()) master1 = clusters[1].master root1 = call_async( master1.get_next_block_to_mine(acc2, branch_value=None)) self.assertNotEqual(root0.header.get_hash(), root1.header.get_hash()) call_async(master1.add_root_block(root1)) genesis1 = (clusters[1].get_shard_state( 2 | 1).db.get_minor_block_by_height(0)) self.assertEqual(genesis1.header.hash_prev_root_block, root1.header.get_hash()) self.assertNotEqual(genesis0.header.get_hash(), genesis1.header.get_hash()) # let's make cluster1's root chain longer than cluster0's root2 = call_async( master1.get_next_block_to_mine(acc2, branch_value=None)) call_async(master1.add_root_block(root2)) self.assertEqual(master1.root_state.tip.height, 2) # reestablish cluster connection call_async(clusters[1].network.connect( "127.0.0.1", clusters[0].master.env.cluster_config.SIMPLE_NETWORK. BOOTSTRAP_PORT, )) # Expect cluster0's genesis change to genesis1 assert_true_with_timeout(lambda: clusters[0].get_shard_state( 2 | 1).db.get_minor_block_by_height(0).header.get_hash() == genesis1.header.get_hash()) self.assertTrue( clusters[0].get_shard_state(2 | 1).root_tip == root2.header)
def test_add_minor_block_request_list(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) with ClusterContext(2, acc1) as clusters: shard_state = clusters[0].slave_list[0].shards[Branch(0b10)].state coinbase_amount = ( shard_state.env.quark_chain_config.SHARD_LIST[ shard_state.shard_id ].COINBASE_AMOUNT // 2 ) b1 = shard_state.get_tip().create_block_to_append() evm_state = shard_state.run_block(b1) b1.finalize( evm_state=evm_state, coinbase_amount=evm_state.block_fee + coinbase_amount, ) add_result = call_async( clusters[0].master.add_raw_minor_block(b1.header.branch, b1.serialize()) ) self.assertTrue(add_result) # Make sure the xshard list is not broadcasted to the other shard self.assertFalse( clusters[0] .slave_list[1] .shards[Branch(0b11)] .state.contain_remote_minor_block_hash(b1.header.get_hash()) ) self.assertTrue( clusters[0].master.root_state.is_minor_block_validated( b1.header.get_hash() ) ) # Make sure another cluster received the new block assert_true_with_timeout( lambda: clusters[1] .slave_list[0] .shards[Branch(0b10)] .state.contain_block_by_hash(b1.header.get_hash()) ) assert_true_with_timeout( lambda: clusters[1].master.root_state.is_minor_block_validated( b1.header.get_hash() ) )
def test_add_transaction(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) acc2 = Address.create_from_identity(id1, full_shard_key=1) with ClusterContext(2, acc1) as clusters: master = clusters[0].master root = call_async( master.get_next_block_to_mine(acc1, branch_value=None)) call_async(master.add_root_block(root)) tx1 = create_transfer_transaction( shard_state=clusters[0].get_shard_state(0b10), key=id1.get_key(), from_address=acc1, to_address=acc1, value=12345, ) self.assertTrue(call_async(master.add_transaction(tx1))) self.assertEqual(len(clusters[0].get_shard_state(0b10).tx_queue), 1) branch1 = Branch(2 | 1) tx2 = create_transfer_transaction( shard_state=clusters[0].get_shard_state(0b11), key=id1.get_key(), from_address=acc2, to_address=acc1, value=12345, gas=30000, ) self.assertTrue(call_async(master.add_transaction(tx2))) self.assertEqual(len(clusters[0].get_shard_state(0b11).tx_queue), 1) # check the tx is received by the other cluster tx_queue = clusters[1].get_shard_state(0b10).tx_queue assert_true_with_timeout(lambda: len(tx_queue) == 1) self.assertEqual(tx_queue.pop_transaction(), tx1.tx.to_evm_tx()) tx_queue = clusters[1].get_shard_state(0b11).tx_queue assert_true_with_timeout(lambda: len(tx_queue) == 1) self.assertEqual(tx_queue.pop_transaction(), tx2.tx.to_evm_tx())
def test_add_transaction(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) acc2 = Address.create_from_identity(id1, full_shard_key=1) with ClusterContext(2, acc1) as clusters: master = clusters[0].master slaves = clusters[0].slave_list branch0 = Branch(2) tx1 = create_transfer_transaction( shard_state=slaves[0].shards[branch0].state, key=id1.get_key(), from_address=acc1, to_address=acc1, value=12345, ) self.assertTrue(call_async(master.add_transaction(tx1))) self.assertEqual(len(slaves[0].shards[branch0].state.tx_queue), 1) branch1 = Branch(2 | 1) tx2 = create_transfer_transaction( shard_state=slaves[1].shards[branch1].state, key=id1.get_key(), from_address=acc2, to_address=acc1, value=12345, gas=30000, ) self.assertTrue(call_async(master.add_transaction(tx2))) self.assertEqual(len(slaves[1].shards[branch1].state.tx_queue), 1) # check the tx is received by the other cluster tx_queue = clusters[1].slave_list[0].shards[branch0].state.tx_queue assert_true_with_timeout(lambda: len(tx_queue) == 1) self.assertEqual(tx_queue.pop_transaction(), tx1.code.get_evm_transaction()) tx_queue = clusters[1].slave_list[1].shards[branch1].state.tx_queue assert_true_with_timeout(lambda: len(tx_queue) == 1) self.assertEqual(tx_queue.pop_transaction(), tx2.code.get_evm_transaction())
def test_add_minor_block_request_list(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext(2, acc1) as clusters: shard_state = clusters[0].get_shard_state(0b10) b1 = _tip_gen(shard_state) add_result = call_async(clusters[0].master.add_raw_minor_block( b1.header.branch, b1.serialize())) self.assertTrue(add_result) # Make sure the xshard list is not broadcasted to the other shard self.assertFalse(clusters[0].get_shard_state( 0b11).contain_remote_minor_block_hash(b1.header.get_hash())) self.assertTrue( clusters[0].master.root_state.db.contain_minor_block_by_hash( b1.header.get_hash())) # Make sure another cluster received the new block assert_true_with_timeout(lambda: clusters[0].get_shard_state( 0b10).contain_block_by_hash(b1.header.get_hash())) assert_true_with_timeout( lambda: clusters[1].master.root_state.db. contain_minor_block_by_hash(b1.header.get_hash()))
def test_shard_synchronizer_with_fork(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext(2, acc1) as clusters: # shutdown cluster connection clusters[1].peer.close() block_list = [] # cluster 0 has 13 blocks added shard_state0 = clusters[0].get_shard_state(0b10) coinbase_amount = ( shard_state0.env.quark_chain_config.shards[ shard_state0.full_shard_id ].COINBASE_AMOUNT // 2 ) for i in range(13): block = shard_state0.get_tip().create_block_to_append() evm_state = shard_state0.run_block(block) block.finalize( evm_state=evm_state, coinbase_amount=evm_state.block_fee + coinbase_amount, ) add_result = call_async( clusters[0].master.add_raw_minor_block( block.header.branch, block.serialize() ) ) self.assertTrue(add_result) block_list.append(block) self.assertEqual(clusters[0].get_shard_state(0b10).header_tip.height, 13) # cluster 1 has 12 blocks added shard_state0 = clusters[1].get_shard_state(0b10) coinbase_amount = ( shard_state0.env.quark_chain_config.shards[ shard_state0.full_shard_id ].COINBASE_AMOUNT // 2 ) for i in range(12): block = shard_state0.get_tip().create_block_to_append() evm_state = shard_state0.run_block(block) block.finalize( evm_state=evm_state, coinbase_amount=evm_state.block_fee + coinbase_amount, ) add_result = call_async( clusters[1].master.add_raw_minor_block( block.header.branch, block.serialize() ) ) self.assertTrue(add_result) self.assertEqual(clusters[1].get_shard_state(0b10).header_tip.height, 12) # reestablish cluster connection call_async( clusters[1].network.connect( "127.0.0.1", clusters[0].master.env.cluster_config.SIMPLE_NETWORK.BOOTSTRAP_PORT, ) ) # a new block from cluster 0 will trigger sync in cluster 1 shard_state0 = clusters[0].get_shard_state(0b10) coinbase_amount = ( shard_state0.env.quark_chain_config.shards[ shard_state0.full_shard_id ].COINBASE_AMOUNT // 2 ) block = shard_state0.get_tip().create_block_to_append() evm_state = shard_state0.run_block(block) block.finalize( evm_state=evm_state, coinbase_amount=evm_state.block_fee + coinbase_amount, ) add_result = call_async( clusters[0].master.add_raw_minor_block( block.header.branch, block.serialize() ) ) self.assertTrue(add_result) block_list.append(block) # expect cluster 1 has all the blocks from cluter 0 and # has the same tip as cluster 0 for block in block_list: assert_true_with_timeout( lambda: clusters[1] .slave_list[0] .shards[Branch(0b10)] .state.contain_block_by_hash(block.header.get_hash()) ) assert_true_with_timeout( lambda: clusters[1].master.root_state.is_minor_block_validated( block.header.get_hash() ) ) self.assertEqual( clusters[1].get_shard_state(0b10).header_tip, clusters[0].get_shard_state(0b10).header_tip, )
def test_add_root_block_request_list(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext(2, acc1) as clusters: # shutdown cluster connection clusters[1].peer.close() # add blocks in cluster 0 block_header_list = [clusters[0].get_shard_state(2 | 0).header_tip] shard_state0 = clusters[0].get_shard_state(0b10) coinbase_amount = ( shard_state0.env.quark_chain_config.shards[ shard_state0.full_shard_id ].COINBASE_AMOUNT // 2 ) for i in range(7): b1 = shard_state0.get_tip().create_block_to_append() evm_state = shard_state0.run_block(b1) b1.finalize( evm_state=evm_state, coinbase_amount=evm_state.block_fee + coinbase_amount, ) add_result = call_async( clusters[0].master.add_raw_minor_block( b1.header.branch, b1.serialize() ) ) self.assertTrue(add_result) block_header_list.append(b1.header) block_header_list.append(clusters[0].get_shard_state(2 | 1).header_tip) shard_state0 = clusters[0].get_shard_state(0b11) coinbase_amount = ( shard_state0.env.quark_chain_config.shards[ shard_state0.full_shard_id ].COINBASE_AMOUNT // 2 ) b2 = shard_state0.get_tip().create_block_to_append() evm_state = shard_state0.run_block(b2) b2.finalize( evm_state=evm_state, coinbase_amount=evm_state.block_fee + coinbase_amount, ) add_result = call_async( clusters[0].master.add_raw_minor_block(b2.header.branch, b2.serialize()) ) self.assertTrue(add_result) block_header_list.append(b2.header) # add 1 block in cluster 1 shard_state1 = clusters[1].get_shard_state(0b11) coinbase_amount = ( shard_state1.env.quark_chain_config.shards[ shard_state1.full_shard_id ].COINBASE_AMOUNT // 2 ) b3 = shard_state1.get_tip().create_block_to_append() evm_state = shard_state1.run_block(b3) b3.finalize( evm_state=evm_state, coinbase_amount=evm_state.block_fee + coinbase_amount, ) add_result = call_async( clusters[1].master.add_raw_minor_block(b3.header.branch, b3.serialize()) ) self.assertTrue(add_result) self.assertEqual(clusters[1].get_shard_state(0b11).header_tip, b3.header) # reestablish cluster connection call_async( clusters[1].network.connect( "127.0.0.1", clusters[0].master.env.cluster_config.SIMPLE_NETWORK.BOOTSTRAP_PORT, ) ) root_block1 = clusters[0].master.root_state.create_block_to_mine( block_header_list, acc1 ) call_async(clusters[0].master.add_root_block(root_block1)) # Make sure the root block tip of local cluster is changed self.assertEqual(clusters[0].master.root_state.tip, root_block1.header) # Make sure the root block tip of cluster 1 is changed assert_true_with_timeout( lambda: clusters[1].master.root_state.tip == root_block1.header, 2 ) # Minor block is downloaded self.assertEqual(b1.header.height, 7) assert_true_with_timeout( lambda: clusters[1].get_shard_state(0b10).header_tip == b1.header ) # The tip is overwritten due to root chain first consensus assert_true_with_timeout( lambda: clusters[1].get_shard_state(0b11).header_tip == b2.header )
def test_get_root_block_headers_with_skip(self): """ Test the broadcast is only done to the neighbors """ id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext(2, acc1) as clusters: master = clusters[0].master # Add a root block first so that later minor blocks referring to this root # can be broadcasted to other shards root_block_header_list = [master.root_state.tip] for i in range(10): root_block = call_async( master.get_next_block_to_mine( Address.create_empty_account(), branch_value=None)) call_async(master.add_root_block(root_block)) root_block_header_list.append(root_block.header) self.assertEqual(root_block_header_list[-1].height, 10) assert_true_with_timeout( lambda: clusters[1].master.root_state.tip.height == 10) peer = clusters[1].peer # Test Case 1 ################################################### op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest. create_for_height( height=1, skip=1, limit=3, direction=Direction.TIP, ))) self.assertEqual(len(resp.block_header_list), 3) self.assertEqual(resp.block_header_list[0], root_block_header_list[1]) self.assertEqual(resp.block_header_list[1], root_block_header_list[3]) self.assertEqual(resp.block_header_list[2], root_block_header_list[5]) op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest.create_for_hash( hash=root_block_header_list[1].get_hash(), skip=1, limit=3, direction=Direction.TIP, ))) self.assertEqual(len(resp.block_header_list), 3) self.assertEqual(resp.block_header_list[0], root_block_header_list[1]) self.assertEqual(resp.block_header_list[1], root_block_header_list[3]) self.assertEqual(resp.block_header_list[2], root_block_header_list[5]) # Test Case 2 ################################################### op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest. create_for_height( height=2, skip=2, limit=4, direction=Direction.TIP, ))) self.assertEqual(len(resp.block_header_list), 3) self.assertEqual(resp.block_header_list[0], root_block_header_list[2]) self.assertEqual(resp.block_header_list[1], root_block_header_list[5]) self.assertEqual(resp.block_header_list[2], root_block_header_list[8]) op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest.create_for_hash( hash=root_block_header_list[2].get_hash(), skip=2, limit=4, direction=Direction.TIP, ))) self.assertEqual(len(resp.block_header_list), 3) self.assertEqual(resp.block_header_list[0], root_block_header_list[2]) self.assertEqual(resp.block_header_list[1], root_block_header_list[5]) self.assertEqual(resp.block_header_list[2], root_block_header_list[8]) # Test Case 3 ################################################### op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest. create_for_height( height=6, skip=0, limit=100, direction=Direction.TIP, ))) self.assertEqual(len(resp.block_header_list), 5) self.assertEqual(resp.block_header_list[0], root_block_header_list[6]) self.assertEqual(resp.block_header_list[1], root_block_header_list[7]) self.assertEqual(resp.block_header_list[2], root_block_header_list[8]) self.assertEqual(resp.block_header_list[3], root_block_header_list[9]) self.assertEqual(resp.block_header_list[4], root_block_header_list[10]) op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest.create_for_hash( hash=root_block_header_list[6].get_hash(), skip=0, limit=100, direction=Direction.TIP, ))) self.assertEqual(len(resp.block_header_list), 5) self.assertEqual(resp.block_header_list[0], root_block_header_list[6]) self.assertEqual(resp.block_header_list[1], root_block_header_list[7]) self.assertEqual(resp.block_header_list[2], root_block_header_list[8]) self.assertEqual(resp.block_header_list[3], root_block_header_list[9]) self.assertEqual(resp.block_header_list[4], root_block_header_list[10]) # Test Case 4 ################################################### op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest. create_for_height( height=2, skip=2, limit=4, direction=Direction.GENESIS, ))) self.assertEqual(len(resp.block_header_list), 1) self.assertEqual(resp.block_header_list[0], root_block_header_list[2]) op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest.create_for_hash( hash=root_block_header_list[2].get_hash(), skip=2, limit=4, direction=Direction.GENESIS, ))) self.assertEqual(len(resp.block_header_list), 1) self.assertEqual(resp.block_header_list[0], root_block_header_list[2]) # Test Case 5 ################################################### op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest. create_for_height( height=11, skip=2, limit=4, direction=Direction.GENESIS, ))) self.assertEqual(len(resp.block_header_list), 0) op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest.create_for_hash( hash=bytes(32), skip=2, limit=4, direction=Direction.GENESIS, ))) self.assertEqual(len(resp.block_header_list), 0) # Test Case 6 ################################################### op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest. create_for_height( height=8, skip=1, limit=5, direction=Direction.GENESIS, ))) self.assertEqual(len(resp.block_header_list), 5) self.assertEqual(resp.block_header_list[0], root_block_header_list[8]) self.assertEqual(resp.block_header_list[1], root_block_header_list[6]) self.assertEqual(resp.block_header_list[2], root_block_header_list[4]) self.assertEqual(resp.block_header_list[3], root_block_header_list[2]) self.assertEqual(resp.block_header_list[4], root_block_header_list[0]) op, resp, rpc_id = call_async( peer.write_rpc_request( op=CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, cmd=GetRootBlockHeaderListWithSkipRequest.create_for_hash( hash=root_block_header_list[8].get_hash(), skip=1, limit=5, direction=Direction.GENESIS, ))) self.assertEqual(len(resp.block_header_list), 5) self.assertEqual(resp.block_header_list[0], root_block_header_list[8]) self.assertEqual(resp.block_header_list[1], root_block_header_list[6]) self.assertEqual(resp.block_header_list[2], root_block_header_list[4]) self.assertEqual(resp.block_header_list[3], root_block_header_list[2]) self.assertEqual(resp.block_header_list[4], root_block_header_list[0])
def test_handle_get_minor_block_list_request_with_total_diff(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext(2, acc1) as clusters: cluster0_root_state = clusters[0].master.root_state cluster1_root_state = clusters[1].master.root_state coinbase = cluster1_root_state._calculate_root_block_coinbase([], 0) # Cluster 0 generates a root block of height 1 with 1e6 difficulty rb0 = cluster0_root_state.get_tip_block() rb1 = rb0.create_block_to_append( difficulty=int(1e6)).finalize(coinbase) # Establish cluster connection call_async(clusters[1].network.connect( "127.0.0.1", clusters[0].master.env.cluster_config.SIMPLE_NETWORK. BOOTSTRAP_PORT, )) # Cluster 0 broadcasts the root block to cluster 1 call_async(clusters[0].master.add_root_block(rb1)) self.assertEqual(cluster0_root_state.tip.get_hash(), rb1.header.get_hash()) # Make sure the root block tip of cluster 1 is changed assert_true_with_timeout( lambda: cluster1_root_state.tip == rb1.header, 2) # Cluster 1 generates a minor block and broadcasts to cluster 0 shard_state = clusters[1].get_shard_state(0b10) b1 = _tip_gen(shard_state) add_result = call_async(clusters[1].master.add_raw_minor_block( b1.header.branch, b1.serialize())) self.assertTrue(add_result) # Make sure another cluster received the new minor block assert_true_with_timeout(lambda: clusters[1].get_shard_state( 0b10).contain_block_by_hash(b1.header.get_hash())) assert_true_with_timeout( lambda: clusters[0].master.root_state.db. contain_minor_block_by_hash(b1.header.get_hash())) # Cluster 1 generates a new root block with higher total difficulty rb2 = rb0.create_block_to_append( difficulty=int(3e6)).finalize(coinbase) call_async(clusters[1].master.add_root_block(rb2)) self.assertEqual(cluster1_root_state.tip.get_hash(), rb2.header.get_hash()) # Generate a minor block b2 b2 = _tip_gen(shard_state) add_result = call_async(clusters[1].master.add_raw_minor_block( b2.header.branch, b2.serialize())) self.assertTrue(add_result) # Make sure another cluster received the new minor block assert_true_with_timeout(lambda: clusters[1].get_shard_state( 0b10).contain_block_by_hash(b2.header.get_hash())) assert_true_with_timeout( lambda: clusters[0].master.root_state.db. contain_minor_block_by_hash(b2.header.get_hash()))