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, 0), 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, 0), opcodes.GTXCOST // 2)
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, small_coinbase=True) 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))) _, 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_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 resp = send_request( "getMinorBlockById", ["0x" + block1.header.get_hash().hex() + "0" * 8, False], ) 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 resp = send_request("getMinorBlockByHeight", ["0x0", "0x1", False]) 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_add_root_block_revert_header_tip(self): """ block's hash_prev_root_block must be on the same chain with root_tip to update tip. +--+ |r1|<-------------+ /+--+ | / | | +--+ / +--+ +--+ +--+ |r0|<----|m1|<---|m2| <---|m3| +--+ \ +--+ +--+ +--+ \ | \ \+--+. +--+ |r2|<-----|r3| (r3 includes m2) +--+ +--+ Initial state: r0 <- m1 <- m2 Adding r1, r2, m3 makes r1 the root_tip, m3 the header_tip Adding r3 should change the root_tip to r3, header_tip to m2 """ 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, shard_id=0) m1 = state.get_tip().create_block_to_append(address=acc1) state.finalize_and_add_block(m1) m2 = state.get_tip().create_block_to_append(address=acc1) state.finalize_and_add_block(m2) r1 = state.root_tip.create_block_to_append() r2 = state.root_tip.create_block_to_append() r1.minor_block_header_list.append(m1.header) r1.finalize() state.add_root_block(r1) r2.minor_block_header_list.append(m1.header) r2.header.create_time = r1.header.create_time + 1 # make r2, r1 different r2.finalize() self.assertNotEqual(r1.header.get_hash(), r2.header.get_hash()) state.add_root_block(r2) self.assertEqual(state.root_tip, r1.header) m3 = state.create_block_to_mine(address=acc1) self.assertEqual(m3.header.hash_prev_root_block, r1.header.get_hash()) state.finalize_and_add_block(m3) r3 = r2.create_block_to_append(address=acc1) r3.add_minor_block_header(m2.header) r3.finalize() state.add_root_block(r3) self.assertEqual(state.root_tip, r3.header) self.assertEqual(state.header_tip, m2.header)
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 tx = create_transfer_transaction( shard_state=clusters[0].get_shard_state(1 | 0), 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, branch_value=shard_id and 0b01)) # solve it and submit work = MiningWork(bytes.fromhex(header_hash_hex[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, "0x" + bytes(65).hex(), ], ) self.assertTrue(resp) # show progress on shard 0 self.assertEqual( clusters[0].get_shard_state(1 | 0).get_tip().header.height, 1)
def test_estimateGas(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) with ClusterContext(1, acc1) as clusters, jrpc_server_context( clusters[0].master): response = send_request("estimateGas", {"to": "0x" + acc1.serialize().hex()}) self.assertEqual(response, "0x5208") # 21000
def test_getTransactionReceipt_not_exist(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, full_shard_id=0) with ClusterContext(1, acc1) as clusters, jrpc_server_context( clusters[0].master): resp = send_request("getTransactionReceipt", "0x" + bytes(36).hex()) self.assertIsNone(resp)
def test_signature(self): header = RootBlockHeader() private_key = KeyAPI.PrivateKey(Identity.create_random_identity().get_key()) self.assertEqual(header.signature, bytes(65)) self.assertFalse(header.is_signed()) self.assertFalse(header.verify_signature(private_key.public_key)) header.sign_with_private_key(private_key) self.assertNotEqual(header.signature, bytes(65)) self.assertTrue(header.is_signed()) self.assertTrue(header.verify_signature(private_key.public_key))
def test_getTransactionReceipt_not_exist(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): for endpoint in ("getTransactionReceipt", "eth_getTransactionReceipt"): resp = send_request(endpoint, ["0x" + bytes(36).hex()]) self.assertIsNone(resp)
def test_not_update_tip_on_root_fork(self): """ block's hash_prev_root_block must be on the same chain with root_tip to update tip. +--+ a. |r1| /+--+ / | +--+ / +--+ +--+ |r0|<----|m1|<---|m2| c. +--+ \ +--+ +--+ \ | | \+--+ | b. |r2|<----+ +--+ Initial state: r0 <- m1 Then adding r1, r2, m2 should not make m2 the tip because r1 is the root tip and r2 and r1 are not on the same root chain. """ 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, shard_id=0) m1 = state.get_tip().create_block_to_append(address=acc1) state.finalize_and_add_block(m1) r1 = state.root_tip.create_block_to_append() r2 = state.root_tip.create_block_to_append() r1.minor_block_header_list.append(m1.header) r1.finalize() state.add_root_block(r1) r2.minor_block_header_list.append(m1.header) r2.header.create_time = r1.header.create_time + 1 # make r2, r1 different r2.finalize() self.assertNotEqual(r1.header.get_hash(), r2.header.get_hash()) state.add_root_block(r2) self.assertEqual(state.root_tip, r1.header) m2 = m1.create_block_to_append(address=acc1) m2.header.hash_prev_root_block = r2.header.get_hash() state.finalize_and_add_block(m2) # m2 is added self.assertEqual( state.db.get_minor_block_by_hash(m2.header.get_hash()), m2) # but m1 should still be the tip self.assertEqual(state.header_tip, m1.header)
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_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_transaction(self): id1 = Identity.create_random_identity() acc2 = Address.create_random_account() tx = create_random_test_transaction(id1, acc2) barray = tx.serialize(bytearray()) bb = ByteBuffer(barray) tx1 = Transaction.deserialize(bb) self.assertEqual(bb.remaining(), 0) self.assertEqual(tx, tx1) self.assertTrue(tx1.verify_signature([id1.get_recipient()]))
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_sendTransaction(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): slaves = clusters[0].slave_list master = clusters[0].master block = call_async( master.get_next_block_to_mine(address=acc2, branch_value=None)) call_async(master.add_root_block(block)) evm_tx = EvmTransaction( nonce=0, gasprice=6, startgas=30000, to=acc2.recipient, value=15, data=b"", from_full_shard_key=acc1.full_shard_key, to_full_shard_key=acc2.full_shard_key, network_id=slaves[0].env.quark_chain_config.NETWORK_ID, gas_token_id=master.env.quark_chain_config.genesis_token, transfer_token_id=master.env.quark_chain_config.genesis_token, ) evm_tx.sign(id1.get_key()) request = dict( to="0x" + acc2.recipient.hex(), gasPrice="0x6", gas=hex(30000), value="0xf", # 15 v=quantity_encoder(evm_tx.v), r=quantity_encoder(evm_tx.r), s=quantity_encoder(evm_tx.s), nonce="0x0", fromFullShardKey="0x00000000", toFullShardKey="0x00000001", network_id=hex(slaves[0].env.quark_chain_config.NETWORK_ID), ) tx = TypedTransaction(SerializedEvmTransaction.from_evm_tx(evm_tx)) response = send_request("sendTransaction", [request]) self.assertEqual(response, "0x" + tx.get_hash().hex() + "00000000") state = clusters[0].get_shard_state(2 | 0) self.assertEqual(len(state.tx_queue), 1) self.assertEqual( state.tx_queue.pop_transaction(state.get_transaction_count), evm_tx)
def new(key=None): """ Create a new account. :param key: the private key to import, or None to generate a random one """ if key is None: identity = Identity.create_random_identity() else: if not isinstance(key, str): raise Exception("Imported key must be a hexadecimal string") identity = Identity.create_from_key(bytes.fromhex(key)) address = Address.create_from_identity(identity) return Account(identity, address)
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 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=master.env.quark_chain_config.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 _, new_block = call_async(master.get_next_block_to_mine(address=acc1)) self.assertIsInstance(new_block, MinorBlock) self.assertEqual(new_block.header.height, 2)
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_get_transaction_by_address(self): id1 = Identity.create_random_identity() miner_addr = Address.create_random_account(full_shard_key=0) acc1 = Address.create_from_identity(id1, full_shard_key=0) acc2 = Address.create_from_identity(id1, full_shard_key=100) acc3 = Address.create_random_account(full_shard_key=0) env = get_test_env(genesis_account=acc1, genesis_minor_quarkash=10000000) state = create_default_shard_state(env=env) # Add a root block to have all the shards initialized root_block = state.root_tip.create_block_to_append().finalize() state.add_root_block(root_block) tx1 = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc2, value=12345, ) self.assertTrue(state.add_tx(tx1)) b1 = state.create_block_to_mine(address=miner_addr) state.finalize_and_add_block(b1) self.assertEqual(state.header_tip, b1.header) tx2 = create_transfer_transaction( shard_state=state, key=id1.get_key(), from_address=acc1, to_address=acc3, value=54321, ) self.assertTrue(state.add_tx(tx2)) b2 = state.create_block_to_mine(address=miner_addr) state.finalize_and_add_block(b2) self.assertEqual(state.header_tip, b2.header) # acc1 and acc2 should have the same transaction history # while acc3 is different tx_list1, _ = state.db.get_transactions_by_address(acc1) self.assertListEqual([t.value for t in tx_list1], [12345, 54321]) tx_list2, _ = state.db.get_transactions_by_address(acc2) self.assertListEqual(tx_list2, tx_list1) tx_list3, _ = state.db.get_transactions_by_address(acc3) self.assertEqual(tx_list3[0].value, 54321) self.assertNotEqual(tx_list3, tx_list1)
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_block_receipt_root_not_match(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1) 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) b1 = state.create_block_to_mine(address=acc3) # Should succeed state.finalize_and_add_block(b1) b1.finalize(evm_state=state.run_block(b1)) b1.meta.hash_evm_receipt_root = b"00" * 32 self.assertRaises(ValueError, state.add_block(b1))
def test_shard_state_recovery_from_root_block(self): 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, shard_id=0) blockHeaders = [] blockMetas = [] for i in range(12): b = state.get_tip().create_block_to_append(address=acc1) state.finalize_and_add_block(b) blockHeaders.append(b.header) blockMetas.append(b.meta) # add a fork b1 = state.db.get_minor_block_by_height(3) b1.header.create_time += 1 state.finalize_and_add_block(b1) self.assertEqual( state.db.get_minor_block_by_hash(b1.header.get_hash()), b1) root_block = state.root_tip.create_block_to_append() root_block.minor_block_header_list = blockHeaders[:5] root_block.finalize() state.add_root_block(root_block) recoveredState = ShardState(env=env, shard_id=0) recoveredState.init_from_root_block(root_block) # forks are pruned self.assertIsNone( recoveredState.db.get_minor_block_by_hash(b1.header.get_hash())) self.assertEqual( recoveredState.db.get_minor_block_by_hash(b1.header.get_hash(), consistency_check=False), b1, ) self.assertEqual(recoveredState.root_tip, root_block.header) self.assertEqual(recoveredState.header_tip, blockHeaders[4]) self.assertEqual(recoveredState.confirmed_header_tip, blockHeaders[4]) self.assertEqual(recoveredState.meta_tip, blockMetas[4]) self.assertEqual(recoveredState.evm_state.trie.root_hash, blockMetas[4].hash_evm_state_root)
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_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_stale_block_count(self): id1 = Identity.create_random_identity() acc1 = Address.create_from_identity(id1, 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) b1 = state.create_block_to_mine(address=acc3) b2 = state.create_block_to_mine(address=acc3) b2.header.create_time += 1 state.finalize_and_add_block(b1) self.assertEqual(state.db.get_block_count_by_height(1), 1) state.finalize_and_add_block(b2) self.assertEqual(state.db.get_block_count_by_height(1), 2)
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_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_sendTransaction(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) with ClusterContext( 1, acc1, small_coinbase=True) as clusters, jrpc_server_context( clusters[0].master): slaves = clusters[0].slave_list branch = Branch.create(2, 0) evm_tx = EvmTransaction( nonce=0, gasprice=6, startgas=30000, to=acc2.recipient, value=15, data=b"", from_full_shard_id=acc1.full_shard_id, to_full_shard_id=acc2.full_shard_id, network_id=slaves[0].env.quark_chain_config.NETWORK_ID, ) evm_tx.sign(id1.get_key()) request = dict( to="0x" + acc2.recipient.hex(), gasPrice="0x6", gas=hex(30000), value="0xf", # 15 v=quantity_encoder(evm_tx.v), r=quantity_encoder(evm_tx.r), s=quantity_encoder(evm_tx.s), nonce="0x0", fromFullShardId="0x00000000", toFullShardId="0x00000001", network_id=hex(slaves[0].env.quark_chain_config.NETWORK_ID), ) tx = Transaction(code=Code.create_evm_code(evm_tx)) response = send_request("sendTransaction", [request]) self.assertEqual(response, "0x" + tx.get_hash().hex() + "00000000") self.assertEqual(len(slaves[0].shards[branch].state.tx_queue), 1) self.assertEqual( slaves[0].shards[branch].state.tx_queue.pop_transaction(), evm_tx)
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)