Closed rjl493456442 closed 1 month ago
I fully agree about the need for a StateReader. However:
*types.StateAccount
might be problematic. Handing out an object raises a question about ownership. If the upper layer modifies the object, will it 'punch through' to the underlying layers? If, through other channels, the balance of account X
is increased, will the value in the object that I'm holding, which represents X
, also increase? Therefore, maybe the interface should be more granular, e.g.
// StateReader defines the interface for accessing accounts or storage slots
// associated with a specific state.
type StateReader interface {
// StateRoot returns the state root that this reader originates from.
StateRoot() common.Hash
// AccountBalance returns the balance for the account at address addr
AccountBalance(addr common.Address) (*uint256.Int, error)
AccountNonce(addr common.Address) (uint64, error)
AccountRoot(addr common.Address) (common.Hash, error)
The existing statedb has the following methods. Perhaps a good simple first step would be to make these into an interface:
func (s *StateDB) Exist(addr common.Address) bool
// Empty returns whether the state object is either non-existent
// or empty according to the EIP161 specification (balance = nonce = code = 0)
func (s *StateDB) Empty(addr common.Address) bool
// GetBalance retrieves the balance from the given address or 0 if object not found
func (s *StateDB) GetBalance(addr common.Address) *uint256.Int
// GetNonce retrieves the nonce from the given address or 0 if object not found
func (s *StateDB) GetNonce(addr common.Address) uint64
// GetStorageRoot retrieves the storage root from the given address or empty
// if object not found.
func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash
func (s *StateDB) GetCode(addr common.Address) []byte
func (s *StateDB) GetCodeSize(addr common.Address) int
func (s *StateDB) GetCodeHash(addr common.Address) common.Hash
// GetState retrieves a value from the given account's storage trie.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash
// GetCommittedState retrieves a value from the given account's committed storage trie.
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash
func (s *StateDB) HasSelfDestructed(addr common.Address) bool
Statedb has two data sources for state access: Trie and Snapshot.
The availability of the state trie is always ensured, allowing it to be utilized for state access and the computation of the post-transition hash.
On the other hand, the availability of the state snapshot is not guaranteed. It can be used exclusively for state access, offering a more efficient method.
Now both trie and snapshot is hardcoded in statedb like
Unfortunately, this approach is not flexible enough. In path mode archive design, a new data source is required to access state from state history.
Besides, in the foreseeable future, state snapshot will be merged into underlying pathdb and a comprehensive reader will be offered which can either access node or state.
In light of these considerations, I propose the implementation of a
StateReader
abstraction to ensure the necessary flexibility.The
StateReader
interface will be something like thisAnd I can imagine a few corresponding implementations
LightReader
: the reader implemented by light client, a wrapper on top of on-demand-state-retrieverMerkleReader
: the reader for chain execution, which composed by state trie and optional state snapshotArchiveReader
: the reader for accessing historic states, a wrapper on top of state historyVerkleReader
: the reader for chain execution, but in the manner of verkle. It's also composed by verkle trie and verkle state snapshot