core, eth, main, state, tests: Return state changes from statedb.Commit

This commit is contained in:
Elizabeth Engelman 2020-03-19 14:46:31 -05:00
parent 0f77f34bb6
commit 0505976975
9 changed files with 157 additions and 22 deletions

View File

@ -686,7 +686,7 @@ func (api *RetestethAPI) AccountRange(ctx context.Context,
root = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number()))
if idx == int(txIndex) {
// This is to make sure root can be opened by OpenTrie
root, err = statedb.Commit(api.chainConfig.IsEIP158(block.Number()))
root, _, err = statedb.Commit(api.chainConfig.IsEIP158(block.Number()))
if err != nil {
return AccountRangeResult{}, err
}
@ -796,7 +796,7 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context,
_ = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number()))
if idx == int(txIndex) {
// This is to make sure root can be opened by OpenTrie
_, err = statedb.Commit(vmenv.ChainConfig().IsEIP158(block.Number()))
_, _, err = statedb.Commit(vmenv.ChainConfig().IsEIP158(block.Number()))
if err != nil {
return StorageRangeResult{}, err
}

View File

@ -1434,10 +1434,13 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
log.Crit("Failed to write block into disk", "err", err)
}
// Commit all cached state changes into underlying memory database.
root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number()))
if err != nil {
return NonStatTy, err
root, stateChanges, commitErr := state.Commit(bc.chainConfig.IsEIP158(block.Number()))
log.Debug("StateChanges", "count", len(stateChanges))
if commitErr != nil {
return NonStatTy, commitErr
}
triedb := bc.stateCache.TrieDB()
// If we're running an archive node, always flush

View File

@ -216,7 +216,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
block, _ := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)
// Write state changes to db
root, err := statedb.Commit(config.IsEIP158(b.header.Number))
root, _, err := statedb.Commit(config.IsEIP158(b.header.Number))
if err != nil {
panic(fmt.Sprintf("state write error: %v", err))
}

View File

@ -167,7 +167,7 @@ func TestSnapshot2(t *testing.T) {
so0.deleted = false
state.setStateObject(so0)
root, _ := state.Commit(false)
root, _, _ := state.Commit(false)
state.Reset(root)
// and one with deleted == true

View File

@ -802,28 +802,51 @@ func (s *StateDB) clearJournalAndRefund() {
s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entires
}
// ModifiedAccount is an Account and it's Storage trie that has changed in the current block.
type ModifiedAccount struct {
Account
Storage
}
// StateChanges are a map between an Account's address to it's ModifiedAccount.
type StateChanges map[common.Address]ModifiedAccount
// Commit writes the state to the underlying in-memory trie database.
func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, StateChanges, error) {
if s.dbErr != nil {
return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
return common.Hash{}, nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
}
// Finalize any pending changes and merge everything into the tries
s.IntermediateRoot(deleteEmptyObjects)
stateChanges := make(StateChanges)
// Commit objects to the trie, measuring the elapsed time
for addr := range s.stateObjectsDirty {
modifiedAccount := ModifiedAccount{}
if obj := s.stateObjects[addr]; !obj.deleted {
// Write any contract code associated with the state object
if obj.code != nil && obj.dirtyCode {
s.db.TrieDB().InsertBlob(common.BytesToHash(obj.CodeHash()), obj.code)
obj.dirtyCode = false
}
// Add the account to the modifiedAccounts map
modifiedAccount = ModifiedAccount{Account: obj.data}
// Add the origin storage to the modifiedAccounts map before it's committed (and flushed from in-memory)
modifiedAccount.Storage = obj.originStorage
// Write any storage changes in the state object to its storage trie
if err := obj.CommitTrie(s.db); err != nil {
return common.Hash{}, err
return common.Hash{}, StateChanges{}, err
}
}
stateChanges[addr] = modifiedAccount
}
if len(s.stateObjectsDirty) > 0 {
s.stateObjectsDirty = make(map[common.Address]struct{})
}
@ -867,5 +890,6 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
}
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
}
return root, err
return root, stateChanges, err
}

View File

