def compute_state_test_unit(state, txdata, indices, konfig, is_qkc_state, qkc_env=None): state.env.config = konfig s = state.snapshot() if "transferTokenId" in txdata: transfer_token_id = parse_int_or_hex( txdata["transferTokenId"][indices["transferTokenId"]]) else: transfer_token_id = token_id_encode("QKC") try: # Create the transaction tx = transactions.Transaction( nonce=parse_int_or_hex(txdata["nonce"] or b"0"), gasprice=parse_int_or_hex(txdata["gasPrice"] or b"0"), startgas=parse_int_or_hex(txdata["gasLimit"][indices["gas"]] or b"0"), to=decode_hex(remove_0x_head(txdata["to"])), value=parse_int_or_hex(txdata["value"][indices["value"]] or b"0"), data=decode_hex(remove_0x_head(txdata["data"][indices["data"]])), gas_token_id=token_id_encode("QKC"), transfer_token_id=transfer_token_id, # Should not set testing flag if testing QuarkChain state is_testing=not is_qkc_state, ) tx.set_quark_chain_config(qkc_env.quark_chain_config) if "secretKey" in txdata: tx.sign(decode_hex(remove_0x_head(txdata["secretKey"]))) else: tx._in_mutable_context = True tx.v = parse_int_or_hex(txdata["v"]) tx._in_mutable_context = False # Run it prev = state.to_dict() success, output = apply_transaction(state, tx, tx_wrapper_hash=bytes(32)) except InvalidTransaction as e: print("Exception: %r" % e) success, output = False, b"" # touch coinbase, make behavior consistent with go-ethereum state.delta_token_balance(state.block_coinbase, token_id_encode("QKC"), 0) state.commit() post = state.to_dict() output_decl = { "hash": "0x" + encode_hex(state.trie.root_hash), "indexes": indices, "diff": mk_state_diff(prev, post), } state.revert(s) return output_decl
def main(): args = parser.parse_args() if args.name is not None: print("Token name %s to id: %d (%s)" % (args.name, token_id_encode( args.name), hex(token_id_encode(args.name)))) if args.id is not None: print("Token id %d (%s) to id: %s" % (args.id, hex(args.id), token_id_decode(args.id)))
def test_proc_transfer_mnt(self): sender = b"\x00" * 19 + b"\x34" precompiled = decode_hex(b"000000000000000000000000000000514b430002") token_id = 1234567 token_id_bytes = token_id.to_bytes(32, byteorder="big") state = State() self.__mint( state, sender, token_id_encode("QKC").to_bytes(32, byteorder="big"), encode_int32(30000), ) self.__mint(state, sender, token_id_bytes, encode_int32(100)) balance = state.get_balance(sender, token_id) self.assertEqual(balance, 100) balance = state.get_balance(sender, token_id_encode("QKC")) self.assertEqual(balance, 30000) new_addr = b"\x01" * 20 testcases = [ # Format: (index, description, to, value, gas, expected ret, post check) (1, "no value transfer", sender, 0, 0, (1, 0, []), None), (2, "value transfer needs more gas", sender, 1, 8999, (0, 0, []), None), # Should have stipend gas left (3, "value transfer needs more gas", sender, 1, 9000, (1, 2300, []), None), (4, "tx on new addr needs more gas", new_addr, 1, 33999, (0, 0, []), None), ( 5, "tx on new addr needs more gas", new_addr, 42, 34000, (1, 2300, []), lambda err_msg: self.assertEqual( state.get_balance(new_addr, token_id), 42, err_msg), ), (6, "insufficient balance", sender, 333, 9000, (0, 0, []), None), (7, "target address is special", precompiled, 0, 9000, (0, 0, []), None), ] for i, desc, addr, value, gas, expected_ret, post_check in testcases: # Data = to address + token ID ++ value ++ (optional) msg data data = (b"\x00" * 12 + addr + token_id_bytes + value.to_bytes(32, byteorder="big")) msg = Message(sender, sender, gas=gas, data=data) ret = proc_transfer_mnt(VMExt(state, sender, gas_price=1), msg) self.assertEqual(ret, expected_ret, "%d: %s" % (i, desc)) if callable(post_check): post_check("%d: %s" % (i, desc))
def test_encoding_change_from_dict_to_trie(): db = InMemoryDb() b = TokenBalances(b"", db) # start with 16 entries - right below the threshold mapping = { token_id_encode("Q" + chr(65 + i)): int(i * 1e3) + 42 for i in range(16) } b._balances = mapping.copy() b.commit() assert b.serialize().startswith(b"\x00") assert b.token_trie is None # add one more entry and expect changes journal = [] new_token = token_id_encode("QKC") b.set_balance(journal, new_token, 123) assert b.balance(new_token) == 123 b.commit() assert b.token_trie is not None assert b.serialize().startswith(b"\x01") root1 = b.token_trie.root_hash copied_mapping = mapping.copy() copied_mapping[new_token] = 123 assert b.to_dict() == copied_mapping # clear all balances except QKC for k in mapping: b.set_balance(journal, k, 0) # still have those token keys in balance map assert b.balance(token_id_encode("QA")) == 0 assert b.to_dict() == {new_token: 123} # trie hash should change after serialization b.commit() serialized = b.serialize() root2 = b.token_trie.root_hash assert serialized == b"\x01" + root2 assert root1 != root2 # balance map truncated, entries with 0 value will be ignored assert b._balances == {} assert b.balance(token_id_encode("QB")) == 0 assert len(b._balances) == 0 assert b.to_dict() == {new_token: 123} assert not b.is_blank() # remove the last entry b.set_balance(journal, new_token, 0) assert b.to_dict() == {} b.commit() assert b.token_trie.root_hash == BLANK_ROOT assert b._balances == {}
def commit(self, allow_empties=False): for addr, acct in self.cache.items(): if acct.touched or acct.deleted: acct.commit() if self.account_exists(addr) or allow_empties: if self.use_mock_evm_account: assert len( acct.token_balances._balances) <= 1, "QKC only" _acct = _MockAccount( acct.nonce, acct.token_balances.balance( token_id_encode("QKC")), acct.storage, acct.code_hash, ) else: _acct = _Account( acct.nonce, acct.token_balances.serialize(), acct.storage, acct.code_hash, acct.full_shard_key, b"", ) self.trie.update(addr, rlp.encode(_acct)) else: self.trie.delete(addr) self.trie.deletes = [] self.cache = {} self.journal = []
def prometheus_balance(args): tokens = { token_name: token_id_encode(token_name) for token_name in args.tokens.strip().split(sep=",") } # Create a metric to track token total balance. balance_gauge = Gauge( "token_total_balance", "Total balance of specified tokens", ("shard", "token") ) # A meta gauge to track block height, because if things go wrong, we want # to know which root block has the wrong balance. block_height_gauge = Gauge("block_height", "Height for root block and minor block") while True: try: # Call when rpc server is ready. latest_block_height = get_highest() total_balance = {} for token_name, token_id in tokens.items(): total_balance[token_name] = get_time_and_balance( latest_block_height, token_id ) except Exception as e: print("failed to get latest root block height", e) # Rpc not ready, wait and try again. time.sleep(3) continue block_height_gauge.set(latest_block_height) for token_name, (_, balance_dict) in total_balance.items(): balance_gauge.labels("total", token_name).set(sum(balance_dict.values())) for shard_id, shard_bal in balance_dict.items(): balance_gauge.labels(shard_id, token_name).set(shard_bal) time.sleep(args.interval)
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 print_shard_balance(env, rb, full_shard_id): shard = Shard(env, full_shard_id, None) state = shard.state state.init_from_root_block(rb) print("Full shard id: %d" % full_shard_id) print("Block height: %d" % state.header_tip.height) print("Trie hash: %s" % state.meta_tip.hash_evm_state_root.hex()) trie = Trie(state.raw_db, state.meta_tip.hash_evm_state_root) key = trie.next(bytes(32)) total = 0 while key is not None: rlpdata = trie.get(key) o = rlp.decode(rlpdata, _Account) tb = TokenBalances(o.token_balances, state.raw_db) balance = tb.balance(token_id_encode("QKC")) print("Key: %s, Balance: %s" % (key.hex(), balance)) total += balance key = trie.next(key) print("Total balance in shard: %d" % total) return total, state.header_tip.height
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) 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.assertFalse(state.add_tx(tx))
def test_native_token_gas(self): """in-shard transfer QETH using native token as gas TODODLL: support 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.assertFalse( state.add_tx(tx) ) # warning log: Failed to add transaction: Gas token must be QKC return 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, QETH), 10000000 - opcodes.GTXCOST) 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.getAfterTaxReward(opcodes.GTXCOST), ) # miner coinbase self.assertEqual( state.get_token_balance(acc3.recipient, self.genesis_token), self.getAfterTaxReward(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 allowed_token_ids(self): if self._allowed_token_ids is None: self._allowed_token_ids = {self.genesis_token} for _, shard in self.shards.items(): for _, token_dict in shard.GENESIS.ALLOC.items(): for token_id in token_dict: self._allowed_token_ids.add(token_id_encode(token_id)) return self._allowed_token_ids
def test_native_token_transfer(self): """in-shard transfer QETH using genesis_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={ self.GENESIS_TOKEN: 10000000, "QETH": 99999 }, ) 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=self.genesis_token, 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(id1.recipient, self.genesis_token), 10000000 - opcodes.GTXCOST, ) self.assertEqual(state.get_token_balance(acc1.recipient, QETH), 99999 - 12345) self.assertEqual(state.get_token_balance(acc2.recipient, QETH), 12345) self.assertEqual( state.get_token_balance(acc3.recipient, self.genesis_token), self.getAfterTaxReward(opcodes.GTXCOST + 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, self.genesis_token) 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, self.genesis_token) self.assertEqual(tx_list[0].transfer_token_id, QETH)
def test_token_id_exceptions(): with pytest.raises(AssertionError): token_id_encode("qkc") with pytest.raises(AssertionError): token_id_encode(" ") with pytest.raises(AssertionError): token_id_encode("btc") with pytest.raises(AssertionError): token_id_encode(TOKEN_MAX + "Z") with pytest.raises(AssertionError): token_id_decode(2 ** 64 - 1) with pytest.raises(AssertionError): token_id_decode(-1) with pytest.raises(AssertionError): token_id_decode(TOKEN_ID_MAX + 1)
def test_token_id_exceptions(): with pytest.raises(AssertionError): token_id_encode("qkc") with pytest.raises(AssertionError): token_id_encode(" ") with pytest.raises(AssertionError): token_id_encode("btc") with pytest.raises(AssertionError): token_id_encode("ZZZZZZZZZZZZZ") with pytest.raises(AssertionError): token_id_decode(2 ** 64 - 1) with pytest.raises(AssertionError): token_id_decode(-1) with pytest.raises(AssertionError): token_id_decode(ZZZZZZZZZZZZ + 1)
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) # Miner acc3 = Address.create_random_account(full_shard_key=0) env = get_test_env( genesis_account=acc1, genesis_minor_token_balances={"QETH": 10000000}, charge_gas_reserve=True, ) 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) # after-tax coinbase + tx fee should only be in QKC self.assertEqual( state.get_token_balance(acc3.recipient, self.genesis_token), self.get_after_tax_reward(self.shard_coinbase + opcodes.GTXCOST), ) 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_encoding_in_trie(): encoding = b"\x01\x84\x8dBq\xe4N\xa4\x14f\xfe5Ua\xddC\xb1f\xc9'\xd2\xec\xa0\xa8\xdd\x90\x1a\x8edi\xec\xde\xb1" mapping = { token_id_encode("Q" + chr(65 + i)): int(i * 1e3) + 42 for i in range(17) } db = InMemoryDb() # starting from blank account b0 = TokenBalances(b"", db) b0._balances = mapping.copy() b0.commit() assert b0.serialize() == encoding # check internal states assert b0.token_trie is not None # starting from RLP encoding b1 = TokenBalances(encoding, db) assert b1.to_dict() == mapping assert b1.serialize() == encoding # check internal states assert b1._balances == {} assert b1.token_trie is not None assert not b1.is_blank() assert b1.balance(token_id_encode("QC")) == mapping[token_id_encode("QC")] # underlying balance map populated assert len(b1._balances) == 1 # serialize without commit should fail try: b1.serialize() pytest.fail() except AssertionError: pass # otherwise should succeed b1.commit() b1.serialize()
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_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_reset_balance_in_trie_and_revert(): db = InMemoryDb() b = TokenBalances(b"", db) mapping = { token_id_encode("Q" + chr(65 + i)): int(i * 1e3) + 42 for i in range(17) } b._balances = mapping.copy() b.commit() assert b._balances == {} journal = [] b.set_balance(journal, 999, 999) b.set_balance(journal, token_id_encode("Q" + chr(65 + 0)), 1) assert b.balance(999) == 999 assert b.balance(token_id_encode("Q" + chr(65 + 0))) == 1 b.reset(journal) assert b.is_blank() assert b.to_dict() == {} for op in journal: op() assert not b.is_blank() assert b.to_dict() == mapping assert b._balances != {}
async def gasPrice(self, full_shard_key: str, token_id: Optional[str] = None): full_shard_key = shard_id_decoder(full_shard_key) if full_shard_key is None: return None parsed_token_id = ( quantity_decoder(token_id) if token_id else token_id_encode("QKC") ) branch = Branch( self.master.env.quark_chain_config.get_full_shard_id_by_full_shard_key( full_shard_key ) ) ret = await self.master.gas_price(branch, parsed_token_id) if ret is None: return None return quantity_encoder(ret)
def allowed_token_ids(self): if self._allowed_token_ids is None: self._allowed_token_ids = {self.genesis_token} for _, shard in self.shards.items(): for _, alloc_data in shard.GENESIS.ALLOC.items(): # genesis config is 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 in balances: if k in ("code", "storage"): continue self._allowed_token_ids.add(token_id_encode(k)) return self._allowed_token_ids
def get_and_cache_account(self, address): if address in self.cache: return self.cache[address] if self.executing_on_head and False: try: rlpdata = self.db[b"address:" + address] except KeyError: rlpdata = b"" else: rlpdata = self.trie.get(address) if rlpdata != trie.BLANK_NODE: if self.use_mock_evm_account: o = rlp.decode(rlpdata, _MockAccount) raw_token_balances = TokenBalances(b"", self.db) raw_token_balances._balances = { token_id_encode("QKC"): o.balance } token_balances = raw_token_balances.serialize() full_shard_key = self.full_shard_key else: o = rlp.decode(rlpdata, _Account) token_balances = o.token_balances full_shard_key = o.full_shard_key o = Account( nonce=o.nonce, token_balances=token_balances, storage=o.storage, code_hash=o.code_hash, full_shard_key=full_shard_key, env=self.env, address=address, db=self.db, ) else: o = Account.blank_account( self.env, address, self.full_shard_key, self.config["ACCOUNT_INITIAL_NONCE"], db=self.db, ) self.cache[address] = o o._mutable = True o._cached_rlp = None return o
def commit(self, allow_empties=False): for addr, acct in self.cache.items(): if acct.touched or acct.deleted: acct.commit() self.deletes.extend(acct.storage_trie.deletes) self.changed[addr] = True if self.account_exists(addr) or allow_empties: if self.use_mock_evm_account: assert len( acct.token_balances._balances) <= 1, "QKC only" _acct = _MockAccount( acct.nonce, acct.token_balances.balance( token_id_encode("QKC")), acct.storage, acct.code_hash, ) else: _acct = _Account( acct.nonce, acct.token_balances.serialize(), acct.storage, acct.code_hash, acct.full_shard_key, b"", ) self.trie.update(addr, rlp.encode(_acct)) if self.executing_on_head: self.db.put(b"address:" + addr, rlp.encode(_acct)) else: self.trie.delete(addr) if self.executing_on_head: try: self.db.remove(b"address:" + addr) except KeyError: pass self.deletes.extend(self.trie.deletes) self.trie.deletes = [] self.cache = {} self.journal = []
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 get_and_cache_account(self, address, should_cache=True): if address in self.cache: return self.cache[address] rlpdata = self.trie.get(address) if rlpdata != trie.BLANK_NODE: if self.use_mock_evm_account: o = rlp.decode(rlpdata, _MockAccount) raw_token_balances = TokenBalances(b"", self.db) raw_token_balances._balances = { token_id_encode("QKC"): o.balance } token_balances = raw_token_balances.serialize() full_shard_key = self.full_shard_key else: o = rlp.decode(rlpdata, _Account) token_balances = o.token_balances full_shard_key = o.full_shard_key o = Account( nonce=o.nonce, token_balances=token_balances, storage=o.storage, code_hash=o.code_hash, full_shard_key=full_shard_key, env=self.env, address=address, db=self.db, ) else: o = Account.blank_account( self.env, address, self.full_shard_key, self.config["ACCOUNT_INITIAL_NONCE"], db=self.db, ) if should_cache: self.cache[address] = o return o
def genesis_token(self): if self._genesis_token is None: self._genesis_token = token_id_encode(self.GENESIS_TOKEN) return self._genesis_token
def default_chain_token(self): if self._default_chain_token is None: self._default_chain_token = token_id_encode( self.DEFAULT_CHAIN_TOKEN) return self._default_chain_token
def validate_transaction(state, tx): # (1) The transaction signature is valid; if not tx.sender: # sender is set and validated on Transaction initialization raise UnsignedTransaction(tx) if tx.version == 2: # When tx.version == 2 (EIP155 tx), check # 0. EIP155_SIGNER enable # 1. tx.v == tx.network_id * 2 + 35 (+ 1) # 2. gas_token_id & transfer_token_id should equal to default_token_id (like qkc) # 3. tx.from_chain_id == tx.to_chain_id and tx.from_shard_key = 0 & tx.to_shard_key = 0 # 4. tx.network_id == chain_config.ETH_CHAIN_ID, where chain_config is derived from tx.from_chain_id chain_config = state.qkc_config.CHAINS[tx.from_chain_id] default_token_id = token_id_encode(chain_config.DEFAULT_CHAIN_TOKEN) if (state.qkc_config.ENABLE_EIP155_SIGNER_TIMESTAMP is not None and state.timestamp < state.qkc_config.ENABLE_EIP155_SIGNER_TIMESTAMP): raise InvalidTransaction("EIP155 Signer is not enable yet.") if tx.v != 35 + tx.network_id * 2 and tx.v != 36 + tx.network_id * 2: raise InvalidTransaction( "network_id {} does not match the signature v {}.".format( tx.network_id, tx.v)) if tx.from_chain_id != tx.to_chain_id: raise InvalidTransaction( "EIP155 Signer do not support cross shard transaction.") if tx.from_shard_key != 0 or tx.to_shard_key != 0: raise InvalidTransaction( "EIP155 Signer do not support cross shard transaction.") if tx.gas_token_id != default_token_id: raise InvalidTransaction( "EIP155 Signer only support {} as gas token.".format( chain_config.DEFAULT_CHAIN_TOKEN)) if tx.transfer_token_id != default_token_id: raise InvalidTransaction( "EIP155 Signer only support {} as transfer token.".format( chain_config.DEFAULT_CHAIN_TOKEN)) assert (tx.network_id == chain_config.ETH_CHAIN_ID, "Invalid network_id.") assert ( tx.eth_chain_id - state.qkc_config.BASE_ETH_CHAIN_ID - 1 == tx.from_chain_id, "Invalid Eth_Chain_Id.", ) # (1a) startgas, gasprice, gas token id, transfer token id must be <= UINT128_MAX if (tx.startgas > UINT128_MAX or tx.gasprice > UINT128_MAX or tx.gas_token_id > TOKEN_ID_MAX or tx.transfer_token_id > TOKEN_ID_MAX): raise InvalidTransaction( "startgas, gasprice, and token_id must <= UINT128_MAX") # (2) the transaction nonce is valid (equivalent to the # sender account's current nonce); req_nonce = state.get_nonce(tx.sender) if req_nonce != tx.nonce: raise InvalidNonce(rp(tx, "nonce", tx.nonce, req_nonce)) # (3) the gas limit is no smaller than the intrinsic gas, # g0, used by the transaction; total_gas = tx.intrinsic_gas_used if tx.startgas < total_gas: raise InsufficientStartGas(rp(tx, "startgas", tx.startgas, total_gas)) default_chain_token = state.shard_config.default_chain_token bal = { tx.transfer_token_id: state.get_balance(tx.sender, tx.transfer_token_id) } if tx.transfer_token_id != tx.gas_token_id: bal[tx.gas_token_id] = state.get_balance(tx.sender, tx.gas_token_id) # (4) requires non-zero balance for transfer_token_id and gas_token_id if non-default for token_id in [tx.transfer_token_id, tx.gas_token_id]: if token_id != default_chain_token and bal[token_id] == 0: raise InvalidNativeToken( "{}: non-default token {} has zero balance".format( tx.__repr__(), token_id_decode(token_id))) # (5) the sender account balance contains at least the cost required in up-front payment cost = Counter({tx.transfer_token_id: tx.value}) + Counter( {tx.gas_token_id: tx.gasprice * tx.startgas}) for token_id, b in bal.items(): if b < cost[token_id]: raise InsufficientBalance( rp( tx, "token %s balance" % token_id_decode(token_id), b, cost[token_id], )) # (6) if gas token non-default, need to check system contract for gas conversion if tx.gasprice != 0 and tx.gas_token_id != default_chain_token: snapshot = state.snapshot() _, genesis_token_gas_price = pay_native_token_as_gas( state, tx.gas_token_id, tx.startgas, tx.gasprice) state.revert(snapshot) if genesis_token_gas_price == 0: raise InvalidNativeToken( "{}: non-default gas token {} not ready for being used to pay gas" .format(tx.__repr__(), token_id_decode(tx.gas_token_id))) # should be guaranteed by previous check. check added to make sure bal_gas_reserve = state.get_balance( SystemContract.GENERAL_NATIVE_TOKEN.addr(), state.genesis_token) if bal_gas_reserve < genesis_token_gas_price * tx.startgas: raise InvalidNativeToken( "{}: non-default gas token {} not enough reserve balance for conversion" .format(tx.__repr__(), token_id_decode(tx.gas_token_id))) # (7) check block gas limit if state.gas_used + tx.startgas > state.gas_limit: raise BlockGasLimitReached( rp(tx, "gaslimit", state.gas_used + tx.startgas, state.gas_limit)) return True
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 )