mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 01:41:36 +00:00
core/state: export StateUpdate struct
This commit is contained in:
parent
5af5510b1e
commit
028ccebd23
13 changed files with 428 additions and 350 deletions
|
|
@ -70,7 +70,7 @@ type Database interface {
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
Commit(update *stateUpdate) error
|
Commit(update *StateUpdate) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trie is a Ethereum Merkle Patricia trie.
|
// Trie is a Ethereum Merkle Patricia trie.
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,7 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
func (db *HistoricDB) Commit(update *stateUpdate) error {
|
func (db *HistoricDB) Commit(update *StateUpdate) error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
)
|
)
|
||||||
|
|
@ -57,9 +58,9 @@ type AccountIterator interface {
|
||||||
// An error will be returned if the preimage is not available.
|
// An error will be returned if the preimage is not available.
|
||||||
Address() (common.Address, error)
|
Address() (common.Address, error)
|
||||||
|
|
||||||
// Account returns the RLP encoded account the iterator is currently at.
|
// Account returns the account the iterator is currently at.
|
||||||
// An error will be retained if the iterator becomes invalid.
|
// An error will be retained if the iterator becomes invalid.
|
||||||
Account() []byte
|
Account() *types.StateAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageIterator is an iterator to step over the specific storage in the
|
// StorageIterator is an iterator to step over the specific storage in the
|
||||||
|
|
@ -73,7 +74,7 @@ type StorageIterator interface {
|
||||||
|
|
||||||
// Slot returns the storage slot the iterator is currently at. An error will
|
// Slot returns the storage slot the iterator is currently at. An error will
|
||||||
// be retained if the iterator becomes invalid.
|
// be retained if the iterator becomes invalid.
|
||||||
Slot() []byte
|
Slot() common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iteratee wraps the NewIterator methods for traversing the accounts and
|
// Iteratee wraps the NewIterator methods for traversing the accounts and
|
||||||
|
|
@ -131,10 +132,7 @@ func (ai *flatAccountIterator) Next() bool {
|
||||||
// Error returns any failure that occurred during iteration, which might have
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
// caused a premature iteration exit.
|
// caused a premature iteration exit.
|
||||||
func (ai *flatAccountIterator) Error() error {
|
func (ai *flatAccountIterator) Error() error {
|
||||||
if ai.err != nil {
|
return errors.Join(ai.err, ai.it.Error())
|
||||||
return ai.err
|
|
||||||
}
|
|
||||||
return ai.it.Error()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hash of the account or storage slot the iterator is
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
|
@ -165,8 +163,8 @@ func (ai *flatAccountIterator) Address() (common.Address, error) {
|
||||||
// Account returns the account data the iterator is currently at. The account
|
// Account returns the account data the iterator is currently at. The account
|
||||||
// data is encoded as slim format from the underlying iterator, the conversion
|
// data is encoded as slim format from the underlying iterator, the conversion
|
||||||
// is required.
|
// is required.
|
||||||
func (ai *flatAccountIterator) Account() []byte {
|
func (ai *flatAccountIterator) Account() *types.StateAccount {
|
||||||
data, err := types.FullAccountRLP(ai.it.Account())
|
data, err := types.FullAccount(ai.it.Account())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ai.err = err
|
ai.err = err
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -176,6 +174,7 @@ func (ai *flatAccountIterator) Account() []byte {
|
||||||
|
|
||||||
// flatStorageIterator is a wrapper around the underlying flat state iterator.
|
// flatStorageIterator is a wrapper around the underlying flat state iterator.
|
||||||
type flatStorageIterator struct {
|
type flatStorageIterator struct {
|
||||||
|
err error
|
||||||
it snapshot.StorageIterator
|
it snapshot.StorageIterator
|
||||||
preimage PreimageReader
|
preimage PreimageReader
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +195,7 @@ func (si *flatStorageIterator) Next() bool {
|
||||||
// Error returns any failure that occurred during iteration, which might have
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
// caused a premature iteration exit.
|
// caused a premature iteration exit.
|
||||||
func (si *flatStorageIterator) Error() error {
|
func (si *flatStorageIterator) Error() error {
|
||||||
return si.it.Error()
|
return errors.Join(si.err, si.it.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hash of the account or storage slot the iterator is
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
|
@ -225,14 +224,24 @@ func (si *flatStorageIterator) Key() (common.Hash, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slot returns the storage slot data the iterator is currently at.
|
// Slot returns the storage slot data the iterator is currently at.
|
||||||
func (si *flatStorageIterator) Slot() []byte {
|
func (si *flatStorageIterator) Slot() common.Hash {
|
||||||
return si.it.Slot()
|
// Perform the rlp-decode as the slot value is RLP-encoded
|
||||||
|
blob := si.it.Slot()
|
||||||
|
_, content, _, err := rlp.Split(blob)
|
||||||
|
if err != nil {
|
||||||
|
si.err = err
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
var value common.Hash
|
||||||
|
value.SetBytes(content)
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// merkleIterator implements the Iterator interface, providing functions to traverse
|
// merkleIterator implements the Iterator interface, providing functions to traverse
|
||||||
// the accounts or storages with the manner of Merkle-Patricia-Trie.
|
// the accounts or storages with the manner of Merkle-Patricia-Trie.
|
||||||
type merkleIterator struct {
|
type merkleIterator struct {
|
||||||
tr Trie
|
tr Trie
|
||||||
|
err error
|
||||||
it *trie.Iterator
|
it *trie.Iterator
|
||||||
account bool
|
account bool
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +269,7 @@ func (ti *merkleIterator) Next() bool {
|
||||||
// Error returns any failure that occurred during iteration, which might have
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
// caused a premature iteration exit.
|
// caused a premature iteration exit.
|
||||||
func (ti *merkleIterator) Error() error {
|
func (ti *merkleIterator) Error() error {
|
||||||
return ti.it.Err
|
return errors.Join(ti.err, ti.it.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hash of the account or storage slot the iterator is
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
|
@ -287,11 +296,16 @@ func (ti *merkleIterator) Address() (common.Address, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account returns the account data the iterator is currently at.
|
// Account returns the account data the iterator is currently at.
|
||||||
func (ti *merkleIterator) Account() []byte {
|
func (ti *merkleIterator) Account() *types.StateAccount {
|
||||||
if !ti.account {
|
if !ti.account {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return ti.it.Value
|
var account types.StateAccount
|
||||||
|
if err := rlp.DecodeBytes(ti.it.Value, &account); err != nil {
|
||||||
|
ti.err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &account
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key returns the raw storage slot key the iterator is currently at.
|
// Key returns the raw storage slot key the iterator is currently at.
|
||||||
|
|
@ -308,11 +322,19 @@ func (ti *merkleIterator) Key() (common.Hash, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slot returns the storage slot the iterator is currently at.
|
// Slot returns the storage slot the iterator is currently at.
|
||||||
func (ti *merkleIterator) Slot() []byte {
|
func (ti *merkleIterator) Slot() common.Hash {
|
||||||
if ti.account {
|
if ti.account {
|
||||||
return nil
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
return ti.it.Value
|
// Perform the rlp-decode as the slot value is RLP-encoded
|
||||||
|
_, content, _, err := rlp.Split(ti.it.Value)
|
||||||
|
if err != nil {
|
||||||
|
ti.err = err
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
var value common.Hash
|
||||||
|
value.SetBytes(content)
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// stateIteratee implements Iteratee interface, providing the state traversal
|
// stateIteratee implements Iteratee interface, providing the state traversal
|
||||||
|
|
@ -430,6 +452,6 @@ func (e exhaustedIterator) Key() (common.Hash, error) {
|
||||||
return common.Hash{}, nil
|
return common.Hash{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e exhaustedIterator) Slot() []byte {
|
func (e exhaustedIterator) Slot() common.Hash {
|
||||||
return nil
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -45,7 +44,7 @@ func TestExhaustedIterator(t *testing.T) {
|
||||||
if key, err := it.Key(); key != (common.Hash{}) || err != nil {
|
if key, err := it.Key(); key != (common.Hash{}) || err != nil {
|
||||||
t.Fatalf("Key() = %x, %v; want zero, nil", key, err)
|
t.Fatalf("Key() = %x, %v; want zero, nil", key, err)
|
||||||
}
|
}
|
||||||
if slot := it.Slot(); slot != nil {
|
if slot := it.Slot(); slot != (common.Hash{}) {
|
||||||
t.Fatalf("Slot() = %x, want nil", slot)
|
t.Fatalf("Slot() = %x, want nil", slot)
|
||||||
}
|
}
|
||||||
it.Release()
|
it.Release()
|
||||||
|
|
@ -95,20 +94,16 @@ func testAccountIterator(t *testing.T, scheme string) {
|
||||||
hashes = append(hashes, hash)
|
hashes = append(hashes, hash)
|
||||||
|
|
||||||
// Decode and verify account data.
|
// Decode and verify account data.
|
||||||
blob := acctIt.Account()
|
got := acctIt.Account()
|
||||||
if blob == nil {
|
if got == nil {
|
||||||
t.Fatalf("(%s) nil account at %x", scheme, hash)
|
t.Fatalf("(%s) nil account at %x", scheme, hash)
|
||||||
}
|
}
|
||||||
var decoded types.StateAccount
|
|
||||||
if err := rlp.DecodeBytes(blob, &decoded); err != nil {
|
|
||||||
t.Fatalf("(%s) bad RLP at %x: %v", scheme, hash, err)
|
|
||||||
}
|
|
||||||
acc := addrByHash[hash]
|
acc := addrByHash[hash]
|
||||||
if decoded.Nonce != acc.nonce {
|
if got.Nonce != acc.nonce {
|
||||||
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, decoded.Nonce, acc.nonce)
|
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, got.Nonce, acc.nonce)
|
||||||
}
|
}
|
||||||
if decoded.Balance.Cmp(acc.balance) != 0 {
|
if got.Balance.Cmp(acc.balance) != 0 {
|
||||||
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, decoded.Balance, acc.balance)
|
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, got.Balance, acc.balance)
|
||||||
}
|
}
|
||||||
// Verify address preimage resolution.
|
// Verify address preimage resolution.
|
||||||
addr, err := acctIt.Address()
|
addr, err := acctIt.Address()
|
||||||
|
|
@ -183,7 +178,7 @@ func testStorageIterator(t *testing.T, scheme string) {
|
||||||
t.Fatalf("(%s) storage hashes not ascending for %x", scheme, acc.address)
|
t.Fatalf("(%s) storage hashes not ascending for %x", scheme, acc.address)
|
||||||
}
|
}
|
||||||
prevHash = hash
|
prevHash = hash
|
||||||
if storageIt.Slot() == nil {
|
if storageIt.Slot() == (common.Hash{}) {
|
||||||
t.Fatalf("(%s) nil slot at %x", scheme, hash)
|
t.Fatalf("(%s) nil slot at %x", scheme, hash)
|
||||||
}
|
}
|
||||||
// Check key preimage resolution on first slot.
|
// Check key preimage resolution on first slot.
|
||||||
|
|
|
||||||
|
|
@ -140,35 +140,44 @@ func (db *MPTDatabase) TrieDB() *triedb.Database {
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
func (db *MPTDatabase) Commit(update *stateUpdate) error {
|
func (db *MPTDatabase) Commit(update *StateUpdate) error {
|
||||||
// Short circuit if nothing to commit
|
// Short circuit if nothing to commit
|
||||||
if update.empty() {
|
if update.Empty() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Commit dirty contract code if any exists
|
// Commit dirty contract code if any exists
|
||||||
if len(update.codes) > 0 {
|
if len(update.Codes) > 0 {
|
||||||
batch := db.codedb.NewBatchWithSize(len(update.codes))
|
batch := db.codedb.NewBatchWithSize(len(update.Codes))
|
||||||
for _, code := range update.codes {
|
for _, code := range update.Codes {
|
||||||
batch.Put(code.hash, code.blob)
|
batch.Put(code.Hash, code.Blob)
|
||||||
}
|
}
|
||||||
if err := batch.Commit(); err != nil {
|
if err := batch.Commit(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Encode the state mutations in the MPT format
|
||||||
|
accounts, accountOrigin, storages, storageOrigin := update.EncodeMPTState()
|
||||||
|
|
||||||
// If snapshotting is enabled, update the snapshot tree with this new version
|
// If snapshotting is enabled, update the snapshot tree with this new version
|
||||||
if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil {
|
if db.snap != nil && db.snap.Snapshot(update.OriginRoot) != nil {
|
||||||
if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil {
|
if err := db.snap.Update(update.Root, update.OriginRoot, accounts, storages); err != nil {
|
||||||
log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
|
log.Warn("Failed to update snapshot tree", "from", update.OriginRoot, "to", update.Root, "err", err)
|
||||||
}
|
}
|
||||||
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||||
// - head layer is paired with HEAD state
|
// - head layer is paired with HEAD state
|
||||||
// - head-1 layer is paired with HEAD-1 state
|
// - head-1 layer is paired with HEAD-1 state
|
||||||
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
||||||
if err := db.snap.Cap(update.root, TriesInMemory); err != nil {
|
if err := db.snap.Cap(update.Root, TriesInMemory); err != nil {
|
||||||
log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err)
|
log.Warn("Failed to cap snapshot tree", "root", update.Root, "layers", TriesInMemory, "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
|
return db.triedb.Update(update.Root, update.OriginRoot, update.BlockNumber, update.Nodes, &triedb.StateSet{
|
||||||
|
Accounts: accounts,
|
||||||
|
AccountsOrigin: accountOrigin,
|
||||||
|
Storages: storages,
|
||||||
|
StoragesOrigin: storageOrigin,
|
||||||
|
RawStorageKey: update.StorageKeyType == StorageKeyPlain,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iteratee returns a state iteratee associated with the specified state root,
|
// Iteratee returns a state iteratee associated with the specified state root,
|
||||||
|
|
|
||||||
|
|
@ -113,22 +113,31 @@ func (db *UBTDatabase) TrieDB() *triedb.Database {
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
func (db *UBTDatabase) Commit(update *stateUpdate) error {
|
func (db *UBTDatabase) Commit(update *StateUpdate) error {
|
||||||
// Short circuit if nothing to commit
|
// Short circuit if nothing to commit
|
||||||
if update.empty() {
|
if update.Empty() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Commit dirty contract code if any exists
|
// Commit dirty contract code if any exists
|
||||||
if len(update.codes) > 0 {
|
if len(update.Codes) > 0 {
|
||||||
batch := db.codedb.NewBatchWithSize(len(update.codes))
|
batch := db.codedb.NewBatchWithSize(len(update.Codes))
|
||||||
for _, code := range update.codes {
|
for _, code := range update.Codes {
|
||||||
batch.Put(code.hash, code.blob)
|
batch.Put(code.Hash, code.Blob)
|
||||||
}
|
}
|
||||||
if err := batch.Commit(); err != nil {
|
if err := batch.Commit(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
|
// Encode the state mutations in the UBT format
|
||||||
|
accounts, accountOrigin, storages, storageOrigin := update.EncodeUBTState()
|
||||||
|
|
||||||
|
return db.triedb.Update(update.Root, update.OriginRoot, update.BlockNumber, update.Nodes, &triedb.StateSet{
|
||||||
|
Accounts: accounts,
|
||||||
|
AccountsOrigin: accountOrigin,
|
||||||
|
Storages: storages,
|
||||||
|
StoragesOrigin: storageOrigin,
|
||||||
|
RawStorageKey: update.StorageKeyType == StorageKeyPlain,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iteratee returns a state iteratee associated with the specified state root,
|
// Iteratee returns a state iteratee associated with the specified state root,
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -144,11 +142,8 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
defer acctIt.Release()
|
defer acctIt.Release()
|
||||||
|
|
||||||
for acctIt.Next() {
|
for acctIt.Next() {
|
||||||
var data types.StateAccount
|
|
||||||
if err := rlp.DecodeBytes(acctIt.Account(), &data); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
|
data = acctIt.Account()
|
||||||
account = DumpAccount{
|
account = DumpAccount{
|
||||||
Balance: data.Balance.String(),
|
Balance: data.Balance.String(),
|
||||||
Nonce: data.Nonce,
|
Nonce: data.Nonce,
|
||||||
|
|
@ -168,7 +163,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
address = &addrBytes
|
address = &addrBytes
|
||||||
account.Address = address
|
account.Address = address
|
||||||
}
|
}
|
||||||
obj := newObject(s, addrBytes, &data)
|
obj := newObject(s, addrBytes, data)
|
||||||
if !conf.SkipCode {
|
if !conf.SkipCode {
|
||||||
account.Code = obj.Code()
|
account.Code = obj.Code()
|
||||||
}
|
}
|
||||||
|
|
@ -181,16 +176,11 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for storageIt.Next() {
|
for storageIt.Next() {
|
||||||
_, content, _, err := rlp.Split(storageIt.Slot())
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to decode the value returned by iterator", "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key, err := storageIt.Key()
|
key, err := storageIt.Key()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
account.Storage[key] = common.Bytes2Hex(content)
|
account.Storage[key] = storageIt.Slot().String()
|
||||||
}
|
}
|
||||||
storageIt.Release()
|
storageIt.Release()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||||
|
|
@ -398,17 +397,8 @@ func (s *stateObject) updateRoot() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitStorage overwrites the clean storage with the storage changes and
|
// commitStorage overwrites the clean storage with the storage changes and
|
||||||
// fulfills the storage diffs into the given accountUpdate struct.
|
// fulfills the storage diffs into the given AccountUpdate struct.
|
||||||
func (s *stateObject) commitStorage(op *accountUpdate) {
|
func (s *stateObject) commitStorage(op *AccountUpdate) {
|
||||||
var (
|
|
||||||
encode = func(val common.Hash) []byte {
|
|
||||||
if val == (common.Hash{}) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
|
|
||||||
return blob
|
|
||||||
}
|
|
||||||
)
|
|
||||||
for key, val := range s.pendingStorage {
|
for key, val := range s.pendingStorage {
|
||||||
// Skip the noop storage changes, it might be possible the value
|
// Skip the noop storage changes, it might be possible the value
|
||||||
// of tracked slot is same in originStorage and pendingStorage
|
// of tracked slot is same in originStorage and pendingStorage
|
||||||
|
|
@ -418,20 +408,20 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hash := crypto.Keccak256Hash(key[:])
|
hash := crypto.Keccak256Hash(key[:])
|
||||||
if op.storages == nil {
|
if op.Storages == nil {
|
||||||
op.storages = make(map[common.Hash][]byte)
|
op.Storages = make(map[common.Hash]common.Hash)
|
||||||
}
|
}
|
||||||
op.storages[hash] = encode(val)
|
op.Storages[hash] = val
|
||||||
|
|
||||||
if op.storagesOriginByKey == nil {
|
if op.StoragesOriginByKey == nil {
|
||||||
op.storagesOriginByKey = make(map[common.Hash][]byte)
|
op.StoragesOriginByKey = make(map[common.Hash]common.Hash)
|
||||||
}
|
}
|
||||||
if op.storagesOriginByHash == nil {
|
if op.StoragesOriginByHash == nil {
|
||||||
op.storagesOriginByHash = make(map[common.Hash][]byte)
|
op.StoragesOriginByHash = make(map[common.Hash]common.Hash)
|
||||||
}
|
}
|
||||||
origin := encode(s.originStorage[key])
|
origin := s.originStorage[key]
|
||||||
op.storagesOriginByKey[key] = origin
|
op.StoragesOriginByKey[key] = origin
|
||||||
op.storagesOriginByHash[hash] = origin
|
op.StoragesOriginByHash[hash] = origin
|
||||||
|
|
||||||
// Overwrite the clean value of storage slots
|
// Overwrite the clean value of storage slots
|
||||||
s.originStorage[key] = val
|
s.originStorage[key] = val
|
||||||
|
|
@ -444,32 +434,32 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
|
||||||
//
|
//
|
||||||
// Note, commit may run concurrently across all the state objects. Do not assume
|
// Note, commit may run concurrently across all the state objects. Do not assume
|
||||||
// thread-safe access to the statedb.
|
// thread-safe access to the statedb.
|
||||||
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
func (s *stateObject) commit() (*AccountUpdate, *trienode.NodeSet, error) {
|
||||||
// commit the account metadata changes
|
// commit the account metadata changes, the data must be deep-copied
|
||||||
op := &accountUpdate{
|
// to prevent accidental mutations later on (in practice the stateDB
|
||||||
address: s.address,
|
// won't be modified after commit). The origin is safe to use directly.
|
||||||
data: types.SlimAccountRLP(s.data),
|
op := &AccountUpdate{
|
||||||
}
|
Address: s.address,
|
||||||
if s.origin != nil {
|
Data: s.data.Copy(),
|
||||||
op.origin = types.SlimAccountRLP(*s.origin)
|
Origin: s.origin,
|
||||||
}
|
}
|
||||||
// commit the contract code if it's modified
|
// commit the contract code if it's modified
|
||||||
if s.dirtyCode {
|
if s.dirtyCode {
|
||||||
op.code = &contractCode{
|
op.Code = &ContractCode{
|
||||||
hash: common.BytesToHash(s.CodeHash()),
|
Hash: common.BytesToHash(s.CodeHash()),
|
||||||
blob: s.code,
|
Blob: s.code,
|
||||||
}
|
}
|
||||||
s.dirtyCode = false // reset the dirty flag
|
s.dirtyCode = false // reset the dirty flag
|
||||||
|
|
||||||
if s.origin == nil {
|
if s.origin == nil {
|
||||||
op.code.originHash = types.EmptyCodeHash
|
op.Code.OriginHash = types.EmptyCodeHash
|
||||||
} else {
|
} else {
|
||||||
op.code.originHash = common.BytesToHash(s.origin.CodeHash)
|
op.Code.OriginHash = common.BytesToHash(s.origin.CodeHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Commit storage changes and the associated storage trie
|
// Commit storage changes and the associated storage trie
|
||||||
s.commitStorage(op)
|
s.commitStorage(op)
|
||||||
if len(op.storages) == 0 {
|
if len(op.Storages) == 0 {
|
||||||
// nothing changed, don't bother to commit the trie
|
// nothing changed, don't bother to commit the trie
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
return op, nil, nil
|
return op, nil, nil
|
||||||
|
|
|
||||||
|
|
@ -125,16 +125,17 @@ func (s SizeStats) add(diff SizeStats) SizeStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
// calSizeStats measures the state size changes of the provided state update.
|
// calSizeStats measures the state size changes of the provided state update.
|
||||||
func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
func calSizeStats(update *StateUpdate) (SizeStats, error) {
|
||||||
stats := SizeStats{
|
stats := SizeStats{
|
||||||
BlockNumber: update.blockNumber,
|
BlockNumber: update.BlockNumber,
|
||||||
StateRoot: update.root,
|
StateRoot: update.Root,
|
||||||
}
|
}
|
||||||
|
accounts, accountOrigin, storages, storageOrigin := update.EncodeMPTState()
|
||||||
|
|
||||||
// Measure the account changes
|
// Measure the account changes
|
||||||
for addr, oldValue := range update.accountsOrigin {
|
for addr, oldValue := range accountOrigin {
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
newValue, exists := update.accounts[addrHash]
|
newValue, exists := accounts[addrHash]
|
||||||
if !exists {
|
if !exists {
|
||||||
return SizeStats{}, fmt.Errorf("account %x not found", addr)
|
return SizeStats{}, fmt.Errorf("account %x not found", addr)
|
||||||
}
|
}
|
||||||
|
|
@ -156,9 +157,9 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure storage changes
|
// Measure storage changes
|
||||||
for addr, slots := range update.storagesOrigin {
|
for addr, slots := range storageOrigin {
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
subset, exists := update.storages[addrHash]
|
subset, exists := storages[addrHash]
|
||||||
if !exists {
|
if !exists {
|
||||||
return SizeStats{}, fmt.Errorf("storage %x not found", addr)
|
return SizeStats{}, fmt.Errorf("storage %x not found", addr)
|
||||||
}
|
}
|
||||||
|
|
@ -167,7 +168,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
exists bool
|
exists bool
|
||||||
newValue []byte
|
newValue []byte
|
||||||
)
|
)
|
||||||
if update.rawStorageKey {
|
if update.StorageKeyType == StorageKeyPlain {
|
||||||
newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||||
} else {
|
} else {
|
||||||
newValue, exists = subset[key]
|
newValue, exists = subset[key]
|
||||||
|
|
@ -194,7 +195,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure trienode changes
|
// Measure trienode changes
|
||||||
for owner, subset := range update.nodes.Sets {
|
for owner, subset := range update.Nodes.Sets {
|
||||||
var (
|
var (
|
||||||
keyPrefix int64
|
keyPrefix int64
|
||||||
isAccount = owner == (common.Hash{})
|
isAccount = owner == (common.Hash{})
|
||||||
|
|
@ -244,13 +245,13 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
codeExists := make(map[common.Hash]struct{})
|
codeExists := make(map[common.Hash]struct{})
|
||||||
for _, code := range update.codes {
|
for _, code := range update.Codes {
|
||||||
if _, ok := codeExists[code.hash]; ok || code.duplicate {
|
if _, ok := codeExists[code.Hash]; ok || code.Duplicate {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stats.ContractCodes += 1
|
stats.ContractCodes += 1
|
||||||
stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
|
stats.ContractCodeBytes += codeKeySize + int64(len(code.Blob))
|
||||||
codeExists[code.hash] = struct{}{}
|
codeExists[code.Hash] = struct{}{}
|
||||||
}
|
}
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
@ -267,7 +268,7 @@ type SizeTracker struct {
|
||||||
triedb *triedb.Database
|
triedb *triedb.Database
|
||||||
abort chan struct{}
|
abort chan struct{}
|
||||||
aborted chan struct{}
|
aborted chan struct{}
|
||||||
updateCh chan *stateUpdate
|
updateCh chan *StateUpdate
|
||||||
queryCh chan *stateSizeQuery
|
queryCh chan *stateSizeQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,7 +282,7 @@ func NewSizeTracker(db ethdb.KeyValueStore, triedb *triedb.Database) (*SizeTrack
|
||||||
triedb: triedb,
|
triedb: triedb,
|
||||||
abort: make(chan struct{}),
|
abort: make(chan struct{}),
|
||||||
aborted: make(chan struct{}),
|
aborted: make(chan struct{}),
|
||||||
updateCh: make(chan *stateUpdate),
|
updateCh: make(chan *StateUpdate),
|
||||||
queryCh: make(chan *stateSizeQuery),
|
queryCh: make(chan *stateSizeQuery),
|
||||||
}
|
}
|
||||||
go t.run()
|
go t.run()
|
||||||
|
|
@ -328,9 +329,9 @@ func (t *SizeTracker) run() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case u := <-t.updateCh:
|
case u := <-t.updateCh:
|
||||||
base, found := stats[u.originRoot]
|
base, found := stats[u.OriginRoot]
|
||||||
if !found {
|
if !found {
|
||||||
log.Debug("Ignored the state size without parent", "parent", u.originRoot, "root", u.root, "number", u.blockNumber)
|
log.Debug("Ignored the state size without parent", "parent", u.OriginRoot, "root", u.Root, "number", u.BlockNumber)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
diff, err := calSizeStats(u)
|
diff, err := calSizeStats(u)
|
||||||
|
|
@ -338,15 +339,15 @@ func (t *SizeTracker) run() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stat := base.add(diff)
|
stat := base.add(diff)
|
||||||
stats[u.root] = stat
|
stats[u.Root] = stat
|
||||||
last = u.root
|
last = u.Root
|
||||||
|
|
||||||
// Publish statistics to metric system
|
// Publish statistics to metric system
|
||||||
stat.publish()
|
stat.publish()
|
||||||
|
|
||||||
// Evict the stale statistics
|
// Evict the stale statistics
|
||||||
heap.Push(&h, stats[u.root])
|
heap.Push(&h, stats[u.Root])
|
||||||
for len(h) > 0 && u.blockNumber-h[0].BlockNumber > statEvictThreshold {
|
for len(h) > 0 && u.BlockNumber-h[0].BlockNumber > statEvictThreshold {
|
||||||
delete(stats, h[0].StateRoot)
|
delete(stats, h[0].StateRoot)
|
||||||
heap.Pop(&h)
|
heap.Pop(&h)
|
||||||
}
|
}
|
||||||
|
|
@ -402,7 +403,7 @@ wait:
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
updates = make(map[common.Hash]*stateUpdate)
|
updates = make(map[common.Hash]*StateUpdate)
|
||||||
children = make(map[common.Hash][]common.Hash)
|
children = make(map[common.Hash][]common.Hash)
|
||||||
done chan buildResult
|
done chan buildResult
|
||||||
)
|
)
|
||||||
|
|
@ -410,9 +411,9 @@ wait:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case u := <-t.updateCh:
|
case u := <-t.updateCh:
|
||||||
updates[u.root] = u
|
updates[u.Root] = u
|
||||||
children[u.originRoot] = append(children[u.originRoot], u.root)
|
children[u.OriginRoot] = append(children[u.OriginRoot], u.Root)
|
||||||
log.Debug("Received state update", "root", u.root, "blockNumber", u.blockNumber)
|
log.Debug("Received state update", "root", u.Root, "blockNumber", u.BlockNumber)
|
||||||
|
|
||||||
case r := <-t.queryCh:
|
case r := <-t.queryCh:
|
||||||
r.err = errors.New("state size is not initialized yet")
|
r.err = errors.New("state size is not initialized yet")
|
||||||
|
|
@ -432,8 +433,8 @@ wait:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
done = make(chan buildResult)
|
done = make(chan buildResult)
|
||||||
go t.build(entry.root, entry.blockNumber, done)
|
go t.build(entry.Root, entry.BlockNumber, done)
|
||||||
log.Info("Measuring persistent state size", "root", root.Hex(), "number", entry.blockNumber)
|
log.Info("Measuring persistent state size", "root", root.Hex(), "number", entry.BlockNumber)
|
||||||
|
|
||||||
case result := <-done:
|
case result := <-done:
|
||||||
if result.err != nil {
|
if result.err != nil {
|
||||||
|
|
@ -646,8 +647,8 @@ func (t *SizeTracker) iterateTableParallel(closed chan struct{}, prefix []byte,
|
||||||
// Notify is an async method used to send the state update to the size tracker.
|
// Notify is an async method used to send the state update to the size tracker.
|
||||||
// It ignores empty updates (where no state changes occurred).
|
// It ignores empty updates (where no state changes occurred).
|
||||||
// If the channel is full, it drops the update to avoid blocking.
|
// If the channel is full, it drops the update to avoid blocking.
|
||||||
func (t *SizeTracker) Notify(update *stateUpdate) {
|
func (t *SizeTracker) Notify(update *StateUpdate) {
|
||||||
if update == nil || update.empty() {
|
if update == nil || update.Empty() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ func TestSizeTracker(t *testing.T) {
|
||||||
}
|
}
|
||||||
tracker.Notify(ret)
|
tracker.Notify(ret)
|
||||||
|
|
||||||
if err := tdb.Commit(ret.root, false); err != nil {
|
if err := tdb.Commit(ret.Root, false); err != nil {
|
||||||
t.Fatalf("Failed to commit trie at block %d: %v", blockNum, err)
|
t.Fatalf("Failed to commit trie at block %d: %v", blockNum, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@ func TestSizeTracker(t *testing.T) {
|
||||||
t.Fatalf("Failed to calculate size stats for block %d: %v", blockNum, err)
|
t.Fatalf("Failed to calculate size stats for block %d: %v", blockNum, err)
|
||||||
}
|
}
|
||||||
trackedUpdates = append(trackedUpdates, diff)
|
trackedUpdates = append(trackedUpdates, diff)
|
||||||
currentRoot = ret.root
|
currentRoot = ret.Root
|
||||||
}
|
}
|
||||||
finalRoot := rawdb.ReadSnapshotRoot(db)
|
finalRoot := rawdb.ReadSnapshotRoot(db)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1045,11 +1045,11 @@ func (s *StateDB) clearJournalAndRefund() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteStorage is designed to delete the storage trie of a designated account.
|
// deleteStorage is designed to delete the storage trie of a designated account.
|
||||||
func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash]common.Hash, map[common.Hash]common.Hash, *trienode.NodeSet, error) {
|
||||||
var (
|
var (
|
||||||
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
||||||
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
|
storages = make(map[common.Hash]common.Hash) // the set for storage mutations (value is nil)
|
||||||
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
|
storageOrigins = make(map[common.Hash]common.Hash) // the set for tracking the original value of slot
|
||||||
)
|
)
|
||||||
iteratee, err := s.db.Iteratee(s.originalRoot)
|
iteratee, err := s.db.Iteratee(s.originalRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1065,19 +1065,24 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
|
||||||
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
|
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
|
||||||
})
|
})
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
slot := common.CopyBytes(it.Slot())
|
slot := it.Slot()
|
||||||
if err := it.Error(); err != nil { // error might occur after Slot function
|
// Error might occur after Slot function
|
||||||
|
if err := it.Error(); err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
if slot == (common.Hash{}) {
|
||||||
|
return nil, nil, nil, fmt.Errorf("unexpected empty storage slot, addr: %x, slot: %x", addrHash, it.Hash())
|
||||||
|
}
|
||||||
key := it.Hash()
|
key := it.Hash()
|
||||||
storages[key] = nil
|
storages[key] = common.Hash{}
|
||||||
storageOrigins[key] = slot
|
storageOrigins[key] = slot
|
||||||
|
|
||||||
if err := stack.Update(key.Bytes(), slot); err != nil {
|
if err := stack.Update(key.Bytes(), encodeSlot(slot)); err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := it.Error(); err != nil { // error might occur during iteration
|
// Error might occur during iteration
|
||||||
|
if err := it.Error(); err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
if stack.Hash() != root {
|
if stack.Hash() != root {
|
||||||
|
|
@ -1104,10 +1109,10 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
|
||||||
// with their values be tracked as original value.
|
// with their values be tracked as original value.
|
||||||
// In case (d), **original** account along with its storages should be deleted,
|
// In case (d), **original** account along with its storages should be deleted,
|
||||||
// with their values be tracked as original value.
|
// with their values be tracked as original value.
|
||||||
func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) {
|
func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*AccountDelete, []*trienode.NodeSet, error) {
|
||||||
var (
|
var (
|
||||||
nodes []*trienode.NodeSet
|
nodes []*trienode.NodeSet
|
||||||
deletes = make(map[common.Hash]*accountDelete)
|
deletes = make(map[common.Hash]*AccountDelete)
|
||||||
)
|
)
|
||||||
for addr, prevObj := range s.stateObjectsDestruct {
|
for addr, prevObj := range s.stateObjectsDestruct {
|
||||||
prev := prevObj.origin
|
prev := prevObj.origin
|
||||||
|
|
@ -1122,9 +1127,9 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
||||||
}
|
}
|
||||||
// The account was existent, it can be either case (c) or (d).
|
// The account was existent, it can be either case (c) or (d).
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
op := &accountDelete{
|
op := &AccountDelete{
|
||||||
address: addr,
|
Address: addr,
|
||||||
origin: types.SlimAccountRLP(*prev),
|
Origin: prev,
|
||||||
}
|
}
|
||||||
deletes[addrHash] = op
|
deletes[addrHash] = op
|
||||||
|
|
||||||
|
|
@ -1140,8 +1145,8 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
||||||
}
|
}
|
||||||
op.storages = storages
|
op.Storages = storages
|
||||||
op.storagesOrigin = storagesOrigin
|
op.StoragesOrigin = storagesOrigin
|
||||||
|
|
||||||
// Aggregate the associated trie node changes.
|
// Aggregate the associated trie node changes.
|
||||||
nodes = append(nodes, set)
|
nodes = append(nodes, set)
|
||||||
|
|
@ -1156,7 +1161,7 @@ func (s *StateDB) GetTrie() Trie {
|
||||||
|
|
||||||
// commit gathers the state mutations accumulated along with the associated
|
// commit gathers the state mutations accumulated along with the associated
|
||||||
// trie changes, resetting all internal flags with the new state as the base.
|
// trie changes, resetting all internal flags with the new state as the base.
|
||||||
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*stateUpdate, error) {
|
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*StateUpdate, error) {
|
||||||
// Short circuit in case any database failure occurred earlier.
|
// Short circuit in case any database failure occurred earlier.
|
||||||
if s.dbErr != nil {
|
if s.dbErr != nil {
|
||||||
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
||||||
|
|
@ -1177,7 +1182,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
||||||
|
|
||||||
lock sync.Mutex // protect two maps below
|
lock sync.Mutex // protect two maps below
|
||||||
nodes = trienode.NewMergedNodeSet() // aggregated trie nodes
|
nodes = trienode.NewMergedNodeSet() // aggregated trie nodes
|
||||||
updates = make(map[common.Hash]*accountUpdate, len(s.mutations)) // aggregated account updates
|
updates = make(map[common.Hash]*AccountUpdate, len(s.mutations)) // aggregated account updates
|
||||||
|
|
||||||
// merge aggregates the dirty trie nodes into the global set.
|
// merge aggregates the dirty trie nodes into the global set.
|
||||||
//
|
//
|
||||||
|
|
@ -1305,12 +1310,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
||||||
origin := s.originalRoot
|
origin := s.originalRoot
|
||||||
s.originalRoot = root
|
s.originalRoot = root
|
||||||
|
|
||||||
return newStateUpdate(noStorageWiping, origin, root, blockNumber, deletes, updates, nodes), nil
|
typ := StorageKeyHashed
|
||||||
|
if noStorageWiping {
|
||||||
|
typ = StorageKeyPlain
|
||||||
|
}
|
||||||
|
return NewStateUpdate(typ, origin, root, blockNumber, deletes, updates, nodes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitAndFlush is a wrapper of commit which also commits the state mutations
|
// commitAndFlush is a wrapper of commit which also commits the state mutations
|
||||||
// to the configured data stores.
|
// to the configured data stores.
|
||||||
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, deriveCodeFields bool) (*stateUpdate, error) {
|
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, deriveCodeFields bool) (*StateUpdate, error) {
|
||||||
ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
|
ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -1351,17 +1360,17 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
return ret.root, nil
|
return ret.Root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitWithUpdate writes the state mutations and returns the state update for
|
// CommitWithUpdate writes the state mutations and returns the state update for
|
||||||
// external processing (e.g., live tracing hooks or size tracker).
|
// external processing (e.g., live tracing hooks or size tracker).
|
||||||
func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) {
|
func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *StateUpdate, error) {
|
||||||
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping, true)
|
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, nil, err
|
return common.Hash{}, nil, err
|
||||||
}
|
}
|
||||||
return ret.root, ret, nil
|
return ret.Root, ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare handles the preparatory steps for executing a state transition with.
|
// Prepare handles the preparatory steps for executing a state transition with.
|
||||||
|
|
|
||||||
|
|
@ -182,11 +182,12 @@ func (test *stateTest) run() bool {
|
||||||
accountOrigin []map[common.Address][]byte
|
accountOrigin []map[common.Address][]byte
|
||||||
storages []map[common.Hash]map[common.Hash][]byte
|
storages []map[common.Hash]map[common.Hash][]byte
|
||||||
storageOrigin []map[common.Address]map[common.Hash][]byte
|
storageOrigin []map[common.Address]map[common.Hash][]byte
|
||||||
copyUpdate = func(update *stateUpdate) {
|
copyUpdate = func(update *StateUpdate) {
|
||||||
accounts = append(accounts, maps.Clone(update.accounts))
|
accts, acctOrigin, slots, slotOrigin := update.EncodeMPTState()
|
||||||
accountOrigin = append(accountOrigin, maps.Clone(update.accountsOrigin))
|
accounts = append(accounts, maps.Clone(accts))
|
||||||
storages = append(storages, maps.Clone(update.storages))
|
accountOrigin = append(accountOrigin, maps.Clone(acctOrigin))
|
||||||
storageOrigin = append(storageOrigin, maps.Clone(update.storagesOrigin))
|
storages = append(storages, maps.Clone(slots))
|
||||||
|
storageOrigin = append(storageOrigin, maps.Clone(slotOrigin))
|
||||||
}
|
}
|
||||||
disk = rawdb.NewMemoryDatabase()
|
disk = rawdb.NewMemoryDatabase()
|
||||||
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
|
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
|
||||||
|
|
@ -232,11 +233,11 @@ func (test *stateTest) run() bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if ret.empty() {
|
if ret.Empty() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
copyUpdate(ret)
|
copyUpdate(ret)
|
||||||
roots = append(roots, ret.root)
|
roots = append(roots, ret.Root)
|
||||||
}
|
}
|
||||||
for i := 0; i < len(test.actions); i++ {
|
for i := 0; i < len(test.actions); i++ {
|
||||||
root := types.EmptyRootHash
|
root := types.EmptyRootHash
|
||||||
|
|
|
||||||
|
|
@ -26,139 +26,148 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// contractCode represents contract bytecode along with its associated metadata.
|
// ContractCode represents contract bytecode mutation along with its
|
||||||
type contractCode struct {
|
// associated metadata.
|
||||||
hash common.Hash // hash is the cryptographic hash of the current contract code.
|
type ContractCode struct {
|
||||||
blob []byte // blob is the binary representation of the current contract code.
|
Hash common.Hash // Hash is the cryptographic hash of the current contract code.
|
||||||
originHash common.Hash // originHash is the cryptographic hash of the code before mutation.
|
Blob []byte // Blob is the binary representation of the current contract code.
|
||||||
|
OriginHash common.Hash // OriginHash is the cryptographic hash of the code before mutation.
|
||||||
|
|
||||||
// Derived fields, populated only when state tracking is enabled.
|
// Derived fields, populated only when state tracking is enabled.
|
||||||
duplicate bool // duplicate indicates whether the updated code already exists.
|
Duplicate bool // Duplicate indicates whether the updated code already exists.
|
||||||
originBlob []byte // originBlob is the original binary representation of the contract code.
|
OriginBlob []byte // OriginBlob is the original binary representation of the contract code.
|
||||||
}
|
}
|
||||||
|
|
||||||
// accountDelete represents an operation for deleting an Ethereum account.
|
// AccountDelete represents a deletion operation for an Ethereum account.
|
||||||
type accountDelete struct {
|
type AccountDelete struct {
|
||||||
address common.Address // address is the unique account identifier
|
Address common.Address // Address uniquely identifies the account.
|
||||||
origin []byte // origin is the original value of account data in slim-RLP encoding.
|
Origin *types.StateAccount // Origin is the account state prior to deletion (never be null).
|
||||||
|
Storages map[common.Hash]common.Hash // Storages contains mutated storage slots.
|
||||||
// storages stores mutated slots, the value should be nil.
|
StoragesOrigin map[common.Hash]common.Hash // StoragesOrigin holds original values of mutated slots; keys are hashes of raw storage slot keys.
|
||||||
storages map[common.Hash][]byte
|
|
||||||
|
|
||||||
// storagesOrigin stores the original values of mutated slots in
|
|
||||||
// prefix-zero-trimmed RLP format. The map key refers to the **HASH**
|
|
||||||
// of the raw storage slot key.
|
|
||||||
storagesOrigin map[common.Hash][]byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// accountUpdate represents an operation for updating an Ethereum account.
|
// AccountUpdate represents an update operation for an Ethereum account.
|
||||||
type accountUpdate struct {
|
type AccountUpdate struct {
|
||||||
address common.Address // address is the unique account identifier
|
Address common.Address // Address uniquely identifies the account.
|
||||||
data []byte // data is the slim-RLP encoded account data.
|
Data *types.StateAccount // Data is the updated account state; nil indicates deletion.
|
||||||
origin []byte // origin is the original value of account data in slim-RLP encoding.
|
Origin *types.StateAccount // Origin is the previous account state; nil indicates non-existence.
|
||||||
code *contractCode // code represents mutated contract code; nil means it's not modified.
|
Code *ContractCode // Code contains updated contract code; nil if unchanged.
|
||||||
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
|
Storages map[common.Hash]common.Hash // Storages contains updated storage slots.
|
||||||
|
|
||||||
// storagesOriginByKey and storagesOriginByHash both store the original values
|
// StoragesOriginByKey and StoragesOriginByHash both record original values
|
||||||
// of mutated slots in prefix-zero-trimmed RLP format. The difference is that
|
// of mutated storage slots:
|
||||||
// storagesOriginByKey uses the **raw** storage slot key as the map ID, while
|
// - StoragesOriginByKey uses raw storage slot keys.
|
||||||
// storagesOriginByHash uses the **hash** of the storage slot key instead.
|
// - StoragesOriginByHash uses hashed storage slot keys.
|
||||||
storagesOriginByKey map[common.Hash][]byte
|
StoragesOriginByKey map[common.Hash]common.Hash
|
||||||
storagesOriginByHash map[common.Hash][]byte
|
StoragesOriginByHash map[common.Hash]common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// stateUpdate represents the difference between two states resulting from state
|
// StorageKeyEncoding specifies the encoding scheme of a storage key.
|
||||||
|
type StorageKeyEncoding int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StorageKeyHashed represents a hashed key (e.g. Keccak256).
|
||||||
|
StorageKeyHashed StorageKeyEncoding = iota
|
||||||
|
|
||||||
|
// StorageKeyPlain represents a raw (unhashed) key.
|
||||||
|
StorageKeyPlain
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateUpdate represents the difference between two states resulting from state
|
||||||
// execution. It contains information about mutated contract codes, accounts,
|
// execution. It contains information about mutated contract codes, accounts,
|
||||||
// and storage slots, along with their original values.
|
// and storage slots, along with their original values.
|
||||||
type stateUpdate struct {
|
type StateUpdate struct {
|
||||||
originRoot common.Hash // hash of the state before applying mutation
|
OriginRoot common.Hash // Hash of the state before applying mutation
|
||||||
root common.Hash // hash of the state after applying mutation
|
Root common.Hash // Hash of the state after applying mutation
|
||||||
blockNumber uint64 // Associated block number
|
BlockNumber uint64 // Associated block number
|
||||||
|
|
||||||
accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding
|
// Accounts contains mutated accounts, keyed by address hash.
|
||||||
accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding
|
Accounts map[common.Hash]*types.StateAccount
|
||||||
|
|
||||||
// storages stores mutated slots in 'prefix-zero-trimmed' RLP format.
|
// Storages contains mutated storage slots, keyed by address
|
||||||
// The value is keyed by account hash and **storage slot key hash**.
|
// hash and storage slot key hash.
|
||||||
storages map[common.Hash]map[common.Hash][]byte
|
Storages map[common.Hash]map[common.Hash]common.Hash
|
||||||
|
|
||||||
// storagesOrigin stores the original values of mutated slots in
|
// AccountsOrigin holds the original values of mutated accounts, keyed by address.
|
||||||
// 'prefix-zero-trimmed' RLP format.
|
AccountsOrigin map[common.Address]*types.StateAccount
|
||||||
// (a) the value is keyed by account hash and **storage slot key** if rawStorageKey is true;
|
|
||||||
// (b) the value is keyed by account hash and **storage slot key hash** if rawStorageKey is false;
|
|
||||||
storagesOrigin map[common.Address]map[common.Hash][]byte
|
|
||||||
rawStorageKey bool
|
|
||||||
|
|
||||||
codes map[common.Address]*contractCode // codes contains the set of dirty codes
|
// StoragesOrigin holds the original values of mutated storage slots.
|
||||||
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
|
// The key format depends on StorageKeyType:
|
||||||
|
// - if StorageKeyType is plain: keyed by account address and plain storage slot key.
|
||||||
|
// - if StorageKeyType is hashed: keyed by account address and storage slot key hash.
|
||||||
|
StoragesOrigin map[common.Address]map[common.Hash]common.Hash
|
||||||
|
StorageKeyType StorageKeyEncoding
|
||||||
|
|
||||||
|
Codes map[common.Address]*ContractCode // Codes contains the set of dirty codes
|
||||||
|
Nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty returns a flag indicating the state transition is empty or not.
|
// Empty returns a flag indicating the state transition is empty or not.
|
||||||
func (sc *stateUpdate) empty() bool {
|
func (sc *StateUpdate) Empty() bool {
|
||||||
return sc.originRoot == sc.root
|
return sc.OriginRoot == sc.Root
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStateUpdate constructs a state update object by identifying the differences
|
// NewStateUpdate constructs a state update object by identifying the differences
|
||||||
// between two states through state execution. It combines the specified account
|
// between two states through state execution. It combines the specified account
|
||||||
// deletions and account updates to create a complete state update.
|
// deletions and account updates to create a complete state update.
|
||||||
//
|
func NewStateUpdate(typ StorageKeyEncoding, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*AccountDelete, updates map[common.Hash]*AccountUpdate, nodes *trienode.MergedNodeSet) *StateUpdate {
|
||||||
// rawStorageKey is a flag indicating whether to use the raw storage slot key or
|
|
||||||
// the hash of the slot key for constructing state update object.
|
|
||||||
func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *stateUpdate {
|
|
||||||
var (
|
var (
|
||||||
accounts = make(map[common.Hash][]byte)
|
accounts = make(map[common.Hash]*types.StateAccount)
|
||||||
accountsOrigin = make(map[common.Address][]byte)
|
accountsOrigin = make(map[common.Address]*types.StateAccount)
|
||||||
storages = make(map[common.Hash]map[common.Hash][]byte)
|
storages = make(map[common.Hash]map[common.Hash]common.Hash)
|
||||||
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
storagesOrigin = make(map[common.Address]map[common.Hash]common.Hash)
|
||||||
codes = make(map[common.Address]*contractCode)
|
codes = make(map[common.Address]*ContractCode)
|
||||||
)
|
)
|
||||||
// Since some accounts might be destroyed and recreated within the same
|
// Since some accounts might be deleted and recreated within the same
|
||||||
// block, deletions must be aggregated first.
|
// block, deletions must be aggregated first.
|
||||||
for addrHash, op := range deletes {
|
for addrHash, op := range deletes {
|
||||||
addr := op.address
|
addr := op.Address
|
||||||
accounts[addrHash] = nil
|
accounts[addrHash] = nil
|
||||||
accountsOrigin[addr] = op.origin
|
accountsOrigin[addr] = op.Origin
|
||||||
|
|
||||||
// If storage wiping exists, the hash of the storage slot key must be used
|
// If storage wiping is present, the StorageKeyEncoding MUST be hashed.
|
||||||
if len(op.storages) > 0 {
|
// Deleted storage slots are iterated from either the trie or the flat
|
||||||
storages[addrHash] = op.storages
|
// state snapshot, both of which use the storage slot hash as the identifier.
|
||||||
|
// Fortunately, storage wiping is no longer practical after the Cancun fork
|
||||||
|
// and is not expected to occur.
|
||||||
|
if len(op.Storages) > 0 {
|
||||||
|
storages[addrHash] = op.Storages
|
||||||
}
|
}
|
||||||
if len(op.storagesOrigin) > 0 {
|
if len(op.StoragesOrigin) > 0 {
|
||||||
storagesOrigin[addr] = op.storagesOrigin
|
storagesOrigin[addr] = op.StoragesOrigin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Aggregate account updates then.
|
// Aggregate account updates then.
|
||||||
for addrHash, op := range updates {
|
for addrHash, op := range updates {
|
||||||
// Aggregate dirty contract codes if they are available.
|
// Aggregate dirty contract codes if they are available.
|
||||||
addr := op.address
|
addr := op.Address
|
||||||
if op.code != nil {
|
if op.Code != nil {
|
||||||
codes[addr] = op.code
|
codes[addr] = op.Code
|
||||||
}
|
}
|
||||||
accounts[addrHash] = op.data
|
accounts[addrHash] = op.Data
|
||||||
|
|
||||||
// Aggregate the account original value. If the account is already
|
// Aggregate the account original value. If the account is already
|
||||||
// present in the aggregated accountsOrigin set, skip it.
|
// present in the aggregated AccountsOrigin set, skip it.
|
||||||
if _, found := accountsOrigin[addr]; !found {
|
if _, found := accountsOrigin[addr]; !found {
|
||||||
accountsOrigin[addr] = op.origin
|
accountsOrigin[addr] = op.Origin
|
||||||
}
|
}
|
||||||
// Aggregate the storage mutation list. If a slot in op.storages is
|
// Aggregate the storage mutation list. If a slot in op.storages is
|
||||||
// already present in aggregated storages set, the value will be
|
// already present in aggregated storages set, the value will be
|
||||||
// overwritten.
|
// overwritten.
|
||||||
if len(op.storages) > 0 {
|
if len(op.Storages) > 0 {
|
||||||
if _, exist := storages[addrHash]; !exist {
|
if _, exist := storages[addrHash]; !exist {
|
||||||
storages[addrHash] = op.storages
|
storages[addrHash] = op.Storages
|
||||||
} else {
|
} else {
|
||||||
maps.Copy(storages[addrHash], op.storages)
|
maps.Copy(storages[addrHash], op.Storages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Aggregate the storage original values. If the slot is already present
|
// Aggregate the storage original values. If the slot is already present
|
||||||
// in aggregated storagesOrigin set, skip it.
|
// in aggregated StoragesOrigin set, skip it.
|
||||||
storageOriginSet := op.storagesOriginByHash
|
storageOriginSet := op.StoragesOriginByHash
|
||||||
if rawStorageKey {
|
if typ == StorageKeyPlain {
|
||||||
storageOriginSet = op.storagesOriginByKey
|
storageOriginSet = op.StoragesOriginByKey
|
||||||
}
|
}
|
||||||
if len(storageOriginSet) > 0 {
|
if len(storageOriginSet) > 0 {
|
||||||
origin, exist := storagesOrigin[addr]
|
origin, exist := storagesOrigin[addr]
|
||||||
|
|
@ -173,32 +182,114 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &stateUpdate{
|
return &StateUpdate{
|
||||||
originRoot: originRoot,
|
OriginRoot: originRoot,
|
||||||
root: root,
|
Root: root,
|
||||||
blockNumber: blockNumber,
|
BlockNumber: blockNumber,
|
||||||
accounts: accounts,
|
Accounts: accounts,
|
||||||
accountsOrigin: accountsOrigin,
|
AccountsOrigin: accountsOrigin,
|
||||||
storages: storages,
|
Storages: storages,
|
||||||
storagesOrigin: storagesOrigin,
|
StoragesOrigin: storagesOrigin,
|
||||||
rawStorageKey: rawStorageKey,
|
StorageKeyType: typ,
|
||||||
codes: codes,
|
Codes: codes,
|
||||||
nodes: nodes,
|
Nodes: nodes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stateSet converts the current stateUpdate object into a triedb.StateSet
|
// encodeSlot encodes the storage slot value by trimming all leading zeros
|
||||||
// object. This function extracts the necessary data from the stateUpdate
|
// and then RLP-encoding the result.
|
||||||
// struct and formats it into the StateSet structure consumed by the triedb
|
func encodeSlot(value common.Hash) []byte {
|
||||||
// package.
|
if value == (common.Hash{}) {
|
||||||
func (sc *stateUpdate) stateSet() *triedb.StateSet {
|
return nil
|
||||||
return &triedb.StateSet{
|
|
||||||
Accounts: sc.accounts,
|
|
||||||
AccountsOrigin: sc.accountsOrigin,
|
|
||||||
Storages: sc.storages,
|
|
||||||
StoragesOrigin: sc.storagesOrigin,
|
|
||||||
RawStorageKey: sc.rawStorageKey,
|
|
||||||
}
|
}
|
||||||
|
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
|
||||||
|
return blob
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMPTState encodes all state mutations alongside their original value
|
||||||
|
// into the Merkle-Patricia-Trie representation.
|
||||||
|
//
|
||||||
|
// It transforms account and storage updates into their corresponding MPT-encoded
|
||||||
|
// key-value mappings, using the same encoding rules as the Ethereum state trie.
|
||||||
|
func (sc *StateUpdate) EncodeMPTState() (map[common.Hash][]byte, map[common.Address][]byte, map[common.Hash]map[common.Hash][]byte, map[common.Address]map[common.Hash][]byte) {
|
||||||
|
var (
|
||||||
|
accounts = make(map[common.Hash][]byte, len(sc.Accounts))
|
||||||
|
storages = make(map[common.Hash]map[common.Hash][]byte, len(sc.Storages))
|
||||||
|
accountOrigin = make(map[common.Address][]byte, len(sc.AccountsOrigin))
|
||||||
|
storageOrigin = make(map[common.Address]map[common.Hash][]byte, len(sc.StoragesOrigin))
|
||||||
|
)
|
||||||
|
for addr, prev := range sc.AccountsOrigin {
|
||||||
|
if prev == nil {
|
||||||
|
accountOrigin[addr] = nil
|
||||||
|
} else {
|
||||||
|
accountOrigin[addr] = types.SlimAccountRLP(*prev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for addrHash, data := range sc.Accounts {
|
||||||
|
if data == nil {
|
||||||
|
accounts[addrHash] = nil
|
||||||
|
} else {
|
||||||
|
accounts[addrHash] = types.SlimAccountRLP(*data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for addr, slots := range sc.StoragesOrigin {
|
||||||
|
subset := make(map[common.Hash][]byte)
|
||||||
|
for key, val := range slots {
|
||||||
|
subset[key] = encodeSlot(val)
|
||||||
|
}
|
||||||
|
storageOrigin[addr] = subset
|
||||||
|
}
|
||||||
|
for addrHash, slots := range sc.Storages {
|
||||||
|
subset := make(map[common.Hash][]byte)
|
||||||
|
for key, val := range slots {
|
||||||
|
subset[key] = encodeSlot(val)
|
||||||
|
}
|
||||||
|
storages[addrHash] = subset
|
||||||
|
}
|
||||||
|
return accounts, accountOrigin, storages, storageOrigin
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeUBTState encodes all state mutations alongside their original value
|
||||||
|
// into the Unified-Binary-Trie representation.
|
||||||
|
//
|
||||||
|
// It transforms account and storage updates into their corresponding UBT-encoded
|
||||||
|
// key-value mappings, using the same encoding rules as the Ethereum state trie.
|
||||||
|
func (sc *StateUpdate) EncodeUBTState() (map[common.Hash][]byte, map[common.Address][]byte, map[common.Hash]map[common.Hash][]byte, map[common.Address]map[common.Hash][]byte) {
|
||||||
|
var (
|
||||||
|
accounts = make(map[common.Hash][]byte, len(sc.Accounts))
|
||||||
|
storages = make(map[common.Hash]map[common.Hash][]byte, len(sc.Storages))
|
||||||
|
accountOrigin = make(map[common.Address][]byte, len(sc.AccountsOrigin))
|
||||||
|
storageOrigin = make(map[common.Address]map[common.Hash][]byte, len(sc.StoragesOrigin))
|
||||||
|
)
|
||||||
|
for addr, prev := range sc.AccountsOrigin {
|
||||||
|
if prev == nil {
|
||||||
|
accountOrigin[addr] = nil
|
||||||
|
} else {
|
||||||
|
accountOrigin[addr] = types.SlimAccountRLP(*prev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for addrHash, data := range sc.Accounts {
|
||||||
|
if data == nil {
|
||||||
|
accounts[addrHash] = nil
|
||||||
|
} else {
|
||||||
|
accounts[addrHash] = types.SlimAccountRLP(*data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for addr, slots := range sc.StoragesOrigin {
|
||||||
|
subset := make(map[common.Hash][]byte)
|
||||||
|
for key, val := range slots {
|
||||||
|
subset[key] = encodeSlot(val)
|
||||||
|
}
|
||||||
|
storageOrigin[addr] = subset
|
||||||
|
}
|
||||||
|
for addrHash, slots := range sc.Storages {
|
||||||
|
subset := make(map[common.Hash][]byte)
|
||||||
|
for key, val := range slots {
|
||||||
|
subset[key] = encodeSlot(val)
|
||||||
|
}
|
||||||
|
storages[addrHash] = subset
|
||||||
|
}
|
||||||
|
return accounts, accountOrigin, storages, storageOrigin
|
||||||
}
|
}
|
||||||
|
|
||||||
// deriveCodeFields derives the missing fields of contract code changes
|
// deriveCodeFields derives the missing fields of contract code changes
|
||||||
|
|
@ -207,135 +298,96 @@ func (sc *stateUpdate) stateSet() *triedb.StateSet {
|
||||||
// Note: This operation is expensive and not needed during normal state
|
// Note: This operation is expensive and not needed during normal state
|
||||||
// transitions. It is only required when SizeTracker or StateUpdate hook
|
// transitions. It is only required when SizeTracker or StateUpdate hook
|
||||||
// is enabled to produce accurate state statistics.
|
// is enabled to produce accurate state statistics.
|
||||||
func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
|
func (sc *StateUpdate) deriveCodeFields(reader ContractCodeReader) error {
|
||||||
cache := make(map[common.Hash]bool)
|
cache := make(map[common.Hash]bool)
|
||||||
for addr, code := range sc.codes {
|
for addr, code := range sc.Codes {
|
||||||
if code.originHash != types.EmptyCodeHash {
|
if code.OriginHash != types.EmptyCodeHash {
|
||||||
blob := reader.Code(addr, code.originHash)
|
blob := reader.Code(addr, code.OriginHash)
|
||||||
if len(blob) == 0 {
|
if len(blob) == 0 {
|
||||||
return fmt.Errorf("original code of %x is empty", addr)
|
return fmt.Errorf("original code of %x is empty", addr)
|
||||||
}
|
}
|
||||||
code.originBlob = blob
|
code.OriginBlob = blob
|
||||||
}
|
}
|
||||||
if exists, ok := cache[code.hash]; ok {
|
if exists, ok := cache[code.Hash]; ok {
|
||||||
code.duplicate = exists
|
code.Duplicate = exists
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res := reader.Has(addr, code.hash)
|
res := reader.Has(addr, code.Hash)
|
||||||
cache[code.hash] = res
|
cache[code.Hash] = res
|
||||||
code.duplicate = res
|
code.Duplicate = res
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate.
|
// ToTracingUpdate converts the internal StateUpdate to an exported tracing.StateUpdate.
|
||||||
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
func (sc *StateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
||||||
update := &tracing.StateUpdate{
|
update := &tracing.StateUpdate{
|
||||||
OriginRoot: sc.originRoot,
|
OriginRoot: sc.OriginRoot,
|
||||||
Root: sc.root,
|
Root: sc.Root,
|
||||||
BlockNumber: sc.blockNumber,
|
BlockNumber: sc.BlockNumber,
|
||||||
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
|
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.AccountsOrigin)),
|
||||||
StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
|
StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
|
||||||
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
|
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.Codes)),
|
||||||
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
|
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
|
||||||
}
|
}
|
||||||
// Gather all account changes
|
// Gather all account changes
|
||||||
for addr, oldData := range sc.accountsOrigin {
|
for addr, oldData := range sc.AccountsOrigin {
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
newData, exists := sc.accounts[addrHash]
|
newData, exists := sc.Accounts[addrHash]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("account %x not found", addr)
|
return nil, fmt.Errorf("account %x not found", addr)
|
||||||
}
|
}
|
||||||
change := &tracing.AccountChange{}
|
change := &tracing.AccountChange{
|
||||||
|
Prev: oldData,
|
||||||
if len(oldData) > 0 {
|
New: newData,
|
||||||
acct, err := types.FullAccount(oldData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
change.Prev = &types.StateAccount{
|
|
||||||
Nonce: acct.Nonce,
|
|
||||||
Balance: acct.Balance,
|
|
||||||
Root: acct.Root,
|
|
||||||
CodeHash: acct.CodeHash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(newData) > 0 {
|
|
||||||
acct, err := types.FullAccount(newData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
change.New = &types.StateAccount{
|
|
||||||
Nonce: acct.Nonce,
|
|
||||||
Balance: acct.Balance,
|
|
||||||
Root: acct.Root,
|
|
||||||
CodeHash: acct.CodeHash,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
update.AccountChanges[addr] = change
|
update.AccountChanges[addr] = change
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather all storage slot changes
|
// Gather all storage slot changes
|
||||||
for addr, slots := range sc.storagesOrigin {
|
for addr, slots := range sc.StoragesOrigin {
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
subset, exists := sc.storages[addrHash]
|
subset, exists := sc.Storages[addrHash]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("storage %x not found", addr)
|
return nil, fmt.Errorf("storage %x not found", addr)
|
||||||
}
|
}
|
||||||
storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
|
storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
|
||||||
|
|
||||||
for key, encPrev := range slots {
|
for key, oldData := range slots {
|
||||||
// Get new value - handle both raw and hashed key formats
|
// Get new value - handle both raw and hashed key formats
|
||||||
var (
|
var (
|
||||||
exists bool
|
exists bool
|
||||||
encNew []byte
|
newData common.Hash
|
||||||
decPrev []byte
|
|
||||||
decNew []byte
|
|
||||||
err error
|
|
||||||
)
|
)
|
||||||
if sc.rawStorageKey {
|
if sc.StorageKeyType == StorageKeyPlain {
|
||||||
encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
newData, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||||
} else {
|
} else {
|
||||||
encNew, exists = subset[key]
|
newData, exists = subset[key]
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
|
return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the prev and new values
|
|
||||||
if len(encPrev) > 0 {
|
|
||||||
_, decPrev, _, err = rlp.Split(encPrev)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode prevValue: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(encNew) > 0 {
|
|
||||||
_, decNew, _, err = rlp.Split(encNew)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode newValue: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
storageChanges[key] = &tracing.StorageChange{
|
storageChanges[key] = &tracing.StorageChange{
|
||||||
Prev: common.BytesToHash(decPrev),
|
Prev: oldData,
|
||||||
New: common.BytesToHash(decNew),
|
New: newData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update.StorageChanges[addr] = storageChanges
|
update.StorageChanges[addr] = storageChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather all contract code changes
|
// Gather all contract code changes
|
||||||
for addr, code := range sc.codes {
|
for addr, code := range sc.Codes {
|
||||||
change := &tracing.CodeChange{
|
change := &tracing.CodeChange{
|
||||||
New: &tracing.ContractCode{
|
New: &tracing.ContractCode{
|
||||||
Hash: code.hash,
|
Hash: code.Hash,
|
||||||
Code: code.blob,
|
Code: code.Blob,
|
||||||
Exists: code.duplicate,
|
Exists: code.Duplicate,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if code.originHash != types.EmptyCodeHash {
|
if code.OriginHash != types.EmptyCodeHash {
|
||||||
change.Prev = &tracing.ContractCode{
|
change.Prev = &tracing.ContractCode{
|
||||||
Hash: code.originHash,
|
Hash: code.OriginHash,
|
||||||
Code: code.originBlob,
|
Code: code.OriginBlob,
|
||||||
Exists: true,
|
Exists: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -343,8 +395,8 @@ func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather all trie node changes
|
// Gather all trie node changes
|
||||||
if sc.nodes != nil {
|
if sc.Nodes != nil {
|
||||||
for owner, subset := range sc.nodes.Sets {
|
for owner, subset := range sc.Nodes.Sets {
|
||||||
nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
|
nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
|
||||||
for path, oldNode := range subset.Origins {
|
for path, oldNode := range subset.Origins {
|
||||||
newNode, exists := subset.Nodes[path]
|
newNode, exists := subset.Nodes[path]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue