def test_broadcast_cross_shard_transactions(self): """ Test the cross shard transactions are broadcasted to the destination shards """ id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) acc3 = Address.create_random_account(full_shard_key=1) with ClusterContext(1, acc1) as clusters: master = clusters[0].master slaves = clusters[0].slave_list # Add a root block first so that later minor blocks referring to this root # can be broadcasted to other shards is_root, root_block = call_async( master.get_next_block_to_mine( Address.create_empty_account(), prefer_root=True ) ) self.assertTrue(is_root) call_async(master.add_root_block(root_block)) tx1 = create_transfer_transaction( shard_state=clusters[0].get_shard_state(2 | 0), key=id1.get_key(), from_address=acc1, to_address=acc3, value=54321, gas=opcodes.GTXXSHARDCOST + opcodes.GTXCOST, ) self.assertTrue(slaves[0].add_tx(tx1)) b1 = clusters[0].get_shard_state(2 | 0).create_block_to_mine(address=acc1) b2 = clusters[0].get_shard_state(2 | 0).create_block_to_mine(address=acc1) b2.header.create_time += 1 self.assertNotEqual(b1.header.get_hash(), b2.header.get_hash()) call_async(clusters[0].get_shard(2 | 0).add_block(b1)) # expect shard 1 got the CrossShardTransactionList of b1 xshard_tx_list = ( clusters[0] .get_shard_state(2 | 1) .db.get_minor_block_xshard_tx_list(b1.header.get_hash()) ) self.assertEqual(len(xshard_tx_list.tx_list), 1) self.assertEqual(xshard_tx_list.tx_list[0].tx_hash, tx1.get_hash()) self.assertEqual(xshard_tx_list.tx_list[0].from_address, acc1) self.assertEqual(xshard_tx_list.tx_list[0].to_address, acc3) self.assertEqual(xshard_tx_list.tx_list[0].value, 54321) call_async(clusters[0].get_shard(2 | 0).add_block(b2)) # b2 doesn't update tip self.assertEqual(clusters[0].get_shard_state(2 | 0).header_tip, b1.header) # expect shard 1 got the CrossShardTransactionList of b2 xshard_tx_list = ( clusters[0] .get_shard_state(2 | 1) .db.get_minor_block_xshard_tx_list(b2.header.get_hash()) ) self.assertEqual(len(xshard_tx_list.tx_list), 1) self.assertEqual(xshard_tx_list.tx_list[0].tx_hash, tx1.get_hash()) self.assertEqual(xshard_tx_list.tx_list[0].from_address, acc1) self.assertEqual(xshard_tx_list.tx_list[0].to_address, acc3) self.assertEqual(xshard_tx_list.tx_list[0].value, 54321) b3 = ( clusters[0] .get_shard_state(2 | 1) .create_block_to_mine(address=acc1.address_in_shard(1)) ) call_async(master.add_raw_minor_block(b3.header.branch, b3.serialize())) is_root, root_block = call_async( master.get_next_block_to_mine(address=acc1) ) self.assertTrue(is_root) call_async(master.add_root_block(root_block)) # b4 should include the withdraw of tx1 b4 = ( clusters[0] .get_shard_state(2 | 1) .create_block_to_mine(address=acc1.address_in_shard(1)) ) # adding b1, b2, b3 again shouldn't affect b4 to be added later self.assertTrue( call_async(master.add_raw_minor_block(b1.header.branch, b1.serialize())) ) self.assertTrue( call_async(master.add_raw_minor_block(b2.header.branch, b2.serialize())) ) self.assertTrue( call_async(master.add_raw_minor_block(b3.header.branch, b3.serialize())) ) self.assertTrue( call_async(master.add_raw_minor_block(b4.header.branch, b4.serialize())) ) self.assertEqual( call_async(master.get_primary_account_data(acc3)).balance, 54321 )
def test_root_state_difficulty_and_coinbase(self): env = get_test_env() env.quark_chain_config.SKIP_ROOT_DIFFICULTY_CHECK = False env.quark_chain_config.ROOT.GENESIS.DIFFICULTY = 1000 diff_calc = EthDifficultyCalculator(cutoff=9, diff_factor=2048, minimum_diff=1) env.quark_chain_config.NETWORK_ID = ( 1) # other network ids will skip difficulty check env.quark_chain_config.REWARD_TAX_RATE = 0.8 env.quark_chain_config.ROOT.COINBASE_AMOUNT = 5 for c in env.quark_chain_config.shards.values(): c.COINBASE_AMOUNT = 5 r_state, s_states = create_default_state(env, diff_calc=diff_calc) s_state0 = s_states[2 | 0] s_state1 = s_states[2 | 1] g0 = s_state0.header_tip b0 = s_state0.get_tip().create_block_to_append() add_minor_block_to_cluster(s_states, b0) g1 = s_state1.header_tip b1 = s_state1.get_tip().create_block_to_append() add_minor_block_to_cluster(s_states, b1) self.assertEqual( b0.header.coinbase_amount_map.balance_map, {env.quark_chain_config.genesis_token: 1}, ) self.assertEqual( b1.header.coinbase_amount_map.balance_map, {env.quark_chain_config.genesis_token: 1}, ) r_state.add_validated_minor_block_hash( b0.header.get_hash(), b0.header.coinbase_amount_map.balance_map) r_state.add_validated_minor_block_hash( b1.header.get_hash(), b1.header.coinbase_amount_map.balance_map) # Test coinbase original_reward_tax_rate = env.quark_chain_config.REWARD_TAX_RATE for tax_rate in [0.8, 0.6, 0.9]: env.quark_chain_config.REWARD_TAX_RATE = tax_rate root_block_tmp = r_state.create_block_to_mine( m_header_list=[b0.header, b1.header], address=Address.create_empty_account(), create_time=r_state.tip.create_time + 9, ) self.assertEqual(root_block_tmp.header.signature, bytes(65)) # empty sig # still use minor block's coinbase amount, 1 self.assertEqual( root_block_tmp.header.coinbase_amount_map.balance_map[ env.quark_chain_config.genesis_token], round((1 + 1) / (1 - tax_rate) * tax_rate + 5), ) env.quark_chain_config.REWARD_TAX_RATE = original_reward_tax_rate # Check new difficulty root_block0 = r_state.create_block_to_mine( m_header_list=[b0.header, b1.header], address=Address.create_empty_account(), create_time=r_state.tip.create_time + 9, ) self.assertEqual(r_state.tip.difficulty, root_block0.header.difficulty) root_block0 = r_state.create_block_to_mine( m_header_list=[b0.header, b1.header], address=Address.create_empty_account(), create_time=r_state.tip.create_time + 3, ) self.assertEqual( r_state.tip.difficulty + r_state.tip.difficulty // 2048, root_block0.header.difficulty, ) root_block0 = r_state.create_block_to_mine( m_header_list=[g0, b0.header, g1, b1.header], address=Address.create_empty_account(), create_time=r_state.tip.create_time + 26, ) self.assertEqual( r_state.tip.difficulty - r_state.tip.difficulty // 2048, root_block0.header.difficulty, )
def create_minor_block( self, root_block: RootBlock, full_shard_id: int, evm_state: EvmState) -> Tuple[MinorBlock, TokenBalanceMap]: """ Create genesis block for shard. Genesis block's hash_prev_root_block is set to the genesis root block. Genesis state will be committed to the given evm_state. Based on ALLOC, genesis_token will be added to initial accounts. """ branch = Branch(full_shard_id) shard_config = self._qkc_config.shards[full_shard_id] genesis = shard_config.GENESIS for address_hex, alloc_data in genesis.ALLOC.items(): address = Address.create_from(bytes.fromhex(address_hex)) check( self._qkc_config.get_full_shard_id_by_full_shard_key( address.full_shard_key) == full_shard_id) evm_state.full_shard_key = address.full_shard_key recipient = address.recipient if "code" in alloc_data: code = decode_hex(remove_0x_head(alloc_data["code"])) evm_state.set_code(recipient, code) evm_state.set_nonce(recipient, 1) if "storage" in alloc_data: for k, v in alloc_data["storage"].items(): evm_state.set_storage_data( recipient, big_endian_to_int(decode_hex(k[2:])), big_endian_to_int(decode_hex(v[2:])), ) # backward compatible: # v1: {addr: {QKC: 1234}} # v2: {addr: {balances: {QKC: 1234}, code: 0x, storage: {0x12: 0x34}}} balances = alloc_data if "balances" in alloc_data: balances = alloc_data["balances"] for k, v in balances.items(): if k in ("code", "storage"): continue evm_state.delta_token_balance(recipient, token_id_encode(k), v) evm_state.commit() meta = MinorBlockMeta( hash_merkle_root=bytes.fromhex(genesis.HASH_MERKLE_ROOT), hash_evm_state_root=evm_state.trie.root_hash, xshard_tx_cursor_info=XshardTxCursorInfo(root_block.header.height, 0, 0), ) local_fee_rate = 1 - self._qkc_config.reward_tax_rate # type: Fraction coinbase_tokens = { self._qkc_config.genesis_token: shard_config.COINBASE_AMOUNT * local_fee_rate.numerator // local_fee_rate.denominator } coinbase_address = Address.create_empty_account(full_shard_id) header = MinorBlockHeader( version=genesis.VERSION, height=genesis.HEIGHT, branch=branch, hash_prev_minor_block=bytes.fromhex(genesis.HASH_PREV_MINOR_BLOCK), hash_prev_root_block=root_block.header.get_hash(), evm_gas_limit=genesis.GAS_LIMIT, hash_meta=sha3_256(meta.serialize()), coinbase_amount_map=TokenBalanceMap(coinbase_tokens), coinbase_address=coinbase_address, create_time=genesis.TIMESTAMP, difficulty=genesis.DIFFICULTY, extra_data=bytes.fromhex(genesis.EXTRA_DATA), ) return ( MinorBlock(header=header, meta=meta, tx_list=[]), TokenBalanceMap(coinbase_tokens), )
def create_minor_block( self, root_block: RootBlock, full_shard_id: int, evm_state: EvmState ) -> MinorBlock: """ Create genesis block for shard. Genesis block's hash_prev_root_block is set to the genesis root block. Genesis state will be committed to the given evm_state. Based on ALLOC, genesis_token will be added to initial accounts. """ branch = Branch(full_shard_id) shard_config = self._qkc_config.shards[full_shard_id] genesis = shard_config.GENESIS for address_hex, alloc_amount in genesis.ALLOC.items(): address = Address.create_from(bytes.fromhex(address_hex)) check( self._qkc_config.get_full_shard_id_by_full_shard_key( address.full_shard_key ) == full_shard_id ) evm_state.full_shard_key = address.full_shard_key if isinstance(alloc_amount, dict): for k, v in alloc_amount.items(): evm_state.delta_token_balance( address.recipient, token_id_encode(k), v ) else: evm_state.delta_token_balance( address.recipient, self._qkc_config.genesis_token, alloc_amount ) evm_state.commit() meta = MinorBlockMeta( hash_merkle_root=bytes.fromhex(genesis.HASH_MERKLE_ROOT), hash_evm_state_root=evm_state.trie.root_hash, ) local_fee_rate = 1 - self._qkc_config.reward_tax_rate # type: Fraction coinbase_tokens = { self._qkc_config.genesis_token: shard_config.COINBASE_AMOUNT * local_fee_rate.numerator // local_fee_rate.denominator } coinbase_address = Address.create_empty_account(full_shard_id) header = MinorBlockHeader( version=genesis.VERSION, height=genesis.HEIGHT, branch=branch, hash_prev_minor_block=bytes.fromhex(genesis.HASH_PREV_MINOR_BLOCK), hash_prev_root_block=root_block.header.get_hash(), evm_gas_limit=genesis.GAS_LIMIT, hash_meta=sha3_256(meta.serialize()), coinbase_amount_map=TokenBalanceMap(coinbase_tokens), coinbase_address=coinbase_address, create_time=genesis.TIMESTAMP, difficulty=genesis.DIFFICULTY, extra_data=bytes.fromhex(genesis.EXTRA_DATA), ) return ( MinorBlock(header=header, meta=meta, tx_list=[]), TokenBalanceMap(coinbase_tokens), )
def test_shard_state_add_root_block(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) env0 = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) env1 = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state0 = create_default_shard_state(env=env0, shard_id=0) state1 = create_default_shard_state(env=env1, shard_id=1) # Add one block and prepare a fork b0 = state0.get_tip().create_block_to_append(address=acc1) b2 = state0.get_tip().create_block_to_append( address=Address.create_empty_account()) state0.finalize_and_add_block(b0) state0.finalize_and_add_block(b2) b1 = state1.get_tip().create_block_to_append() b1.finalize(evm_state=state1.run_block(b1)) # Create a root block containing the block with the x-shard tx state0.add_cross_shard_tx_list_by_minor_block_hash( h=b1.header.get_hash(), tx_list=CrossShardTransactionList(tx_list=[])) root_block = ( state0.root_tip.create_block_to_append().add_minor_block_header( b0.header).add_minor_block_header(b1.header).finalize()) root_block1 = ( state0.root_tip.create_block_to_append().add_minor_block_header( b2.header).add_minor_block_header(b1.header).finalize()) state0.add_root_block(root_block) b00 = b0.create_block_to_append() state0.finalize_and_add_block(b00) self.assertEqual(state0.header_tip, b00.header) # Create another fork that is much longer (however not confirmed by root_block) b3 = b2.create_block_to_append() state0.finalize_and_add_block(b3) b4 = b3.create_block_to_append() state0.finalize_and_add_block(b4) self.assertEqual(state0.header_tip, b00.header) self.assertEqual(state0.db.get_minor_block_by_height(2), b00) self.assertIsNone(state0.db.get_minor_block_by_height(3)) b5 = b1.create_block_to_append() state0.add_cross_shard_tx_list_by_minor_block_hash( h=b5.header.get_hash(), tx_list=CrossShardTransactionList(tx_list=[])) root_block2 = ( root_block1.create_block_to_append().add_minor_block_header( b3.header).add_minor_block_header( b4.header).add_minor_block_header(b5.header).finalize()) self.assertFalse(state0.add_root_block(root_block1)) self.assertTrue(state0.add_root_block(root_block2)) self.assertEqual(state0.header_tip, b4.header) self.assertEqual(state0.meta_tip, b4.meta) self.assertEqual(state0.root_tip, root_block2.header) self.assertEqual(state0.db.get_minor_block_by_height(2), b3) self.assertEqual(state0.db.get_minor_block_by_height(3), b4)
def get_test_env( genesis_account=Address.create_empty_account(), genesis_minor_quarkash=0, chain_size=2, shard_size=2, genesis_root_heights=None, # dict(full_shard_id, genesis_root_height) remote_mining=False, genesis_minor_token_balances=None, ): check(is_p2(shard_size)) env = DEFAULT_ENV.copy() env.db = InMemoryDb() env.set_network_id(1234567890) env.cluster_config = ClusterConfig() env.quark_chain_config.update(chain_size, shard_size, 10, 1, env.quark_chain_config.GENESIS_TOKEN) env.quark_chain_config.MIN_TX_POOL_GAS_PRICE = 0 env.quark_chain_config.MIN_MINING_GAS_PRICE = 0 if remote_mining: env.quark_chain_config.ROOT.CONSENSUS_CONFIG.REMOTE_MINE = True env.quark_chain_config.ROOT.CONSENSUS_TYPE = ConsensusType.POW_DOUBLESHA256 env.quark_chain_config.ROOT.GENESIS.DIFFICULTY = 10 env.quark_chain_config.ROOT.DIFFICULTY_ADJUSTMENT_CUTOFF_TIME = 40 env.quark_chain_config.ROOT.DIFFICULTY_ADJUSTMENT_FACTOR = 1024 if genesis_root_heights: check(len(genesis_root_heights) == shard_size * chain_size) for chain_id in range(chain_size): for shard_id in range(shard_size): full_shard_id = chain_id << 16 | shard_size | shard_id shard = env.quark_chain_config.shards[full_shard_id] shard.GENESIS.ROOT_HEIGHT = genesis_root_heights[full_shard_id] # fund genesis account in all shards for full_shard_id, shard in env.quark_chain_config.shards.items(): addr = genesis_account.address_in_shard( full_shard_id).serialize().hex() if genesis_minor_token_balances is not None: shard.GENESIS.ALLOC[addr] = genesis_minor_token_balances else: shard.GENESIS.ALLOC[addr] = { env.quark_chain_config.GENESIS_TOKEN: genesis_minor_quarkash } shard.CONSENSUS_CONFIG.REMOTE_MINE = remote_mining shard.DIFFICULTY_ADJUSTMENT_CUTOFF_TIME = 7 shard.DIFFICULTY_ADJUSTMENT_FACTOR = 512 if remote_mining: shard.CONSENSUS_TYPE = ConsensusType.POW_DOUBLESHA256 shard.GENESIS.DIFFICULTY = 10 shard.POSW_CONFIG.WINDOW_SIZE = 2 env.quark_chain_config.SKIP_MINOR_DIFFICULTY_CHECK = True env.quark_chain_config.SKIP_ROOT_DIFFICULTY_CHECK = True env.cluster_config.ENABLE_TRANSACTION_HISTORY = True env.cluster_config.DB_PATH_ROOT = "" check(env.cluster_config.use_mem_db()) return env