class Account(rlp.Serializable): def __init__(self, nonce, balance, storage, code_hash, full_shard_id, env, address, db=None): self.db = env.db if db is None else db assert isinstance(db, Db) self.env = env self.address = address acc = _Account(nonce, balance, storage, code_hash, full_shard_id) self.nonce = acc.nonce self.balance = acc.balance self.storage = acc.storage self.code_hash = acc.code_hash self.full_shard_id = acc.full_shard_id self.storage_cache = {} self.storage_trie = SecureTrie(Trie(self.db)) self.storage_trie.root_hash = self.storage self.touched = False self.existent_at_start = True self._mutable = True self.deleted = False def commit(self): for k, v in self.storage_cache.items(): if v: self.storage_trie.update(utils.encode_int32(k), rlp.encode(v)) else: self.storage_trie.delete(utils.encode_int32(k)) self.storage_cache = {} self.storage = self.storage_trie.root_hash @property def code(self): return self.db[self.code_hash] @code.setter def code(self, value): self.code_hash = utils.sha3(value) # Technically a db storage leak, but doesn't really matter; the only # thing that fails to get garbage collected is when code disappears due # to a suicide self.db.put(self.code_hash, value) def get_storage_data(self, key): if key not in self.storage_cache: v = self.storage_trie.get(utils.encode_int32(key)) self.storage_cache[key] = utils.big_endian_to_int( rlp.decode(v) if v else b"") return self.storage_cache[key] def set_storage_data(self, key, value): self.storage_cache[key] = value @classmethod def blank_account(cls, env, address, full_shard_id, initial_nonce=0, db=None): if db is None: db = env.db db.put(BLANK_HASH, b"") o = cls( initial_nonce, 0, trie.BLANK_ROOT, BLANK_HASH, full_shard_id, env, address, db=db, ) o.existent_at_start = False return o def is_blank(self): return self.nonce == 0 and self.balance == 0 and self.code_hash == BLANK_HASH @property def exists(self): if self.is_blank(): return self.touched or (self.existent_at_start and not self.deleted) return True def to_dict(self): odict = self.storage_trie.to_dict() for k, v in self.storage_cache.items(): odict[utils.encode_int(k)] = rlp.encode(utils.encode_int(v)) return { "balance": str(self.balance), "nonce": str(self.nonce), "code": "0x" + encode_hex(self.code), "storage": { "0x" + encode_hex(key.lstrip(b"\x00") or b"\x00"): "0x" + encode_hex(rlp.decode(val)) for key, val in odict.items() }, }
class TokenBalances: """Token balances including non-genesis native tokens.""" def __init__(self, data: bytes, db): self.token_trie = None # We don't want outside world to know this self._balances = {} self._db = db if len(data) != 0: enum = data[:1] if enum == b"\x00": for p in rlp.decode(data[1:], CountableList(TokenBalancePair)): self._balances[p.token_id] = p.balance elif enum == b"\x01": self.token_trie = SecureTrie(Trie(db, data[1:])) else: raise ValueError("Unknown enum byte in token_balances") def commit(self): # No-op if not using trie and token size is small if self._not_using_trie: return # Init trie if not already done so if not self.token_trie: self.token_trie = SecureTrie(Trie(self._db)) for token_id, bal in self._balances.items(): k = utils.encode_int32(token_id) if bal: self.token_trie.update(k, rlp.encode(bal)) else: self.token_trie.delete(k) self._balances = {} @property def _not_using_trie(self): return ( not self.token_trie and self._non_zero_entries_in_balance_cache <= TOKEN_TRIE_THRESHOLD ) def serialize(self): # Make sure is committed check(self._not_using_trie or (self.token_trie and self._balances == {})) # Return trie hash if possible if self.token_trie: return b"\x01" + self.token_trie.root_hash # Serialize in-memory balance representation as an array if len(self._balances) == 0: return b"" # Don't serialize 0 balance ls = [TokenBalancePair(k, v) for k, v in self._balances.items() if v > 0] # Sort by token id to make token balances serialization deterministic ls.sort(key=lambda b: b.token_id) return b"\x00" + rlp.encode(ls) def balance(self, token_id): if token_id in self._balances: return self._balances[token_id] if self.token_trie: v = self.token_trie.get(utils.encode_int32(token_id)) ret = utils.big_endian_to_int(rlp.decode(v)) if v else 0 if ret: # Only cache when non-zero self._balances[token_id] = ret return ret return 0 def set_balance(self, journal, token_id, value): preval = self.balance(token_id) self._balances[token_id] = value journal.append(lambda: self._balances.__setitem__(token_id, preval)) def is_blank(self): # If token tris exists, means either a) has non-zero balance or b) has non-zero nonce # Besides, account receiving 0 coins should still be regarded as blank return not self.token_trie and self._non_zero_entries_in_balance_cache == 0 def to_dict(self): if not self.token_trie: return self._balances # Iterating trie is costly. It's only for JSONRPC querying account information, which # can be improved later trie_dict = self.token_trie.to_dict() ret = { utils.big_endian_to_int(k): utils.big_endian_to_int(rlp.decode(v)) for k, v in trie_dict.items() } # Get latest update for k, v in self._balances.items(): if v == 0: ret.pop(k, None) else: ret[k] = v return ret def reset(self, journal): pre_balance = self._balances self._balances = {} journal.append(lambda: setattr(self, "_balance", pre_balance)) pre_token_trie = self.token_trie self.token_trie = None journal.append(lambda: setattr(self, "token_trie", pre_token_trie)) @property def _non_zero_entries_in_balance_cache(self): return sum(1 for bal in self._balances.values() if bal > 0)
class State: def __init__(self, root=BLANK_ROOT, env=Env(), qkc_config=None, executing_on_head=False, db=None, **kwargs): if db is None: db = env.db self.env = env self.__db = db self.trie = SecureTrie(Trie(self.db, root)) for k, v in STATE_DEFAULTS.items(): setattr(self, k, kwargs.get(k, copy.copy(v))) self.journal = [] self.cache = {} self.log_listeners = [] self.deletes = [] self.changed = {} self.executing_on_head = executing_on_head self.qkc_config = qkc_config @property def db(self): return self.__db @property def config(self): return self.env.config def get_block_hash(self, n): if self.block_number < n or n > 256 or n < 0: o = b"\x00" * 32 else: o = (self.prev_headers[n].get_hash() if self.prev_headers[n] else b"\x00" * 32) return o def add_block_header(self, block_header): self.prev_headers = [block_header] + self.prev_headers 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: o = rlp.decode(rlpdata, _Account) o = Account( nonce=o.nonce, balance=o.balance, storage=o.storage, code_hash=o.code_hash, full_shard_id=o.full_shard_id, env=self.env, address=address, db=self.db, ) else: o = Account.blank_account( self.env, address, self.full_shard_id, self.config["ACCOUNT_INITIAL_NONCE"], db=self.db, ) self.cache[address] = o o._mutable = True o._cached_rlp = None return o def get_balance(self, address): return self.get_and_cache_account( utils.normalize_address(address)).balance def get_code(self, address): return self.get_and_cache_account( utils.normalize_address(address)).code def get_nonce(self, address): return self.get_and_cache_account( utils.normalize_address(address)).nonce def get_full_shard_id(self, address): return self.get_and_cache_account( utils.normalize_address(address)).full_shard_id def set_and_journal(self, acct, param, val): # self.journal.append((acct, param, getattr(acct, param))) preval = getattr(acct, param) self.journal.append(lambda: setattr(acct, param, preval)) setattr(acct, param, val) def set_balance(self, address, value): acct = self.get_and_cache_account(utils.normalize_address(address)) self.set_and_journal(acct, "balance", value) self.set_and_journal(acct, "touched", True) def set_code(self, address, value): # assert is_string(value) acct = self.get_and_cache_account(utils.normalize_address(address)) self.set_and_journal(acct, "code", value) self.set_and_journal(acct, "touched", True) def set_nonce(self, address, value): acct = self.get_and_cache_account(utils.normalize_address(address)) self.set_and_journal(acct, "nonce", value) self.set_and_journal(acct, "touched", True) def delta_balance(self, address, value): address = utils.normalize_address(address) acct = self.get_and_cache_account(address) newbal = acct.balance + value self.set_and_journal(acct, "balance", newbal) self.set_and_journal(acct, "touched", True) def increment_nonce(self, address): address = utils.normalize_address(address) acct = self.get_and_cache_account(address) newnonce = acct.nonce + 1 self.set_and_journal(acct, "nonce", newnonce) self.set_and_journal(acct, "touched", True) def get_storage_data(self, address, key): return self.get_and_cache_account( utils.normalize_address(address)).get_storage_data(key) def set_storage_data(self, address, key, value): acct = self.get_and_cache_account(utils.normalize_address(address)) preval = acct.get_storage_data(key) acct.set_storage_data(key, value) self.journal.append(lambda: acct.set_storage_data(key, preval)) self.set_and_journal(acct, "touched", True) def add_suicide(self, address): self.suicides.append(address) self.journal.append(lambda: self.suicides.pop()) def add_log(self, log): for listener in self.log_listeners: listener(log) self.logs.append(log) self.journal.append(lambda: self.logs.pop()) def add_receipt(self, receipt): self.receipts.append(receipt) self.journal.append(lambda: self.receipts.pop()) def add_refund(self, value): preval = self.refunds self.refunds += value self.journal.append(lambda: setattr(self.refunds, preval)) def snapshot(self): return ( self.trie.root_hash, len(self.journal), {k: copy.copy(getattr(self, k)) for k in STATE_DEFAULTS}, ) def revert(self, snapshot): h, L, auxvars = snapshot # Compatibility with weird geth+parity bug three_touched = self.cache[ THREE].touched if THREE in self.cache else False while len(self.journal) > L: try: lastitem = self.journal.pop() lastitem() except Exception as e: print(e) if h != self.trie.root_hash: assert L == 0 self.trie.root_hash = h self.cache = {} for k in STATE_DEFAULTS: setattr(self, k, copy.copy(auxvars[k])) if (three_touched and 2675000 < self.block_number < 2675200): # Compatibility with weird geth+parity bug self.delta_balance(THREE, 0) def set_param(self, k, v): preval = getattr(self, k) self.journal.append(lambda: setattr(self, k, preval)) setattr(self, k, v) def is_SERENITY(self, at_fork_height=False): if at_fork_height: return self.block_number == self.config["SERENITY_FORK_BLKNUM"] else: return self.block_number >= self.config["SERENITY_FORK_BLKNUM"] def is_HOMESTEAD(self, at_fork_height=False): if at_fork_height: return self.block_number == self.config["HOMESTEAD_FORK_BLKNUM"] else: return self.block_number >= self.config["HOMESTEAD_FORK_BLKNUM"] def is_METROPOLIS(self, at_fork_height=False): if at_fork_height: return self.block_number == self.config["METROPOLIS_FORK_BLKNUM"] else: return self.block_number >= self.config["METROPOLIS_FORK_BLKNUM"] def is_CONSTANTINOPLE(self, at_fork_height=False): if at_fork_height: return self.block_number == self.config[ "CONSTANTINOPLE_FORK_BLKNUM"] else: return self.block_number >= self.config[ "CONSTANTINOPLE_FORK_BLKNUM"] def is_ANTI_DOS(self, at_fork_height=False): if at_fork_height: return self.block_number == self.config["ANTI_DOS_FORK_BLKNUM"] else: return self.block_number >= self.config["ANTI_DOS_FORK_BLKNUM"] def is_SPURIOUS_DRAGON(self, at_fork_height=False): if at_fork_height: return self.block_number == self.config[ "SPURIOUS_DRAGON_FORK_BLKNUM"] else: return self.block_number >= self.config[ "SPURIOUS_DRAGON_FORK_BLKNUM"] def is_DAO(self, at_fork_height=False): if at_fork_height: return self.block_number == self.config["DAO_FORK_BLKNUM"] else: return self.block_number >= self.config["DAO_FORK_BLKNUM"] def account_exists(self, address): if self.is_SPURIOUS_DRAGON(): o = not self.get_and_cache_account( utils.normalize_address(address)).is_blank() else: a = self.get_and_cache_account(address) if a.deleted and not a.touched: return False if a.touched: return True else: return a.existent_at_start return o def transfer_value(self, from_addr, to_addr, value): assert value >= 0 if self.get_balance(from_addr) >= value: self.delta_balance(from_addr, -value) self.delta_balance(to_addr, value) return True return False def deduct_value(self, from_addr, value): assert value >= 0 if self.get_balance(from_addr) >= value: self.delta_balance(from_addr, -value) return True return False def account_to_dict(self, address): return self.get_and_cache_account( utils.normalize_address(address)).to_dict() 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: _acct = _Account( acct.nonce, acct.balance, acct.storage, acct.code_hash, acct.full_shard_id, ) 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 to_dict(self): for addr in self.trie.to_dict().keys(): self.get_and_cache_account(addr) return { encode_hex(addr): acct.to_dict() for addr, acct in self.cache.items() } def del_account(self, address): self.set_balance(address, 0) self.set_nonce(address, 0) self.set_code(address, b"") self.reset_storage(address) self.set_and_journal( self.get_and_cache_account(utils.normalize_address(address)), "deleted", True, ) self.set_and_journal( self.get_and_cache_account(utils.normalize_address(address)), "touched", False, ) # self.set_and_journal(self.get_and_cache_account(utils.normalize_address(address)), 'existent_at_start', False) def reset_storage(self, address): acct = self.get_and_cache_account(address) pre_cache = acct.storage_cache acct.storage_cache = {} self.journal.append(lambda: setattr(acct, "storage_cache", pre_cache)) pre_root = acct.storage_trie.root_hash self.journal.append( lambda: setattr(acct.storage_trie, "root_hash", pre_root)) acct.storage_trie.root_hash = BLANK_ROOT # Creates a snapshot from a state def to_snapshot(self, root_only=False, no_prevblocks=False): snapshot = {} if root_only: # Smaller snapshot format that only includes the state root # (requires original DB to re-initialize) snapshot["state_root"] = "0x" + encode_hex(self.trie.root_hash) else: # "Full" snapshot snapshot["alloc"] = self.to_dict() # Save non-state-root variables for k, default in STATE_DEFAULTS.items(): default = copy.copy(default) v = getattr(self, k) if is_numeric(default): snapshot[k] = str(v) elif isinstance(default, (str, bytes)): snapshot[k] = "0x" + encode_hex(v) elif k == "prev_headers" and not no_prevblocks: snapshot[k] = [ prev_header_to_dict(h) for h in v[:self.config["PREV_HEADER_DEPTH"]] ] elif k == "recent_uncles" and not no_prevblocks: snapshot[k] = { str(n): ["0x" + encode_hex(h) for h in headers] for n, headers in v.items() } return snapshot # Creates a state from a snapshot @classmethod def from_snapshot(cls, snapshot_data, env, executing_on_head=False): state = State(env=env) if "alloc" in snapshot_data: for addr, data in snapshot_data["alloc"].items(): if len(addr) == 40: addr = decode_hex(addr) assert len(addr) == 20 if "wei" in data: state.set_balance(addr, parse_as_int(data["wei"])) if "balance" in data: state.set_balance(addr, parse_as_int(data["balance"])) if "code" in data: state.set_code(addr, parse_as_bin(data["code"])) if "nonce" in data: state.set_nonce(addr, parse_as_int(data["nonce"])) if "storage" in data: for k, v in data["storage"].items(): state.set_storage_data( addr, big_endian_to_int(parse_as_bin(k)), big_endian_to_int(parse_as_bin(v)), ) elif "state_root" in snapshot_data: state.trie.root_hash = parse_as_bin(snapshot_data["state_root"]) else: raise Exception( "Must specify either alloc or state root parameter") for k, default in STATE_DEFAULTS.items(): default = copy.copy(default) v = snapshot_data[k] if k in snapshot_data else None if is_numeric(default): setattr(state, k, parse_as_int(v) if k in snapshot_data else default) elif is_string(default): setattr(state, k, parse_as_bin(v) if k in snapshot_data else default) elif k == "prev_headers": if k in snapshot_data: headers = [dict_to_prev_header(h) for h in v] else: headers = default setattr(state, k, headers) elif k == "recent_uncles": if k in snapshot_data: uncles = {} for height, _uncles in v.items(): uncles[int(height)] = [] for uncle in _uncles: uncles[int(height)].append(parse_as_bin(uncle)) else: uncles = default setattr(state, k, uncles) if executing_on_head: state.executing_on_head = True state.commit() state.changed = {} return state def ephemeral_clone(self): snapshot = self.to_snapshot(root_only=True, no_prevblocks=True) env2 = Env(OverlayDb(self.db), self.env.config) s = State.from_snapshot(snapshot, env2) for param in STATE_DEFAULTS: setattr(s, param, getattr(self, param)) s.recent_uncles = self.recent_uncles s.prev_headers = self.prev_headers for acct in self.cache.values(): assert not acct.touched or not acct.deleted s.journal = copy.copy(self.journal) s.cache = {} s.qkc_config = self.qkc_config return s
class State: def __init__( self, root=BLANK_ROOT, env=Env(), qkc_config=None, executing_on_head=False, db=None, use_mock_evm_account=False, **kwargs ): if db is None: db = env.db self.env = env self.__db = db self.trie = SecureTrie(Trie(self.db, root)) for k, v in STATE_DEFAULTS.items(): setattr(self, k, kwargs.get(k, copy.copy(v))) self.journal = [] self.cache = {} self.log_listeners = [] self.deletes = [] self.changed = {} self.executing_on_head = executing_on_head self.qkc_config = qkc_config self.sender_disallow_map = dict() # type: Dict[bytes, int] self.shard_config = ShardConfig(ChainConfig()) self.use_mock_evm_account = use_mock_evm_account @property def db(self): return self.__db @property def config(self): return self.env.config def get_bloom(self): bloom = 0 for r in self.receipts: bloom |= r.bloom for r in self.xshard_deposit_receipts: bloom |= r.bloom return bloom def get_block_hash(self, n): if self.block_number < n or n > 256 or n < 0: o = b"\x00" * 32 else: o = ( self.prev_headers[n].get_hash() if self.prev_headers[n] else b"\x00" * 32 ) return o def add_block_header(self, block_header): self.prev_headers = [block_header] + self.prev_headers 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 get_balances(self, address) -> dict: return self.get_and_cache_account( utils.normalize_address(address) ).token_balances.to_dict() def get_balance(self, address, token_id=None): if token_id is None: token_id = self.shard_config.default_chain_token return self.get_and_cache_account( utils.normalize_address(address) ).token_balances.balance(token_id) def get_code(self, address): return self.get_and_cache_account(utils.normalize_address(address)).code def get_nonce(self, address): return self.get_and_cache_account(utils.normalize_address(address)).nonce def get_full_shard_key(self, address): return self.get_and_cache_account( utils.normalize_address(address) ).full_shard_key def set_and_journal(self, acct, param, val): # self.journal.append((acct, param, getattr(acct, param))) preval = getattr(acct, param) self.journal.append(lambda: setattr(acct, param, preval)) setattr(acct, param, val) def reset_balances(self, address): acct = self.get_and_cache_account(utils.normalize_address(address)) acct.token_balances.reset(self.journal) def set_code(self, address, value): # assert is_string(value) acct = self.get_and_cache_account(utils.normalize_address(address)) self.set_and_journal(acct, "code", value) self.set_and_journal(acct, "touched", True) def set_nonce(self, address, value): acct = self.get_and_cache_account(utils.normalize_address(address)) self.set_and_journal(acct, "nonce", value) self.set_and_journal(acct, "touched", True) def set_token_balance(self, address, token_id, val): acct = self.get_and_cache_account(utils.normalize_address(address)) if val == self.get_balance(address, token_id=token_id): self.set_and_journal(acct, "touched", True) return self._set_token_balance_and_journal(acct, token_id, val) self.set_and_journal(acct, "touched", True) def set_balance(self, address, val): self.set_token_balance( address, token_id=self.shard_config.default_chain_token, val=val ) def _set_token_balance_and_journal(self, acct, token_id, val): """if token_id was not set, journal will erase token_id when reverted """ acct.token_balances.set_balance(self.journal, token_id, val) def delta_token_balance(self, address, token_id, value): address = utils.normalize_address(address) acct = self.get_and_cache_account(address) if value == 0: self.set_and_journal(acct, "touched", True) return newbal = acct.token_balances.balance(token_id) + value self._set_token_balance_and_journal(acct, token_id, newbal) self.set_and_journal(acct, "touched", True) def increment_nonce(self, address): address = utils.normalize_address(address) acct = self.get_and_cache_account(address) newnonce = acct.nonce + 1 self.set_and_journal(acct, "nonce", newnonce) self.set_and_journal(acct, "touched", True) def get_storage_data(self, address, key): return self.get_and_cache_account( utils.normalize_address(address) ).get_storage_data(key) def set_storage_data(self, address, key, value): acct = self.get_and_cache_account(utils.normalize_address(address)) preval = acct.get_storage_data(key) acct.set_storage_data(key, value) self.journal.append(lambda: acct.set_storage_data(key, preval)) self.set_and_journal(acct, "touched", True) def add_suicide(self, address): self.suicides.append(address) self.journal.append(lambda: self.suicides.pop()) def add_log(self, log): for listener in self.log_listeners: listener(log) self.logs.append(log) self.journal.append(lambda: self.logs.pop()) def add_receipt(self, receipt): self.receipts.append(receipt) self.journal.append(lambda: self.receipts.pop()) def add_xshard_deposit_receipt(self, receipt): self.xshard_deposit_receipts.append(receipt) self.journal.append(lambda: self.xshard_deposit_receipts.pop()) def add_refund(self, value): preval = self.refunds self.refunds += value self.journal.append(lambda: setattr(self.refunds, preval)) def snapshot(self): return ( self.trie.root_hash, len(self.journal), {k: copy.copy(getattr(self, k)) for k in STATE_DEFAULTS}, ) def revert(self, snapshot): h, L, auxvars = snapshot # Compatibility with weird geth+parity bug three_touched = self.cache[THREE].touched if THREE in self.cache else False while len(self.journal) > L: try: lastitem = self.journal.pop() lastitem() except Exception as e: print(e) if h != self.trie.root_hash: assert L == 0 self.trie.root_hash = h self.cache = {} for k in STATE_DEFAULTS: setattr(self, k, copy.copy(auxvars[k])) if ( three_touched and 2675000 < self.block_number < 2675200 ): # Compatibility with weird geth+parity bug self.delta_token_balance(THREE, self.shard_config.default_chain_token, 0) def set_param(self, k, v): preval = getattr(self, k) self.journal.append(lambda: setattr(self, k, preval)) setattr(self, k, v) def account_exists(self, address): o = not self.get_and_cache_account(utils.normalize_address(address)).is_blank() return o def transfer_value(self, from_addr, to_addr, token_id, value): assert value >= 0 if self.get_balance(from_addr, token_id=token_id) >= value: self.delta_token_balance(from_addr, token_id, -value) self.delta_token_balance(to_addr, token_id, value) return True return False def deduct_value(self, from_addr, token_id, value): assert value >= 0 if self.get_balance(from_addr, token_id=token_id) >= value: self.delta_token_balance(from_addr, token_id, -value) return True return False def account_to_dict(self, address): return self.get_and_cache_account(utils.normalize_address(address)).to_dict() 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 to_dict(self): for addr in self.trie.to_dict().keys(): self.get_and_cache_account(addr) return {encode_hex(addr): acct.to_dict() for addr, acct in self.cache.items()} def del_account(self, address): self.reset_balances(address) self.set_nonce(address, 0) self.set_code(address, b"") self.reset_storage(address) self.set_and_journal( self.get_and_cache_account(utils.normalize_address(address)), "deleted", True, ) self.set_and_journal( self.get_and_cache_account(utils.normalize_address(address)), "touched", False, ) # self.set_and_journal(self.get_and_cache_account(utils.normalize_address(address)), 'existent_at_start', False) def reset_storage(self, address): acct = self.get_and_cache_account(address) pre_cache = acct.storage_cache acct.storage_cache = {} self.journal.append(lambda: setattr(acct, "storage_cache", pre_cache)) pre_root = acct.storage_trie.root_hash self.journal.append(lambda: setattr(acct.storage_trie, "root_hash", pre_root)) acct.storage_trie.root_hash = BLANK_ROOT # Creates a snapshot from a state def to_snapshot(self, no_prevblocks=False): snapshot = dict() snapshot["state_root"] = "0x" + encode_hex(self.trie.root_hash) # Save non-state-root variables for k, default in STATE_DEFAULTS.items(): default = copy.copy(default) v = getattr(self, k) if is_numeric(default): snapshot[k] = str(v) elif isinstance(default, (str, bytes)): snapshot[k] = "0x" + encode_hex(v) elif k == "prev_headers" and not no_prevblocks: snapshot[k] = [ prev_header_to_dict(h) for h in v[: self.config["PREV_HEADER_DEPTH"]] ] elif k == "recent_uncles" and not no_prevblocks: snapshot[k] = { str(n): ["0x" + encode_hex(h) for h in headers] for n, headers in v.items() } return snapshot # Creates a state from a snapshot @classmethod def from_snapshot(cls, snapshot_data, env, executing_on_head=False): state = State(env=env) if "state_root" in snapshot_data: state.trie.root_hash = parse_as_bin(snapshot_data["state_root"]) else: raise Exception("Must specify either alloc or state root parameter") for k, default in STATE_DEFAULTS.items(): default = copy.copy(default) v = snapshot_data[k] if k in snapshot_data else None if is_numeric(default): setattr(state, k, parse_as_int(v) if k in snapshot_data else default) elif is_string(default): setattr(state, k, parse_as_bin(v) if k in snapshot_data else default) elif k == "prev_headers": if k in snapshot_data: headers = [dict_to_prev_header(h) for h in v] else: headers = default setattr(state, k, headers) elif k == "recent_uncles": if k in snapshot_data: uncles = {} for height, _uncles in v.items(): uncles[int(height)] = [] for uncle in _uncles: uncles[int(height)].append(parse_as_bin(uncle)) else: uncles = default setattr(state, k, uncles) if executing_on_head: state.executing_on_head = True state.commit() state.changed = {} return state def ephemeral_clone(self): snapshot = self.to_snapshot(no_prevblocks=True) env2 = Env(OverlayDb(self.db), self.env.config) s = State.from_snapshot(snapshot, env2) for param in STATE_DEFAULTS: setattr(s, param, getattr(self, param)) s.recent_uncles = self.recent_uncles s.prev_headers = self.prev_headers for acct in self.cache.values(): assert not acct.touched or not acct.deleted s.journal = copy.copy(self.journal) s.cache = {} s.qkc_config = self.qkc_config s.sender_disallow_map = self.sender_disallow_map return s @property def genesis_token(self): return self.qkc_config.genesis_token