def commit_state(self): """Commit account caches""" """Write the acount caches on the corresponding tries.""" changes = [] if len(self.journal) == 0: # log_state.trace('delta', changes=[]) return addresses = sorted(list(self.caches['all'].keys())) for addr in addresses: acct = self._get_acct(addr) # storage for field in ('balance', 'nonce', 'code', 'storage'): if addr in self.caches[field]: v = self.caches[field][addr] changes.append([field, addr, v]) setattr(acct, field, v) t = SecureTrie(Trie(self.db, acct.storage)) for k, v in self.caches.get(b'storage:' + addr, {}).items(): enckey = utils.zpad(utils.coerce_to_bytes(k), 32) val = rlp.encode(v) changes.append(['storage', addr, k, v]) if v: t.update(enckey, val) else: t.delete(enckey) acct.storage = t.root_hash self.state.update(addr, rlp.encode(acct)) log_state.trace('delta', changes=changes) self.reset_cache() self.db.put(b'validated:' + self.hash, '1')
class Account(): def __init__(self, nonce, balance, storage, code_hash, env, address): assert isinstance(env.db, BaseDB) self.env = env self.address = address acc = _Account(nonce, balance, storage, code_hash) self.nonce = acc.nonce self.balance = acc.balance self.storage = acc.storage self.code_hash = acc.code_hash self.storage_cache = {} self.storage_trie = SecureTrie(Trie(RefcountDB(self.env.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.env.db.get(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.env.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, initial_nonce=0): env.db.put(BLANK_HASH, b'') o = cls(initial_nonce, 0, trie.BLANK_ROOT, BLANK_HASH, env, address) 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 State(): def __init__(self, root=b'', env=Env(), executing_on_head=False, **kwargs): self.env = env self.trie = SecureTrie(Trie(RefcountDB(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 @property def db(self): return self.env.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].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.get(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, env=self.env, address=address ) else: o = Account.blank_account( self.env, address, self.config['ACCOUNT_INITIAL_NONCE']) 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 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 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) 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.delete(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.env.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 = {} return s
class Block(rlp.Serializable): """A block. All attributes from the block header are accessible via properties (i.e. ``block.prevhash`` is equivalent to ``block.header.prevhash``). It is ensured that no discrepancies between header and block occur. :param header: the block header :param transaction_list: a list of transactions which are replayed if the state given by the header is not known. If the state is known, `None` can be used instead of the empty list. :param uncles: a list of the headers of the uncles of this block :param db: the database in which the block's state, transactions and receipts are stored (required) :param parent: optional parent which if not given may have to be loaded from the database for replay """ fields = [ ('header', BlockHeader), ('transaction_list', CountableList(Transaction)), ('uncles', CountableList(BlockHeader)) ] def __init__(self, header, transaction_list=[], uncles=[], db=None, parent=None, making=False): if db is None: raise TypeError("No database object given") self.db = db self.header = header self.uncles = uncles self.uncles = uncles self.suicides = [] self.logs = [] self.log_listeners = [] self.refunds = 0 self.ether_delta = 0 # Journaling cache for state tree updates self.caches = { 'balance': {}, 'nonce': {}, 'code': {}, 'storage': {}, 'all': {} } self.journal = [] if self.number > 0: self.ancestor_hashes = [self.prevhash] else: self.ancestor_hashes = [None] * 256 # do some consistency checks on parent if given if parent: if hasattr(parent, 'db') and self.db != parent.db: raise ValueError("Parent lives in different database") if self.prevhash != parent.header.hash: raise ValueError("Block's prevhash and parent's hash do not match") if self.number != parent.header.number + 1: raise ValueError("Block's number is not the successor of its parent number") if not check_gaslimit(parent, self.gas_limit): raise ValueError("Block's gaslimit is inconsistent with its parent's gaslimit") if self.difficulty != calc_difficulty(parent, self.timestamp): raise ValueError("Block's difficulty is inconsistent with its parent's difficulty") for uncle in uncles: assert isinstance(uncle, BlockHeader) original_values = { 'gas_used': header.gas_used, 'timestamp': header.timestamp, 'difficulty': header.difficulty, 'uncles_hash': header.uncles_hash, 'bloom': header.bloom, } self.transactions = Trie(db, trie.BLANK_ROOT) self.receipts = Trie(db, trie.BLANK_ROOT) # replay transactions if state is unknown state_unknown = (header.prevhash != GENESIS_PREVHASH and header.state_root != trie.BLANK_ROOT and (len(header.state_root) != 32 or b'validated:' + self.hash not in db) and not making) if state_unknown: assert transaction_list is not None if not parent: parent = self.get_parent_header() self.state = SecureTrie(Trie(db, parent.state_root)) self.transaction_count = 0 self.gas_used = 0 # replay for tx in transaction_list: success, output = processblock.apply_transaction(self, tx) self.finalize() else: # trust the state root in the header self.state = SecureTrie(Trie(self.db, header._state_root)) self.transaction_count = 0 if transaction_list: for tx in transaction_list: self.add_transaction_to_list(tx) if self.transactions.root_hash != header.tx_list_root: raise ValueError("Transaction list root hash does not match") # receipts trie populated by add_transaction_to_list is incorrect # (it doesn't know intermediate states), so reset it self.receipts = Trie(self.db, header.receipts_root) if self.number < 40000: assert len(self.transactions) == 0 # checks ############################## def must(what, f, symb, a, b): if not f(a, b): if dump_block_on_failed_verification: sys.stderr.write('%r' % self.to_dict()) raise VerificationFailed(what, a, symb, b) def must_equal(what, a, b): return must(what, lambda x, y: x == y, "==", a, b) def must_ge(what, a, b): return must(what, lambda x, y: x >= y, ">=", a, b) def must_le(what, a, b): return must(what, lambda x, y: x <= y, "<=", a, b) if parent: must_equal('prev_hash', self.prevhash, parent.hash) must_ge('gas_limit', self.gas_limit, parent.gas_limit * (GASLIMIT_ADJMAX_FACTOR - 1) // GASLIMIT_ADJMAX_FACTOR) must_le('gas_limit', self.gas_limit, parent.gas_limit * (GASLIMIT_ADJMAX_FACTOR + 1) // GASLIMIT_ADJMAX_FACTOR) must_equal('gas_used', original_values['gas_used'], self.gas_used) must_equal('timestamp', self.timestamp, original_values['timestamp']) must_equal('difficulty', self.difficulty, original_values['difficulty']) must_equal('uncles_hash', utils.sha3(rlp.encode(uncles)), original_values['uncles_hash']) assert header.block is None must_equal('state_root', self.state.root_hash, header.state_root) must_equal('tx_list_root', self.transactions.root_hash, header.tx_list_root) must_equal('receipts_root', self.receipts.root_hash, header.receipts_root) must_equal('bloom', self.bloom, original_values['bloom']) # from now on, trie roots refer to block instead of header header.block = self # Basic consistency verifications if not self.check_fields(): raise ValueError("Block is invalid") if len(self.header.extra_data) > 1024: raise ValueError("Extra data cannot exceed 1024 bytes") if self.header.coinbase == '': raise ValueError("Coinbase cannot be empty address") if not self.state.root_hash_valid(): raise ValueError("State Merkle root of block %r not found in " "database" % self) if (not self.is_genesis() and self.nonce and not self.header.check_pow()): raise ValueError("PoW check failed") self.db.put(b'validated:' + self.hash, '1') @classmethod def init_from_header(cls, header_rlp, db): """Create a block without specifying transactions or uncles. :param header_rlp: the RLP encoded block header :param db: the database for the block """ header = rlp.decode(header_rlp, BlockHeader, db=db) return cls(header, None, [], db=db) @classmethod def init_from_parent(cls, parent, coinbase, nonce=b'', extra_data=b'', timestamp=int(time.time()), uncles=[]): """Create a new block based on a parent block. The block will not include any transactions and will not be finalized. """ header = BlockHeader(prevhash=parent.hash, uncles_hash=utils.sha3(rlp.encode(uncles)), coinbase=coinbase, state_root=parent.state_root, tx_list_root=trie.BLANK_ROOT, receipts_root=trie.BLANK_ROOT, bloom=0, difficulty=calc_difficulty(parent, timestamp), mixhash='', number=parent.number + 1, gas_limit=calc_gaslimit(parent), gas_used=0, timestamp=timestamp, extra_data=extra_data, nonce=nonce) block = Block(header, [], uncles, db=parent.db, parent=parent, making=True) block.ancestor_hashes = [parent.hash] + parent.ancestor_hashes return block def check_fields(self): """Check that the values of all fields are well formed.""" # serialize and deserialize and check that the values didn't change l = Block.serialize(self) return rlp.decode(rlp.encode(l)) == l @property def hash(self): """The binary block hash This is equivalent to ``header.hash``. """ return utils.sha3(rlp.encode(self.header)) def hex_hash(self): """The hex encoded block hash. This is equivalent to ``header.hex_hash(). """ return encode_hex(self.hash) @property def tx_list_root(self): return self.transactions.root_hash @tx_list_root.setter def tx_list_root(self, value): self.transactions = Trie(self.db, value) @property def receipts_root(self): return self.receipts.root_hash @receipts_root.setter def receipts_root(self, value): self.receipts = Trie(self.db, value) @property def state_root(self): self.commit_state() return self.state.root_hash @state_root.setter def state_root(self, value): self.state = SecureTrie(Trie(self.db, value)) self.reset_cache() @property def uncles_hash(self): return utils.sha3(rlp.encode(self.uncles)) @property def transaction_list(self): txs = [] for i in range(self.transaction_count): txs.append(self.get_transaction(i)) return txs def validate_uncles(self): """Validate the uncles of this block.""" if utils.sha3(rlp.encode(self.uncles)) != self.uncles_hash: return False if len(self.uncles) > MAX_UNCLES: return False for uncle in self.uncles: assert uncle.prevhash in self.db if uncle.number == self.number: log.error("uncle at same block height", block=self) return False # Check uncle validity ancestor_chain = [self] + [a for a in self.get_ancestor_list(MAX_UNCLE_DEPTH + 1) if a] assert len(ancestor_chain) == min(self.header.number + 1, MAX_UNCLE_DEPTH + 2) ineligible = [] # Uncles of this block cannot be direct ancestors and cannot also # be uncles included 1-6 blocks ago for ancestor in ancestor_chain[1:]: ineligible.extend(ancestor.uncles) ineligible.extend([b.header for b in ancestor_chain]) eligible_ancestor_hashes = [x.hash for x in ancestor_chain[2:]] for uncle in self.uncles: if not uncle.check_pow(): return False if uncle.prevhash not in eligible_ancestor_hashes: log.error("Uncle does not have a valid ancestor", block=self, eligible=[x.encode('hex') for x in eligible_ancestor_hashes], uncle_prevhash=uncle.prevhash.encode('hex')) return False if uncle in ineligible: log.error("Duplicate uncle", block=self, uncle=encode_hex(utils.sha3(rlp.encode(uncle)))) return False ineligible.append(uncle) return True def get_ancestor_list(self, n): """Return `n` ancestors of this block. :returns: a list [p(self), p(p(self)), ..., p^n(self)] """ if n == 0 or self.header.number == 0: return [] p = self.get_parent() return [p] + p.get_ancestor_list(n-1) def get_ancestor_hash(self, n): assert n > 0 while len(self.ancestor_hashes) < n: if self.number == len(self.ancestor_hashes) - 1: self.ancestor_hashes.append(None) else: self.ancestor_hashes.append( get_block(self.db, self.ancestor_hashes[-1]).get_parent().hash) return self.ancestor_hashes[n-1] def get_ancestor(self, n): return self.get_block(self.get_ancestor_hash(n)) def is_genesis(self): """`True` if this block is the genesis block, otherwise `False`.""" return self.header.number == 0 def _get_acct(self, address): """Get the account with the given address. Note that this method ignores cached account items. """ if len(address) == 40: address = decode_hex(address) assert len(address) == 20 or len(address) == 0 rlpdata = self.state.get(address) if rlpdata != trie.BLANK_NODE: acct = rlp.decode(rlpdata, Account, db=self.db) else: acct = Account.blank_account(self.db) return acct def _get_acct_item(self, address, param): """Get a specific parameter of a specific account. :param address: the address of the account (binary or hex string) :param param: the requested parameter (`'nonce'`, `'balance'`, `'storage'` or `'code'`) """ if len(address) == 40: address = decode_hex(address) assert len(address) == 20 or len(address) == 0 if address in self.caches[param]: return self.caches[param][address] else: account = self._get_acct(address) o = getattr(account, param) self.caches[param][address] = o return o def _set_acct_item(self, address, param, value): """Set a specific parameter of a specific account. :param address: the address of the account (binary or hex string) :param param: the requested parameter (`'nonce'`, `'balance'`, `'storage'` or `'code'`) :param value: the new value """ if len(address) == 40: address = decode_hex(address) assert len(address) == 20 self.set_and_journal(param, address, value) self.set_and_journal('all', address, True) def set_and_journal(self, cache, index, value): prev = self.caches[cache].get(index, None) if prev != value: self.journal.append([cache, index, prev, value]) self.caches[cache][index] = value def _delta_item(self, address, param, value): """Add a value to an account item. If the resulting value would be negative, it is left unchanged and `False` is returned. :param address: the address of the account (binary or hex string) :param param: the parameter to increase or decrease (`'nonce'`, `'balance'`, `'storage'` or `'code'`) :param value: can be positive or negative :returns: `True` if the operation was successful, `False` if not """ new_value = self._get_acct_item(address, param) + value if new_value < 0: return False self._set_acct_item(address, param, new_value % 2**256) return True def mk_transaction_receipt(self, tx): """Create a receipt for a transaction.""" return Receipt(self.state_root, self.gas_used, self.logs) def add_transaction_to_list(self, tx): """Add a transaction to the transaction trie. Note that this does not execute anything, i.e. the state is not updated. """ k = rlp.encode(self.transaction_count) self.transactions.update(k, rlp.encode(tx)) r = self.mk_transaction_receipt(tx) self.receipts.update(k, rlp.encode(r)) self.bloom |= r.bloom # int self.transaction_count += 1 def get_transaction(self, num): """Get the `num`th transaction in this block. :raises: :exc:`IndexError` if the transaction does not exist """ index = rlp.encode(num) tx = self.transactions.get(index) if tx == trie.BLANK_NODE: raise IndexError('Transaction does not exist') else: return rlp.decode(tx, Transaction) _get_transactions_cache = None def get_transactions(self): """Build a list of all transactions in this block.""" num = self.transaction_count if not self._get_transactions_cache or len(self._get_transactions_cache) != num: txs = [] for i in range(num): txs.append(self.get_transaction(i)) self._get_transactions_cache = txs return self._get_transactions_cache def get_transaction_hashes(self): "helper to check if blk contains a tx" return [utils.sha3(self.transactions.get(rlp.encode(i))) for i in range(self.transaction_count)] def includes_transaction(self, tx_hash): assert isinstance(tx_hash, bytes) #assert self.get_transaction_hashes() == [tx.hash for tx in self.get_transactions()] return tx_hash in self.get_transaction_hashes() def get_receipt(self, num): """Get the receipt of the `num`th transaction. :returns: an instance of :class:`Receipt` """ index = rlp.encode(num) receipt = self.receipts.get(index) if receipt == trie.BLANK_NODE: raise IndexError('Receipt does not exist') else: return rlp.decode(receipt, Receipt) def get_receipts(self): """Build a list of all receipts in this block.""" receipts = [] for i in count(): try: receipts.append(self.get_receipt(i)) except IndexError: return receipts def get_nonce(self, address): """Get the nonce of an account. :param address: the address of the account (binary or hex string) """ return self._get_acct_item(address, 'nonce') def set_nonce(self, address, value): """Set the nonce of an account. :param address: the address of the account (binary or hex string) :param value: the new nonce :returns: `True` if successful, otherwise `False` """ return self._set_acct_item(address, 'nonce', value) def increment_nonce(self, address): """Increment the nonce of an account. :param address: the address of the account (binary or hex string) :returns: `True` if successful, otherwise `False` """ return self._delta_item(address, 'nonce', 1) def decrement_nonce(self, address): """Decrement the nonce of an account. :param address: the address of the account (binary or hex string) :returns: `True` if successful, otherwise `False` """ return self._delta_item(address, 'nonce', -1) def get_balance(self, address): """Get the balance of an account. :param address: the address of the account (binary or hex string) """ return self._get_acct_item(address, 'balance') def set_balance(self, address, value): """Set the balance of an account. :param address: the address of the account (binary or hex string) :param value: the new balance :returns: `True` if successful, otherwise `False` """ self._set_acct_item(address, 'balance', value) def delta_balance(self, address, value): """Increase the balance of an account. :param address: the address of the account (binary or hex string) :param value: can be positive or negative :returns: `True` if successful, otherwise `False` """ return self._delta_item(address, 'balance', value) def transfer_value(self, from_addr, to_addr, value): """Transfer a value between two account balances. :param from_addr: the address of the sending account (binary or hex string) :param to_addr: the address of the receiving account (binary or hex string) :param value: the (positive) value to send :returns: `True` if successful, otherwise `False` """ assert value >= 0 if self.delta_balance(from_addr, -value): return self.delta_balance(to_addr, value) return False def get_code(self, address): """Get the code of an account. :param address: the address of the account (binary or hex string) """ return self._get_acct_item(address, 'code') def set_code(self, address, value): """Set the code of an account. :param address: the address of the account (binary or hex string) :param value: the new code :returns: `True` if successful, otherwise `False` """ self._set_acct_item(address, 'code', value) def get_storage(self, address): """Get the trie holding an account's storage. :param address: the address of the account (binary or hex string) :param value: the new code """ storage_root = self._get_acct_item(address, 'storage') return SecureTrie(Trie(self.db, storage_root)) def reset_storage(self, address): self._set_acct_item(address, 'storage', b'') CACHE_KEY = b'storage:' + address if CACHE_KEY in self.caches: for k in self.caches[CACHE_KEY]: self.set_and_journal(CACHE_KEY, k, 0) def get_storage_data(self, address, index): """Get a specific item in the storage of an account. :param address: the address of the account (binary or hex string) :param index: the index of the requested item in the storage """ if len(address) == 40: address = decode_hex(address) assert len(address) == 20 CACHE_KEY = b'storage:' + address if CACHE_KEY in self.caches: if index in self.caches[CACHE_KEY]: return self.caches[CACHE_KEY][index] key = utils.zpad(utils.coerce_to_bytes(index), 32) storage = self.get_storage(address).get(key) if storage: return rlp.decode(storage, big_endian_int) else: return 0 def set_storage_data(self, address, index, value): """Set a specific item in the storage of an account. :param address: the address of the account (binary or hex string) :param index: the index of the item in the storage :param value: the new value of the item """ if len(address) == 40: address = decode_hex(address) assert len(address) == 20 CACHE_KEY = b'storage:' + address if CACHE_KEY not in self.caches: self.caches[CACHE_KEY] = {} self.set_and_journal('all', address, True) self.set_and_journal(CACHE_KEY, index, value) def account_exists(self, address): if len(address) == 40: address = decode_hex(address) assert len(address) == 20 return len(self.state.get(address)) > 0 or address in self.caches['all'] def add_log(self, log): self.logs.append(log) for L in self.log_listeners: L(log) def commit_state(self): """Commit account caches""" """Write the acount caches on the corresponding tries.""" changes = [] if len(self.journal) == 0: # log_state.trace('delta', changes=[]) return addresses = sorted(list(self.caches['all'].keys())) for addr in addresses: acct = self._get_acct(addr) # storage for field in ('balance', 'nonce', 'code', 'storage'): if addr in self.caches[field]: v = self.caches[field][addr] changes.append([field, addr, v]) setattr(acct, field, v) t = SecureTrie(Trie(self.db, acct.storage)) for k, v in self.caches.get(b'storage:' + addr, {}).items(): enckey = utils.zpad(utils.coerce_to_bytes(k), 32) val = rlp.encode(v) changes.append(['storage', addr, k, v]) if v: t.update(enckey, val) else: t.delete(enckey) acct.storage = t.root_hash self.state.update(addr, rlp.encode(acct)) log_state.trace('delta', changes=changes) self.reset_cache() self.db.put(b'validated:' + self.hash, '1') def del_account(self, address): """Delete an account. :param address: the address of the account (binary or hex string) """ if len(address) == 40: address = decode_hex(address) assert len(address) == 20 self.commit_state() self.state.delete(address) def account_to_dict(self, address, with_storage_root=False, with_storage=True): """Serialize an account to a dictionary with human readable entries. :param address: the 20 bytes account address :param with_storage_root: include the account's storage root :param with_storage: include the whole account's storage """ if len(address) == 40: address = decode_hex(address) assert len(address) == 20 if with_storage_root: # if there are uncommited account changes the current storage root # is meaningless assert len(self.journal) == 0 med_dict = {} account = self._get_acct(address) for field in ('balance', 'nonce'): value = self.caches[field].get(address, getattr(account, field)) med_dict[field] = to_string(value) code = self.caches['code'].get(address, account.code) med_dict['code'] = b'0x' + encode_hex(code) storage_trie = SecureTrie(Trie(self.db, account.storage)) if with_storage_root: med_dict['storage_root'] = encode_hex(storage_trie.get_root_hash()) if with_storage: med_dict['storage'] = {} d = storage_trie.to_dict() subcache = self.caches.get(b'storage:' + address, {}) subkeys = [utils.zpad(utils.coerce_to_bytes(kk), 32) for kk in list(subcache.keys())] for k in list(d.keys()) + subkeys: v = d.get(k, None) v2 = subcache.get(utils.big_endian_to_int(k), None) hexkey = b'0x' + encode_hex(utils.zunpad(k)) if v2 is not None: if v2 != 0: med_dict['storage'][hexkey] = \ b'0x' + encode_hex(utils.int_to_big_endian(v2)) elif v is not None: med_dict['storage'][hexkey] = b'0x' + encode_hex(rlp.decode(v)) return med_dict def reset_cache(self): """Reset cache and journal without commiting any changes.""" self.caches = { 'all': {}, 'balance': {}, 'nonce': {}, 'code': {}, 'storage': {}, } self.journal = [] def snapshot(self): """Make a snapshot of the current state to enable later reverting.""" return { 'state': self.state.root_hash, 'gas': self.gas_used, 'txs': self.transactions, 'txcount': self.transaction_count, 'suicides': self.suicides, 'logs': self.logs, 'refunds': self.refunds, 'suicides_size': len(self.suicides), 'logs_size': len(self.logs), 'journal': self.journal, # pointer to reference, so is not static 'journal_size': len(self.journal), 'ether_delta': self.ether_delta } def revert(self, mysnapshot): """Revert to a previously made snapshot. Reverting is for example necessary when a contract runs out of gas during execution. """ self.journal = mysnapshot['journal'] log_state.trace('reverting') while len(self.journal) > mysnapshot['journal_size']: cache, index, prev, post = self.journal.pop() log_state.trace('%r %r %r %r' % (cache, index, prev, post)) if prev is not None: self.caches[cache][index] = prev else: del self.caches[cache][index] self.suicides = mysnapshot['suicides'] while len(self.suicides) > mysnapshot['suicides_size']: self.suicides.pop() self.logs = mysnapshot['logs'] while len(self.logs) > mysnapshot['logs_size']: self.logs.pop() self.refunds = mysnapshot['refunds'] self.state.root_hash = mysnapshot['state'] self.gas_used = mysnapshot['gas'] self.transactions = mysnapshot['txs'] self.transaction_count = mysnapshot['txcount'] self._get_transactions_cache = None self.ether_delta = mysnapshot['ether_delta'] def finalize(self): """Apply rewards and commit.""" delta = int(BLOCK_REWARD + NEPHEW_REWARD * len(self.uncles)) self.delta_balance(self.coinbase, delta) self.ether_delta += delta for uncle in self.uncles: r = BLOCK_REWARD * \ (UNCLE_DEPTH_PENALTY_FACTOR + uncle.number - self.number) \ / UNCLE_DEPTH_PENALTY_FACTOR r = int(r) self.delta_balance(uncle.coinbase, r) self.ether_delta += r self.commit_state() def to_dict(self, with_state=False, full_transactions=False, with_storage_roots=False, with_uncles=False): """Serialize the block to a readable dictionary. :param with_state: include state for all accounts :param full_transactions: include serialized transactions (hashes otherwise) :param with_storage_roots: if account states are included also include their storage roots :param with_uncles: include uncle hashes """ b = {"header": self.header.to_dict()} txlist = [] for i, tx in enumerate(self.get_transactions()): receipt_rlp = self.receipts.get(rlp.encode(i)) receipt = rlp.decode(receipt_rlp, Receipt) if full_transactions: txjson = tx.to_dict() else: txjson = tx.hash txlist.append({ "tx": txjson, "medstate": encode_hex(receipt.state_root), "gas": to_string(receipt.gas_used), "logs": [Log.serialize(log) for log in receipt.logs], "bloom": utils.int256.serialize(receipt.bloom) }) b["transactions"] = txlist if with_state: state_dump = {} for address, v in self.state.to_dict().items(): state_dump[encode_hex(address)] = self.account_to_dict(address, with_storage_roots) b['state'] = state_dump if with_uncles: b['uncles'] = [self.__class__.deserialize_header(u) for u in self.uncles] return b @property def mining_hash(self): return utils.sha3(rlp.encode(self.header, BlockHeader.exclude(['nonce', 'mixhash']))) def get_parent(self): """Get the parent of this block.""" if self.number == 0: raise UnknownParentException('Genesis block has no parent') try: parent = get_block(self.db, self.prevhash) except KeyError: raise UnknownParentException(encode_hex(self.prevhash)) # assert parent.state.db.db == self.state.db.db return parent def get_parent_header(self): """Get the parent of this block.""" if self.number == 0: raise UnknownParentException('Genesis block has no parent') try: parent_header = get_block_header(self.db, self.prevhash) except KeyError: raise UnknownParentException(encode_hex(self.prevhash)) # assert parent.state.db.db == self.state.db.db return parent_header def has_parent(self): """`True` if this block has a known parent, otherwise `False`.""" try: self.get_parent() return True except UnknownParentException: return False def chain_difficulty(self): """Get the summarized difficulty. If the summarized difficulty is not stored in the database, it will be calculated recursively and put in the database. """ if self.is_genesis(): return self.difficulty elif b'difficulty:' + encode_hex(self.hash) in self.db: encoded = self.db.get(b'difficulty:' + encode_hex(self.hash)) return utils.decode_int(encoded) else: o = self.difficulty + self.get_parent().chain_difficulty() # o += sum([uncle.difficulty for uncle in self.uncles]) self.state.db.put(b'difficulty:' + encode_hex(self.hash), utils.encode_int(o)) return o return rlp.decode(rlp.encode(l)) == l def __eq__(self, other): """Two blocks are equal iff they have the same hash.""" return isinstance(other, (Block, CachedBlock)) and self.hash == other.hash def __hash__(self): return utils.big_endian_to_int(self.hash) def __ne__(self, other): return not self.__eq__(other) def __gt__(self, other): return self.number > other.number def __lt__(self, other): return self.number < other.number def __repr__(self): return '<%s(#%d %s)>' % (self.__class__.__name__, self.number, encode_hex(self.hash)[:8]) def __structlog__(self): return encode_hex(self.hash)