def test_xshard_tx_sent(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_from_identity(id1, full_shard_id=1) acc3 = Address.create_random_account(full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state = create_default_shard_state(env=env, shard_id=0) env1 = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state1 = create_default_shard_state(env=env1, shard_id=1) # Add a root block to update block gas limit so that xshard tx can be included root_block = ( state.root_tip.create_block_to_append().add_minor_block_header( state.header_tip).add_minor_block_header( state1.header_tip).finalize()) state.add_root_block(root_block) tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=888888, gas=opcodes.GTXXSHARDCOST + opcodes.GTXCOST, ) state.add_tx(tx) b1 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b1.tx_list), 1) self.assertEqual(state.evm_state.gas_used, 0) # Should succeed state.finalize_and_add_block(b1) self.assertEqual(len(state.evm_state.xshard_list), 1) self.assertEqual( state.evm_state.xshard_list[0], CrossShardTransactionDeposit( tx_hash=tx.get_hash(), from_address=acc1, to_address=acc2, value=888888, gas_price=1, ), ) self.assertEqual( state.get_balance(id1.recipient), 10000000 - 888888 - opcodes.GTXCOST - opcodes.GTXXSHARDCOST, ) # Make sure the xshard gas is not used by local block self.assertEqual(state.evm_state.gas_used, opcodes.GTXCOST + opcodes.GTXXSHARDCOST) # GTXXSHARDCOST is consumed by remote shard self.assertEqual(state.get_balance(acc3.recipient), opcodes.GTXCOST // 2)
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_getTransactionReceipt_on_x_shard_transfer(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_from_identity(id1, full_shard_id=1) with ClusterContext(1, acc1) as clusters, jrpc_server_context( clusters[0].master ): master = clusters[0].master slaves = clusters[0].slave_list is_root, block = call_async(master.get_next_block_to_mine(address=acc2)) self.assertTrue(is_root) call_async(master.add_root_block(block)) s1, s2 = clusters[0].get_shard_state(0), clusters[0].get_shard_state(1) tx_gen = lambda s, f, t: create_transfer_transaction( shard_state=s, key=id1.get_key(), from_address=f, to_address=t, gas=21000 if f == t else 30000, value=12345, ) self.assertTrue(slaves[0].add_tx(tx_gen(s1, acc1, acc2))) _, b1 = call_async(master.get_next_block_to_mine(address=acc1)) self.assertTrue(call_async(clusters[0].get_shard(0).add_block(b1))) _, b2 = call_async(master.get_next_block_to_mine(address=acc2)) self.assertTrue(call_async(clusters[0].get_shard(1).add_block(b2))) _, root_block = call_async( master.get_next_block_to_mine(address=acc1, prefer_root=True) ) call_async(master.add_root_block(root_block)) tx = tx_gen(s2, acc2, acc2) self.assertTrue(slaves[1].add_tx(tx)) _, b3 = call_async(master.get_next_block_to_mine(address=acc2)) self.assertTrue(call_async(clusters[0].get_shard(1).add_block(b3))) # in-shard tx 21000 + receiving x-shard tx 9000 self.assertEqual(s2.evm_state.gas_used, 30000) self.assertEqual(s2.evm_state.xshard_receive_gas_used, 9000) for endpoint in ("getTransactionReceipt", "eth_getTransactionReceipt"): resp = send_request( endpoint, "0x" + tx.get_hash().hex() + acc2.full_shard_id.to_bytes(4, "big").hex(), ) self.assertEqual(resp["transactionHash"], "0x" + tx.get_hash().hex()) self.assertEqual(resp["status"], "0x1") self.assertEqual(resp["cumulativeGasUsed"], hex(30000)) self.assertEqual(resp["gasUsed"], hex(21000)) self.assertIsNone(resp["contractAddress"])
def test_getWork_and_submitWork(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext( 1, acc1, remote_mining=True, shard_size=1, small_coinbase=True ) as clusters, jrpc_server_context(clusters[0].master): master = clusters[0].master slaves = clusters[0].slave_list branch = Branch.create(1, 0) tx = create_transfer_transaction( shard_state=slaves[0].shards[branch].state, key=id1.get_key(), from_address=acc1, to_address=acc1, value=0, gas_price=12, ) self.assertTrue(slaves[0].add_tx(tx)) for shard_id in ["0x0", None]: # shard, then root resp = send_request("getWork", shard_id) self.assertEqual(resp[1:], ["0x1", "0xa"]) # height and diff header_hash_hex = resp[0] if shard_id is not None: # shard 0 miner_address = Address.create_from( master.env.quark_chain_config.shards[1].COINBASE_ADDRESS ) else: # root miner_address = Address.create_from( master.env.quark_chain_config.ROOT.COINBASE_ADDRESS ) _, block = call_async( master.get_next_block_to_mine( address=miner_address, prefer_root=shard_id is None ) ) self.assertEqual( header_hash_hex[2:], block.header.get_hash_for_mining().hex() ) # solve it and submit work = MiningWork(bytes.fromhex(resp[0][2:]), 1, 10) solver = DoubleSHA256(work) nonce = solver.mine(0, 10000).nonce mixhash = "0x" + sha3_256(b"").hex() resp = send_request( "submitWork", shard_id, header_hash_hex, hex(nonce), mixhash ) self.assertTrue(resp) # show progress on shard 0 self.assertEqual( clusters[0].get_shard_state(1 | 0).get_tip().header.height, 1 )
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_native_token_gas(self): """in-shard transfer QETH using native token as gas """ QETH = token_id_encode("QETH") id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) acc2 = Address.create_random_account(full_shard_key=0) acc3 = Address.create_random_account(full_shard_key=0) env = get_test_env( genesis_account=acc1, genesis_minor_token_balances={"QETH": 10000000} ) state = create_default_shard_state(env=env) tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=12345, gas=21000, gas_token_id=QETH, transfer_token_id=QETH, ) self.assertTrue(state.add_tx(tx)) b1 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b1.tx_list), 1) state.finalize_and_add_block(b1) self.assertEqual(state.header_tip, b1.header) self.assertEqual( state.get_token_balance(acc1.recipient, QETH), 10000000 - opcodes.GTXCOST - 12345, ) self.assertEqual(state.get_token_balance(acc2.recipient, QETH), 12345) # tx fee self.assertEqual( state.get_token_balance(acc3.recipient, QETH), self.get_after_tax_reward(opcodes.GTXCOST), ) # miner coinbase self.assertEqual( state.get_token_balance(acc3.recipient, self.genesis_token), self.get_after_tax_reward(self.shard_coinbase), ) tx_list, _ = state.db.get_transactions_by_address(acc1) self.assertEqual(tx_list[0].value, 12345) self.assertEqual(tx_list[0].gas_token_id, QETH) self.assertEqual(tx_list[0].transfer_token_id, QETH) tx_list, _ = state.db.get_transactions_by_address(acc2) self.assertEqual(tx_list[0].value, 12345) self.assertEqual(tx_list[0].gas_token_id, QETH) self.assertEqual(tx_list[0].transfer_token_id, QETH)
def test_exceeding_xshard_limit(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_random_account(full_shard_id=1) acc3 = Address.create_random_account(full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) # a huge number to make xshard tx limit become 0 so that no xshard tx can be # included in the block env.quark_chain_config.MAX_NEIGHBORS = 10**18 state = create_default_shard_state(env=env) # xshard tx tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=12345, gas=50000, ) self.assertTrue(state.add_tx(tx)) b1 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b1.tx_list), 0) # inshard tx tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc3, value=12345, gas=50000, ) self.assertTrue(state.add_tx(tx)) b1 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b1.tx_list), 1)
def test_duplicated_tx(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_random_account(full_shard_id=0) acc3 = Address.create_random_account(full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state = create_default_shard_state(env=env) tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=12345, ) self.assertTrue(state.add_tx(tx)) self.assertFalse(state.add_tx(tx)) # already in tx_queue self.assertEqual(len(state.tx_queue), 1) self.assertEqual(len(state.tx_dict), 1) block, i = state.get_transaction_by_hash(tx.get_hash()) self.assertEqual(len(block.tx_list), 1) self.assertEqual(block.tx_list[0], tx) self.assertEqual(block.header.create_time, 0) self.assertEqual(i, 0) b1 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b1.tx_list), 1) # Should succeed state.finalize_and_add_block(b1) self.assertEqual(state.header_tip, b1.header) self.assertEqual(state.get_balance(id1.recipient), 10000000 - opcodes.GTXCOST - 12345) self.assertEqual(state.get_balance(acc2.recipient), 12345) self.assertEqual(state.get_balance(acc3.recipient), opcodes.GTXCOST // 2) # Check receipts self.assertEqual(len(state.evm_state.receipts), 1) self.assertEqual(state.evm_state.receipts[0].state_root, b"\x01") self.assertEqual(state.evm_state.receipts[0].gas_used, 21000) block, i = state.get_transaction_by_hash(tx.get_hash()) self.assertEqual(block, b1) self.assertEqual(i, 0) # tx already confirmed self.assertTrue(state.db.contain_transaction_hash(tx.get_hash())) self.assertFalse(state.add_tx(tx))
def test_getTransactionCount(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) acc2 = Address.create_random_account(full_shard_key=1) with ClusterContext( 1, acc1, small_coinbase=True ) as clusters, jrpc_server_context(clusters[0].master): master = clusters[0].master slaves = clusters[0].slave_list self.assertEqual( call_async(master.get_primary_account_data(acc1)).transaction_count, 0 ) for i in range(3): tx = create_transfer_transaction( shard_state=clusters[0].get_shard_state(2 | 0), key=id1.get_key(), from_address=acc1, to_address=acc1, value=12345, ) self.assertTrue(slaves[0].add_tx(tx)) block = call_async( master.get_next_block_to_mine(address=acc1, branch_value=0b10) ) self.assertEqual(i + 1, block.header.height) self.assertTrue( call_async(clusters[0].get_shard(2 | 0).add_block(block)) ) response = send_request( "getTransactionCount", "0x" + acc2.serialize().hex() ) self.assertEqual(response, "0x0") response = send_request( "getTransactionCount", "0x" + acc1.serialize().hex() ) self.assertEqual(response, "0x3") response = send_request( "getTransactionCount", "0x" + acc1.serialize().hex(), "latest" ) self.assertEqual(response, "0x3") for i in range(3): response = send_request( "getTransactionCount", "0x" + acc1.serialize().hex(), hex(i + 1) ) self.assertEqual(response, hex(i + 1))
def test_native_token_transfer_0_value_success(self): """to prevent storage spamming, do not delta_token_balance does not take action if value is 0 """ MALICIOUS0 = token_id_encode("MALICIOUS0") id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) acc3 = Address.create_random_account(full_shard_key=0) env = get_test_env( genesis_account=acc1, genesis_minor_token_balances={ self.GENESIS_TOKEN: 10000000, "MALICIOUS0": 0, }, ) state = create_default_shard_state(env=env) tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc1, value=0, gas=opcodes.GTXCOST, gas_token_id=self.genesis_token, transfer_token_id=MALICIOUS0, ) self.assertTrue(state.add_tx(tx)) b1 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b1.tx_list), 1) state.finalize_and_add_block(b1) self.assertEqual(state.header_tip, b1.header) self.assertEqual( state.get_token_balance(id1.recipient, self.genesis_token), 10000000 - opcodes.GTXCOST, ) self.assertEqual(state.get_token_balance(acc1.recipient, MALICIOUS0), 0) # MALICIOUS0 shall not be in the dict self.assertNotEqual( state.get_balances(acc1.recipient), { self.genesis_token: 10000000 - opcodes.GTXCOST, MALICIOUS0: 0 }, ) self.assertEqual( state.get_balances(acc1.recipient), {self.genesis_token: 10000000 - opcodes.GTXCOST}, )
def setUp(self): super().setUp() id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state = create_default_shard_state(env=env) tx = create_contract_creation_with_event_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_full_shard_id=acc1.full_shard_id, ) self.assertTrue(state.add_tx(tx)) b = state.create_block_to_mine(address=acc1, gas_limit=1000000) hit_block = b # will be used later state.finalize_and_add_block(b) start_height = b.header.height # https://hastebin.com/debezaqocu.cs # 1 log with 2 topics - sha3(b'Hi(address)') and msg.sender log = Log.create_from_eth_log(state.evm_state.receipts[0].logs[0], b, 0, 0) # add other random blocks with normal tx for _ in range(10): tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=Address.create_from_identity( Identity.create_random_identity(), full_shard_id=0), value=1, gas=40000, ) self.assertTrue(state.add_tx(tx)) b = state.create_block_to_mine(address=acc1, gas_limit=100000) state.finalize_and_add_block(b) self.assertEqual(b.header.height, start_height + 10) self.hit_block = hit_block self.log = log self.state = state self.start_height = start_height def filter_gen_with_criteria(criteria, addresses=None): return Filter(state.db, addresses or [], criteria, start_height, start_height + 10) self.filter_gen_with_criteria = filter_gen_with_criteria
def test_disallowed_unknown_token(self): """do not allow tx with unknown token id """ MALICIOUS0 = token_id_encode("MALICIOUS0") MALICIOUS1 = token_id_encode("MALICIOUS1") id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) acc3 = Address.create_random_account(full_shard_key=0) env = get_test_env( genesis_account=acc1, genesis_minor_token_balances={self.GENESIS_TOKEN: 10000000}, ) state = create_default_shard_state(env=env) tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc1, value=0, gas=opcodes.GTXCOST, gas_token_id=self.genesis_token, transfer_token_id=MALICIOUS0, ) self.assertFalse(state.add_tx(tx)) tx1 = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc1, value=0, gas=opcodes.GTXCOST, gas_token_id=MALICIOUS1, transfer_token_id=self.genesis_token, ) self.assertFalse(state.add_tx(tx1))
def test_revert_fork_put_tx_back_to_queue(self): """Tx in the reverted chain should be put back to the queue""" id1 = Identity.create_random_identity() id2 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_from_identity(id2, full_shard_id=0) acc3 = Address.create_random_account(full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=2000000 + opcodes.GTXCOST) state = create_default_shard_state(env=env) state.add_tx( create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=1000000, )) b0 = state.create_block_to_mine(address=acc3) b1 = state.create_block_to_mine(address=acc3) state.finalize_and_add_block(b0) self.assertEqual(len(state.tx_queue), 0) b1.tx_list = [] # make b1 empty state.finalize_and_add_block(b1) self.assertEqual(len(state.tx_queue), 0) b2 = b1.create_block_to_append() state.finalize_and_add_block(b2) # now b1-b2 becomes the best chain and we expect b0 to be reverted and put the tx back to queue self.assertEqual(len(state.tx_queue), 1) b3 = b0.create_block_to_append() state.finalize_and_add_block(b3) self.assertEqual(len(state.tx_queue), 1) b4 = b3.create_block_to_append() state.finalize_and_add_block(b4) # b0-b3-b4 becomes the best chain self.assertEqual(len(state.tx_queue), 0)
def test_add_tx_incorrect_from_shard_id(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=1) acc2 = Address.create_random_account(full_shard_id=1) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state = create_default_shard_state(env=env) # state is shard 0 but tx from shard 1 tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=12345, ) self.assertFalse(state.add_tx(tx)) self.assertIsNone(state.execute_tx(tx, acc1))
def test_add_invalid_tx_fail(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_random_account(full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state = create_default_shard_state(env=env) tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=999999999999999999999, # insane ) self.assertFalse(state.add_tx(tx)) self.assertEqual(len(state.tx_queue), 0)
def test_execute_tx(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_random_account(full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state = create_default_shard_state(env=env) tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=12345, ) # adding this line to make sure `execute_tx` would reset `gas_used` state.evm_state.gas_used = state.evm_state.gas_limit res = state.execute_tx(tx, acc1) self.assertEqual(res, b"")
def test_get_primary_account_data(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) acc2 = Address.create_random_account(full_shard_key=1) with ClusterContext(1, acc1) as clusters: master = clusters[0].master slaves = clusters[0].slave_list branch = Branch(2) self.assertEqual( call_async( master.get_primary_account_data(acc1)).transaction_count, 0) tx = create_transfer_transaction( shard_state=slaves[0].shards[branch].state, key=id1.get_key(), from_address=acc1, to_address=acc1, value=12345, ) self.assertTrue(slaves[0].add_tx(tx)) is_root, root = call_async( master.get_next_block_to_mine(address=acc1, prefer_root=True)) self.assertTrue(is_root) call_async(master.add_root_block(root)) is_root, block1 = call_async( master.get_next_block_to_mine(address=acc1)) self.assertFalse(is_root) self.assertTrue( call_async( master.add_raw_minor_block(block1.header.branch, block1.serialize()))) self.assertEqual( call_async( master.get_primary_account_data(acc1)).transaction_count, 1) self.assertEqual( call_async( master.get_primary_account_data(acc2)).transaction_count, 0)
def test_getWork_and_submitWork(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) with ClusterContext(1, acc1, remote_mining=True, shard_size=1) as clusters, jrpc_server_context( clusters[0].master): master = clusters[0].master slaves = clusters[0].slave_list branch = Branch.create(1, 0) tx = create_transfer_transaction( shard_state=slaves[0].shards[branch].state, key=id1.get_key(), from_address=acc1, to_address=acc1, value=0, gas_price=12, ) self.assertTrue(slaves[0].add_tx(tx)) for shard_id in ["0x0", None]: # shard, then root resp = send_request("getWork", shard_id) self.assertEqual(resp[1:], ["0x1", "0xa"]) # height and diff header_hash_hex = resp[0] _, block = call_async( master.get_next_block_to_mine(address=acc1)) self.assertEqual(header_hash_hex[2:], block.header.get_hash_for_mining().hex()) # solve it and submit solver = DoubleSHA256(block) mined = solver.mine(0, 1000) self.assertTrue(mined) nonce_found = "0x" + solver.nonce_found.hex() mixhash = "0x" + sha3_256(b"").hex() resp = send_request("submitWork", shard_id, header_hash_hex, nonce_found, mixhash) # FIXME: also verify root chain block addition after fixing https://github.com/QuarkChain/pyquarkchain/issues/130 if shard_id is not None: self.assertTrue(resp)
def test_estimate_gas(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_random_account(full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state = create_default_shard_state(env=env) tx_gen = lambda data: create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=12345, data=data, ) tx = tx_gen(b"") estimate = state.estimate_gas(tx, acc1) self.assertEqual(estimate, 21000) tx = tx_gen(b"12123478123412348125936583475758") estimate = state.estimate_gas(tx, acc1) self.assertEqual(estimate, 23176)
def test_gasPrice(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext( 1, acc1, small_coinbase=True) as clusters, jrpc_server_context( clusters[0].master): master = clusters[0].master slaves = clusters[0].slave_list # run for multiple times for _ in range(3): tx = create_transfer_transaction( shard_state=clusters[0].get_shard_state(2 | 0), key=id1.get_key(), from_address=acc1, to_address=acc1, value=0, gas_price=12, ) self.assertTrue(slaves[0].add_tx(tx)) block = call_async( master.get_next_block_to_mine(address=acc1, branch_value=0b10)) self.assertTrue( call_async(clusters[0].get_shard(2 | 0).add_block(block))) for using_eth_endpoint in (True, False): if using_eth_endpoint: resp = send_request("eth_gasPrice", ["0x0"]) else: resp = send_request( "gasPrice", ["0x0", quantity_encoder(token_id_encode("QKC"))]) self.assertEqual(resp, "0xc")
def test_getTransactionReceipt_on_transfer(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext( 1, acc1, small_coinbase=True) as clusters, jrpc_server_context( clusters[0].master): master = clusters[0].master slaves = clusters[0].slave_list tx = create_transfer_transaction( shard_state=clusters[0].get_shard_state(2 | 0), key=id1.get_key(), from_address=acc1, to_address=acc1, value=12345, ) self.assertTrue(slaves[0].add_tx(tx)) block1 = call_async( master.get_next_block_to_mine(address=acc1, branch_value=0b10)) self.assertTrue( call_async(clusters[0].get_shard(2 | 0).add_block(block1))) for endpoint in ("getTransactionReceipt", "eth_getTransactionReceipt"): resp = send_request( endpoint, [ "0x" + tx.get_hash().hex() + acc1.full_shard_key.to_bytes(4, "big").hex() ], ) self.assertEqual(resp["transactionHash"], "0x" + tx.get_hash().hex()) self.assertEqual(resp["status"], "0x1") self.assertEqual(resp["cumulativeGasUsed"], "0x5208") self.assertIsNone(resp["contractAddress"])
def test_xshard_tx_insufficient_gas(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_from_identity(id1, full_shard_id=1) acc3 = Address.create_random_account(full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state = create_default_shard_state(env=env, shard_id=0) state.add_tx( create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=888888, gas=opcodes.GTXCOST, )) b1 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b1.tx_list), 0) self.assertEqual(len(state.tx_queue), 0)
def test_getTransactionById(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext( 1, acc1, small_coinbase=True) as clusters, jrpc_server_context( clusters[0].master): master = clusters[0].master slaves = clusters[0].slave_list self.assertEqual( call_async( master.get_primary_account_data(acc1)).transaction_count, 0) tx = create_transfer_transaction( shard_state=clusters[0].get_shard_state(2 | 0), key=id1.get_key(), from_address=acc1, to_address=acc1, value=12345, ) self.assertTrue(slaves[0].add_tx(tx)) block1 = call_async( master.get_next_block_to_mine(address=acc1, branch_value=0b10)) self.assertTrue( call_async(clusters[0].get_shard(2 | 0).add_block(block1))) resp = send_request( "getTransactionById", [ "0x" + tx.get_hash().hex() + acc1.full_shard_key.to_bytes(4, "big").hex() ], ) self.assertEqual(resp["hash"], "0x" + tx.get_hash().hex())
def test_fork_does_not_confirm_tx(self): """Tx should only be confirmed and removed from tx queue by the best chain""" id1 = Identity.create_random_identity() id2 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc2 = Address.create_from_identity(id2, full_shard_id=0) acc3 = Address.create_random_account(full_shard_id=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=2000000 + opcodes.GTXCOST) state = create_default_shard_state(env=env) state.add_tx( create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=1000000, )) b0 = state.create_block_to_mine(address=acc3) b1 = state.create_block_to_mine(address=acc3) b0.tx_list = [] # make b0 empty state.finalize_and_add_block(b0) self.assertEqual(len(state.tx_queue), 1) self.assertEqual(len(b1.tx_list), 1) state.finalize_and_add_block(b1) # b1 is a fork and does not remove the tx from queue self.assertEqual(len(state.tx_queue), 1) b2 = state.create_block_to_mine(address=acc3) state.finalize_and_add_block(b2) self.assertEqual(len(state.tx_queue), 0)
def test_getRootblockConfirmationIdAndCount(self): # TODO test root chain forks id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext( 1, acc1, small_coinbase=True) as clusters, jrpc_server_context( clusters[0].master): master = clusters[0].master slaves = clusters[0].slave_list self.assertEqual( call_async( master.get_primary_account_data(acc1)).transaction_count, 0) block = call_async( master.get_next_block_to_mine(address=acc1, branch_value=None)) call_async(master.add_root_block(block)) tx = create_transfer_transaction( shard_state=clusters[0].get_shard_state(2 | 0), key=id1.get_key(), from_address=acc1, to_address=acc1, value=12345, ) self.assertTrue(slaves[0].add_tx(tx)) block1 = call_async( master.get_next_block_to_mine(address=acc1, branch_value=0b10)) self.assertTrue( call_async(clusters[0].get_shard(2 | 0).add_block(block1))) tx_id = ("0x" + tx.get_hash().hex() + acc1.full_shard_key.to_bytes(4, "big").hex()) resp = send_request("getTransactionById", [tx_id]) self.assertEqual(resp["hash"], "0x" + tx.get_hash().hex()) self.assertEqual( resp["blockId"], "0x" + block1.header.get_hash().hex() + block1.header.branch.get_full_shard_id().to_bytes( 4, byteorder="big").hex(), ) minor_hash = resp["blockId"] # zero root block confirmation resp_hash = send_request("getRootHashConfirmingMinorBlockById", [minor_hash]) self.assertIsNone( resp_hash, "should return None for unconfirmed minor blocks") resp_count = send_request( "getTransactionConfirmedByNumberRootBlocks", [tx_id]) self.assertEqual(resp_count, "0x0") # 1 root block confirmation block = call_async( master.get_next_block_to_mine(address=acc1, branch_value=None)) call_async(master.add_root_block(block)) resp_hash = send_request("getRootHashConfirmingMinorBlockById", [minor_hash]) self.assertIsNotNone(resp_hash, "confirmed by root block") self.assertEqual(resp_hash, "0x" + block.header.get_hash().hex()) resp_count = send_request( "getTransactionConfirmedByNumberRootBlocks", [tx_id]) self.assertEqual(resp_count, "0x1") # 2 root block confirmation block = call_async( master.get_next_block_to_mine(address=acc1, branch_value=None)) call_async(master.add_root_block(block)) resp_hash = send_request("getRootHashConfirmingMinorBlockById", [minor_hash]) self.assertIsNotNone(resp_hash, "confirmed by root block") self.assertNotEqual(resp_hash, "0x" + block.header.get_hash().hex()) resp_count = send_request( "getTransactionConfirmedByNumberRootBlocks", [tx_id]) self.assertEqual(resp_count, "0x2")
def test_getMinorBlock(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) with ClusterContext( 1, acc1, small_coinbase=True) as clusters, jrpc_server_context( clusters[0].master): master = clusters[0].master slaves = clusters[0].slave_list self.assertEqual( call_async( master.get_primary_account_data(acc1)).transaction_count, 0) tx = create_transfer_transaction( shard_state=clusters[0].get_shard_state(2 | 0), key=id1.get_key(), from_address=acc1, to_address=acc1, value=12345, ) self.assertTrue(slaves[0].add_tx(tx)) block1 = call_async( master.get_next_block_to_mine(address=acc1, branch_value=0b10)) self.assertTrue( call_async(clusters[0].get_shard(2 | 0).add_block(block1))) # By id for need_extra_info in [True, False]: resp = send_request( "getMinorBlockById", [ "0x" + block1.header.get_hash().hex() + "0" * 8, False, need_extra_info, ], ) self.assertEqual(resp["transactions"][0], "0x" + tx.get_hash().hex() + "00000002") resp = send_request( "getMinorBlockById", ["0x" + block1.header.get_hash().hex() + "0" * 8, True], ) self.assertEqual(resp["transactions"][0]["hash"], "0x" + tx.get_hash().hex()) resp = send_request("getMinorBlockById", ["0x" + "ff" * 36, True]) self.assertIsNone(resp) # By height for need_extra_info in [True, False]: resp = send_request("getMinorBlockByHeight", ["0x0", "0x1", False, need_extra_info]) self.assertEqual(resp["transactions"][0], "0x" + tx.get_hash().hex() + "00000002") resp = send_request("getMinorBlockByHeight", ["0x0", "0x1", True]) self.assertEqual(resp["transactions"][0]["hash"], "0x" + tx.get_hash().hex()) resp = send_request("getMinorBlockByHeight", ["0x1", "0x2", False]) self.assertIsNone(resp) resp = send_request("getMinorBlockByHeight", ["0x0", "0x4", False]) self.assertIsNone(resp)
def test_getNextBlockToMine_and_addBlock(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) acc3 = Address.create_random_account(full_shard_id=1) with ClusterContext(1, acc1) as clusters, jrpc_server_context( clusters[0].master): slaves = clusters[0].slave_list # Expect to mine root that confirms the genesis minor blocks response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertTrue(response["isRootBlock"]) block = RootBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block.header.height, 1) self.assertEqual(len(block.minor_block_header_list), 2) self.assertEqual(block.minor_block_header_list[0].height, 0) self.assertEqual(block.minor_block_header_list[1].height, 0) send_request("addBlock", "0x0", response["blockData"]) tx = create_transfer_transaction( shard_state=clusters[0].get_shard_state(0), key=id1.get_key(), from_address=acc1, to_address=acc3, value=14, gas=opcodes.GTXXSHARDCOST + opcodes.GTXCOST, ) self.assertTrue(slaves[0].add_tx(tx)) # Expect to mine shard 0 since it has one tx response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertFalse(response["isRootBlock"]) block1 = MinorBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block1.header.branch.value, 0b10) self.assertTrue( send_request("addBlock", "0x2", response["blockData"])) self.assertEqual( clusters[0].get_shard_state(1).get_balance(acc3.recipient, 0), 0) # Expect to mine shard 1 due to proof-of-progress response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertFalse(response["isRootBlock"]) block2 = MinorBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block2.header.branch.value, 0b11) self.assertTrue( send_request("addBlock", "0x3", response["blockData"])) # Expect to mine root response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertTrue(response["isRootBlock"]) block = RootBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block.header.height, 2) self.assertEqual(len(block.minor_block_header_list), 2) self.assertEqual(block.minor_block_header_list[0], block1.header) self.assertEqual(block.minor_block_header_list[1], block2.header) send_request("addBlock", "0x0", response["blockData"]) self.assertEqual( clusters[0].get_shard_state(1).get_balance(acc3.recipient, 0), 0) # Expect to mine shard 1 for the gas on xshard tx to acc3 response = send_request("getNextBlockToMine", "0x" + acc1.serialize().hex(), "0x0") self.assertFalse(response["isRootBlock"]) block3 = MinorBlock.deserialize( bytes.fromhex(response["blockData"][2:])) self.assertEqual(block3.header.branch.value, 0b11) self.assertTrue( send_request("addBlock", "0x3", response["blockData"])) # Expect withdrawTo is included in acc3's balance resp = send_request("getBalance", "0x" + acc3.serialize().hex()) self.assertEqual(resp["branch"], "0x3") self.assertEqual(resp["balance"], "0xe")
def test_contract_suicide(self): """ Kill Call Data: 0x41c0e1b5 """ QETH = token_id_encode("QETH") id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) id2 = Identity.create_random_identity() acc2 = Address.create_from_identity(id2, full_shard_key=0) acc3 = Address.create_random_account(full_shard_key=0) env = get_test_env( genesis_account=acc1, genesis_minor_token_balances={ self.GENESIS_TOKEN: 200 * 10 ** 18, "QETH": 99999, }, ) state = create_default_shard_state(env=env) # 1. create contract BYTECODE = "6080604052348015600f57600080fd5b5060948061001e6000396000f3fe6080604052600436106039576000357c01000000000000000000000000000000000000000000000000000000009004806341c0e1b514603b575b005b348015604657600080fd5b50604d604f565b005b3373ffffffffffffffffffffffffffffffffffffffff16fffea165627a7a7230582034cc4e996685dcadcc12db798751d2913034a3e963356819f2293c3baea4a18c0029" """ pragma solidity ^0.5.1; contract Sample { function () payable external{} function kill() external {selfdestruct(msg.sender);} } """ CREATION_GAS = 92417 tx = contract_creation_tx( shard_state=state, key=id1.get_key(), from_address=acc1, to_full_shard_key=acc1.full_shard_key, bytecode=BYTECODE, gas_token_id=self.genesis_token, transfer_token_id=self.genesis_token, ) self.assertTrue(state.add_tx(tx)) b1 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b1.tx_list), 1) state.finalize_and_add_block(b1) self.assertEqual(state.header_tip, b1.header) self.assertEqual(len(state.evm_state.receipts), 1) self.assertEqual(state.evm_state.receipts[0].state_root, b"\x01") self.assertEqual(state.evm_state.receipts[0].gas_used, CREATION_GAS) contract_address = mk_contract_address(acc1.recipient, 0, acc1.full_shard_key) self.assertEqual(contract_address, state.evm_state.receipts[0].contract_address) self.assertEqual( acc1.full_shard_key, state.evm_state.receipts[0].contract_full_shard_key ) self.assertEqual( state.get_token_balance(id1.recipient, self.genesis_token), 200 * 10 ** 18 - CREATION_GAS, ) self.assertEqual( state.get_token_balance(acc3.recipient, self.genesis_token), self.get_after_tax_reward(CREATION_GAS + self.shard_coinbase), ) tx_list, _ = state.db.get_transactions_by_address(acc1) self.assertEqual(tx_list[0].value, 0) self.assertEqual(tx_list[0].gas_token_id, self.genesis_token) self.assertEqual(tx_list[0].transfer_token_id, self.genesis_token) # 2. send some default token tx_send = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=Address(contract_address, acc1.full_shard_key), value=10 * 10 ** 18, gas=opcodes.GTXCOST + 40, gas_price=1, nonce=None, data=b"", gas_token_id=self.genesis_token, transfer_token_id=self.genesis_token, ) self.assertTrue(state.add_tx(tx_send)) b2 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b2.tx_list), 1) state.finalize_and_add_block(b2) self.assertEqual(state.header_tip, b2.header) self.assertEqual(len(state.evm_state.receipts), 1) self.assertEqual(state.evm_state.receipts[0].state_root, b"\x01") self.assertEqual(state.evm_state.receipts[0].gas_used, opcodes.GTXCOST + 40) self.assertEqual( state.get_token_balance(id1.recipient, self.genesis_token), 200 * 10 ** 18 - CREATION_GAS - (opcodes.GTXCOST + 40) - 10 * 10 ** 18, ) self.assertEqual( state.get_token_balance(contract_address, self.genesis_token), 10 * 10 ** 18 ) # 3. suicide SUICIDE_GAS = 13199 tx_kill = create_transfer_transaction( shard_state=state, key=id2.get_key(), from_address=acc2, to_address=Address(contract_address, acc1.full_shard_key), value=0, gas=1000000, gas_price=0, # !!! acc2 has no token yet... nonce=None, data=bytes.fromhex("41c0e1b5"), gas_token_id=self.genesis_token, transfer_token_id=self.genesis_token, ) self.assertTrue(state.add_tx(tx_kill)) b3 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b3.tx_list), 1) state.finalize_and_add_block(b3) self.assertEqual(state.header_tip, b3.header) self.assertEqual(len(state.evm_state.receipts), 1) self.assertEqual(state.evm_state.receipts[0].state_root, b"\x01") self.assertEqual(state.evm_state.receipts[0].gas_used, SUICIDE_GAS) self.assertEqual( state.get_token_balance(id2.recipient, self.genesis_token), 10 * 10 ** 18 ) self.assertEqual( state.get_token_balance(contract_address, self.genesis_token), 0 )
def test_xshard_native_token_gas_received(self): qeth = token_id_encode("QETHXX") id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_key=0) acc2 = Address.create_from_identity(id1, full_shard_key=16) acc3 = Address.create_random_account(full_shard_key=0) env0 = get_test_env( genesis_account=acc1, genesis_minor_token_balances={"QETHXX": 9999999}, shard_size=64, ) env1 = get_test_env( genesis_account=acc1, genesis_minor_token_balances={"QETHXX": 9999999}, shard_size=64, ) state0 = create_default_shard_state(env=env0, shard_id=0) state1 = create_default_shard_state(env=env1, shard_id=16) # Add a root block to allow later minor blocks referencing this root block to # be broadcasted root_block = ( state0.root_tip.create_block_to_append() .add_minor_block_header(state0.header_tip) .add_minor_block_header(state1.header_tip) .finalize() ) state0.add_root_block(root_block) state1.add_root_block(root_block) # Add one block in shard 0 b0 = state0.create_block_to_mine() state0.finalize_and_add_block(b0) b1 = state1.get_tip().create_block_to_append() b1.header.hash_prev_root_block = root_block.header.get_hash() tx = create_transfer_transaction( shard_state=state1, key=id1.get_key(), from_address=acc2, to_address=acc1, value=8888888, gas=opcodes.GTXXSHARDCOST + opcodes.GTXCOST, gas_price=2, gas_token_id=qeth, transfer_token_id=qeth, ) b1.add_tx(tx) # Add a x-shard tx from remote peer state0.add_cross_shard_tx_list_by_minor_block_hash( h=b1.header.get_hash(), tx_list=CrossShardTransactionList( tx_list=[ CrossShardTransactionDeposit( tx_hash=tx.get_hash(), from_address=acc2, to_address=acc1, value=8888888, gas_price=2, gas_token_id=self.genesis_token, transfer_token_id=qeth, ) ] ), ) # Create a root block containing the block with the x-shard tx root_block = ( state0.root_tip.create_block_to_append() .add_minor_block_header(b0.header) .add_minor_block_header(b1.header) .finalize() ) state0.add_root_block(root_block) # Add b0 and make sure all x-shard tx's are added b2 = state0.create_block_to_mine(address=acc3) state0.finalize_and_add_block(b2) self.assertEqual( state0.get_token_balance(acc1.recipient, qeth), 9999999 + 8888888 ) # Half coinbase collected by root + tx fee self.assertEqual( state0.get_token_balance(acc3.recipient, self.genesis_token), self.get_after_tax_reward(self.shard_coinbase + opcodes.GTXXSHARDCOST * 2), ) # X-shard gas used self.assertEqual( state0.evm_state.xshard_receive_gas_used, opcodes.GTXXSHARDCOST )
def test_xshard_native_token_gas_sent(self): """x-shard transfer QETH using QETH as gas """ qeth = token_id_encode("QETHXX") 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) acc3 = Address.create_random_account(full_shard_key=0) env = get_test_env( genesis_account=acc1, genesis_minor_token_balances={"QETHXX": 9999999}, charge_gas_reserve=True, ) state = create_default_shard_state(env=env, shard_id=0) env1 = get_test_env(genesis_account=acc1, genesis_minor_token_balances={}) state1 = create_default_shard_state(env=env1, shard_id=1) # Add a root block to update block gas limit so that xshard tx can be included root_block = ( state.root_tip.create_block_to_append() .add_minor_block_header(state.header_tip) .add_minor_block_header(state1.header_tip) .finalize() ) state.add_root_block(root_block) tx = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=8888888, gas=opcodes.GTXXSHARDCOST + opcodes.GTXCOST, gas_token_id=qeth, transfer_token_id=qeth, ) state.add_tx(tx) b1 = state.create_block_to_mine(address=acc3) self.assertEqual(len(b1.tx_list), 1) self.assertEqual(state.evm_state.gas_used, 0) # Should succeed state.finalize_and_add_block(b1) self.assertEqual(len(state.evm_state.xshard_list), 1) self.assertEqual( state.evm_state.xshard_list[0], CrossShardTransactionDeposit( tx_hash=tx.get_hash(), from_address=acc1, to_address=acc2, value=8888888, gas_price=1, gas_token_id=self.genesis_token, transfer_token_id=qeth, ), ) self.assertEqual( state.get_token_balance(id1.recipient, qeth), 9999999 - 8888888 - (opcodes.GTXCOST + opcodes.GTXXSHARDCOST), ) # Make sure the xshard gas is not used by local block self.assertEqual(state.evm_state.gas_used, opcodes.GTXCOST) # block coinbase for mining is still in genesis_token + xshard fee self.assertEqual( state.get_token_balance(acc3.recipient, self.genesis_token), self.get_after_tax_reward(self.shard_coinbase + opcodes.GTXCOST), )