@ -67,6 +67,114 @@ func TestUpdateLeaks(t *testing.T) {
it.Release()
}
func TestStateChangesEmittedFromCommit(t *testing.T) {
// Create an empty state database
db := rawdb.NewMemoryDatabase()
state, _ := New(common.Hash{}, NewDatabase(db), nil)
// Commit 1: Create accounts with storage
expectedStateChangesOne := make(StateChanges)
for i := byte(0); i < 2; i++ {
addr := common.BytesToAddress([]byte{i})
modifiedAccount := ModifiedAccount{
Storage: make(map[common.Hash]common.Hash),
Account: Account{
Nonce: 0,
Balance: big.NewInt(0),
Root: emptyRoot,
CodeHash: emptyCodeHash,
},
}
newBalance := big.NewInt(int64(i))
state.SetBalance(addr, newBalance)
modifiedAccount.Account.Balance = newBalance
if i%2 == 0 {
storageKey := common.BytesToHash([]byte{i, i, i})
storageValue := common.BytesToHash([]byte{i, i, i, i})
modifiedAccount.Storage[storageKey] = storageValue
state.SetState(addr, storageKey, storageValue)
}
// Collect modified accounts to assert against
expectedStateChangesOne[addr] = modifiedAccount
}
_, actualStateChangesOne, commitErr := state.Commit(false)
if commitErr != nil {
t.Errorf("error committing to statedb")
}
assertStateChanges(actualStateChangesOne, expectedStateChangesOne, t)
// Commit 2: Update existing account storage
expectedStateChangesTwo := make(StateChanges)
var counter int
for addr, account := range expectedStateChangesOne {
// Only update some of the account storage
if counter%2 == 0 {
newBalance := big.NewInt(100)
account.Balance = newBalance
state.AddBalance(addr, newBalance)
for storageKey := range account.Storage {
updatedStorageValue := common.BytesToHash([]byte{0})
// Collect modified accounts to assert against
account.Storage[storageKey] = updatedStorageValue
expectedStateChangesTwo[addr] = account
state.SetState(addr, storageKey, updatedStorageValue)
}
}
counter++
}
_, stateChangesTwo, commitTwoErr := state.Commit(false)
if commitTwoErr != nil {
t.Errorf("error committing to statedb")
}
assertStateChanges(stateChangesTwo, expectedStateChangesTwo, t)
}
func assertStateChanges(actualChanges, expectedChanges StateChanges, t *testing.T) {
if len(actualChanges) != len(expectedChanges) {
t.Error("Test failure:", t.Name())
t.Logf("Mismatch in number of StateChanges.ModifiedAccounts. actual: %v, expected: %v", len(actualChanges), len(expectedChanges))
}
for addr, account := range actualChanges {
expected := expectedChanges[addr]
if !reflect.DeepEqual(account.Nonce, expected.Nonce) {
t.Error("Test failure:", t.Name())
t.Errorf("Account Nonce does not match expected. actual: %v, expected: %v", account.Nonce, expected.Nonce)
}
if !reflect.DeepEqual(account.Balance, expected.Balance) {
t.Error("Test failure:", t.Name())
t.Errorf("Account Balance does not match expected. actual: %v, expected: %v", account.Balance, expected.Balance)
}
if !reflect.DeepEqual(account.CodeHash, expected.CodeHash) {
t.Error("Test failure:", t.Name())
t.Errorf("Account CodeHash does not match expected. actual: %v, expected: %v", account.CodeHash, expected.CodeHash)
}
// Not asserting on the Account's Root because it's not easy to get the root between `SetState` calls
if !reflect.DeepEqual(account.Storage, expected.Storage) {
t.Error("Test failure:", t.Name())
t.Errorf("Account Storage does not match expected. actual: %v, expected: %v", account.Storage, expected.Storage)
}
}
}
// Tests that no intermediate state of an object is stored into the database,
// only the one right before the commit.
func TestIntermediateLeaks(t *testing.T) {
@ -102,7 +210,7 @@ func TestIntermediateLeaks(t *testing.T) {
}
// Commit and cross check the databases.
transRoot, err := transState.Commit(false)
transRoot, _, err := transState.Commit(false)
if err != nil {
t.Fatalf("failed to commit transition state: %v", err)
}
@ -110,7 +218,7 @@ func TestIntermediateLeaks(t *testing.T) {
t.Errorf("can not commit trie %v to persistent database", transRoot.Hex())
}
finalRoot, err := finalState.Commit(false)
finalRoot, _, err := finalState.Commit(false)
if err != nil {
t.Fatalf("failed to commit final state: %v", err)
}
@ -459,7 +567,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
func TestTouchDelete(t *testing.T) {
s := newStateTest()
s.state.GetOrNewStateObject(common.Address{})
root, _ := s.state.Commit(false)
root, _, _ := s.state.Commit(false)
s.state.Reset(root)
snapshot := s.state.Snapshot()
@ -661,7 +769,7 @@ func TestDeleteCreateRevert(t *testing.T) {
addr := toAddr([]byte("so"))
state.SetBalance(addr, big.NewInt(1))
root, _ := state.Commit(false)
root, _, _ := state.Commit(false)
state.Reset(root)
// Simulate self-destructing in one transaction, then create-reverting in another
@ -673,7 +781,7 @@ func TestDeleteCreateRevert(t *testing.T) {
state.RevertToSnapshot(id)
// Commit the entire state and make sure we don't crash and have the correct state
root, _ = state.Commit(true)
root, _, _ = state.Commit(true)
state.Reset(root)
if state.getStateObject(addr) != nil {
@ -698,7 +806,7 @@ func TestMissingTrieNodes(t *testing.T) {
a2 := toAddr([]byte("another"))
state.SetBalance(a2, big.NewInt(100))
state.SetCode(a2, []byte{1, 2, 4})
root, _ = state.Commit(false)
root, _, _ = state.Commit(false)
t.Logf("root: %x", root)
// force-flush
state.Database().TrieDB().Cap(0)
@ -722,7 +830,7 @@ func TestMissingTrieNodes(t *testing.T) {
}
// Modify the state
state.SetBalance(addr, big.NewInt(2))
root, err := state.Commit(false)
root, err, _ := state.Commit(false)
if err == nil {
t.Fatalf("expected error, got root :%x", root)
}

View File

@ -62,7 +62,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
state.updateStateObject(obj)
accounts = append(accounts, acc)
}
root, _ := state.Commit(false)
root, _, _ := state.Commit(false)
// Return the generated state
return db, root, accounts

View File

@ -295,7 +295,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
break
}
// Finalize the state so any modifications are written to the trie
root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
root, _, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
if err != nil {
failed = err
break
@ -681,7 +681,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err)
}
// Finalize the state so any modifications are written to the trie
root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
root, _, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
if err != nil {
return nil, err
}

View File

@ -218,7 +218,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo
}
}
// Commit and re-open to start with a clean state.
root, _ := statedb.Commit(false)
root, _, _ := statedb.Commit(false)
var snaps *snapshot.Tree
if snapshotter {