aboutsummaryrefslogtreecommitdiffstats
path: root/core/state
diff options
context:
space:
mode:
authorFelix Lange <fjl@users.noreply.github.com>2017-06-27 21:57:06 +0800
committerGitHub <noreply@github.com>2017-06-27 21:57:06 +0800
commit9e5f03b6c487175cc5aa1224e5e12fd573f483a7 (patch)
tree475e573ff6c7e77cd069a2f6238afdb27d4bce43 /core/state
parentbb366271fe33cf87b462dc5a25ac6c448ac6d2e1 (diff)
downloaddexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.gz
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.bz2
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.lz
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.xz
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.zst
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.zip
core/state: access trie through Database interface, track errors (#14589)
With this commit, core/state's access to the underlying key/value database is mediated through an interface. Database errors are tracked in StateDB and returned by CommitTo or the new Error method. Motivation for this change: We can remove the light client's duplicated copy of core/state. The light client now supports node iteration, so tracing and storage enumeration can work with the light client (not implemented in this commit).
Diffstat (limited to 'core/state')
-rw-r--r--core/state/database.go154
-rw-r--r--core/state/dump.go2
-rw-r--r--core/state/iterator.go13
-rw-r--r--core/state/iterator_test.go8
-rw-r--r--core/state/managed_state_test.go2
-rw-r--r--core/state/state_object.go56
-rw-r--r--core/state/state_test.go39
-rw-r--r--core/state/statedb.go139
-rw-r--r--core/state/statedb_test.go40
-rw-r--r--core/state/sync_test.go75
10 files changed, 312 insertions, 216 deletions
diff --git a/core/state/database.go b/core/state/database.go
new file mode 100644
index 000000000..946625e76
--- /dev/null
+++ b/core/state/database.go
@@ -0,0 +1,154 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package state
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/trie"
+ lru "github.com/hashicorp/golang-lru"
+)
+
+// Trie cache generation limit after which to evic trie nodes from memory.
+var MaxTrieCacheGen = uint16(120)
+
+const (
+ // Number of past tries to keep. This value is chosen such that
+ // reasonable chain reorg depths will hit an existing trie.
+ maxPastTries = 12
+
+ // Number of codehash->size associations to keep.
+ codeSizeCacheSize = 100000
+)
+
+// Database wraps access to tries and contract code.
+type Database interface {
+ // Accessing tries:
+ // OpenTrie opens the main account trie.
+ // OpenStorageTrie opens the storage trie of an account.
+ OpenTrie(root common.Hash) (Trie, error)
+ OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
+ // Accessing contract code:
+ ContractCode(addrHash, codeHash common.Hash) ([]byte, error)
+ ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
+ // CopyTrie returns an independent copy of the given trie.
+ CopyTrie(Trie) Trie
+}
+
+// Trie is a Ethereum Merkle Trie.
+type Trie interface {
+ TryGet(key []byte) ([]byte, error)
+ TryUpdate(key, value []byte) error
+ TryDelete(key []byte) error
+ CommitTo(trie.DatabaseWriter) (common.Hash, error)
+ Hash() common.Hash
+ NodeIterator(startKey []byte) trie.NodeIterator
+ GetKey([]byte) []byte // TODO(fjl): remove this when SecureTrie is removed
+}
+
+// NewDatabase creates a backing store for state. The returned database is safe for
+// concurrent use and retains cached trie nodes in memory.
+func NewDatabase(db ethdb.Database) Database {
+ csc, _ := lru.New(codeSizeCacheSize)
+ return &cachingDB{db: db, codeSizeCache: csc}
+}
+
+type cachingDB struct {
+ db ethdb.Database
+ mu sync.Mutex
+ pastTries []*trie.SecureTrie
+ codeSizeCache *lru.Cache
+}
+
+func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ for i := len(db.pastTries) - 1; i >= 0; i-- {
+ if db.pastTries[i].Hash() == root {
+ return cachedTrie{db.pastTries[i].Copy(), db}, nil
+ }
+ }
+ tr, err := trie.NewSecure(root, db.db, MaxTrieCacheGen)
+ if err != nil {
+ return nil, err
+ }
+ return cachedTrie{tr, db}, nil
+}
+
+func (db *cachingDB) pushTrie(t *trie.SecureTrie) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ if len(db.pastTries) >= maxPastTries {
+ copy(db.pastTries, db.pastTries[1:])
+ db.pastTries[len(db.pastTries)-1] = t
+ } else {
+ db.pastTries = append(db.pastTries, t)
+ }
+}
+
+func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
+ return trie.NewSecure(root, db.db, 0)
+}
+
+func (db *cachingDB) CopyTrie(t Trie) Trie {
+ switch t := t.(type) {
+ case cachedTrie:
+ return cachedTrie{t.SecureTrie.Copy(), db}
+ case *trie.SecureTrie:
+ return t.Copy()
+ default:
+ panic(fmt.Errorf("unknown trie type %T", t))
+ }
+}
+
+func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
+ code, err := db.db.Get(codeHash[:])
+ if err == nil {
+ db.codeSizeCache.Add(codeHash, len(code))
+ }
+ return code, err
+}
+
+func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
+ if cached, ok := db.codeSizeCache.Get(codeHash); ok {
+ return cached.(int), nil
+ }
+ code, err := db.ContractCode(addrHash, codeHash)
+ if err == nil {
+ db.codeSizeCache.Add(codeHash, len(code))
+ }
+ return len(code), err
+}
+
+// cachedTrie inserts its trie into a cachingDB on commit.
+type cachedTrie struct {
+ *trie.SecureTrie
+ db *cachingDB
+}
+
+func (m cachedTrie) CommitTo(dbw trie.DatabaseWriter) (common.Hash, error) {
+ root, err := m.SecureTrie.CommitTo(dbw)
+ if err == nil {
+ m.db.pushTrie(m.SecureTrie)
+ }
+ return root, err
+}
diff --git a/core/state/dump.go b/core/state/dump.go
index ffa1a7283..46e612850 100644
--- a/core/state/dump.go
+++ b/core/state/dump.go
@@ -41,7 +41,7 @@ type Dump struct {
func (self *StateDB) RawDump() Dump {
dump := Dump{
- Root: common.Bytes2Hex(self.trie.Root()),
+ Root: fmt.Sprintf("%x", self.trie.Hash()),
Accounts: make(map[string]DumpAccount),
}
diff --git a/core/state/iterator.go b/core/state/iterator.go
index a8a2722ae..6a5c73d3d 100644
--- a/core/state/iterator.go
+++ b/core/state/iterator.go
@@ -19,7 +19,6 @@ package state
import (
"bytes"
"fmt"
- "math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
@@ -105,16 +104,11 @@ func (it *NodeIterator) step() error {
return nil
}
// Otherwise we've reached an account node, initiate data iteration
- var account struct {
- Nonce uint64
- Balance *big.Int
- Root common.Hash
- CodeHash []byte
- }
+ var account Account
if err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob()), &account); err != nil {
return err
}
- dataTrie, err := trie.New(account.Root, it.state.db)
+ dataTrie, err := it.state.db.OpenStorageTrie(common.BytesToHash(it.stateIt.LeafKey()), account.Root)
if err != nil {
return err
}
@@ -124,7 +118,8 @@ func (it *NodeIterator) step() error {
}
if !bytes.Equal(account.CodeHash, emptyCodeHash) {
it.codeHash = common.BytesToHash(account.CodeHash)
- it.code, err = it.state.db.Get(account.CodeHash)
+ addrHash := common.BytesToHash(it.stateIt.LeafKey())
+ it.code, err = it.state.db.ContractCode(addrHash, common.BytesToHash(account.CodeHash))
if err != nil {
return fmt.Errorf("code %x: %v", account.CodeHash, err)
}
diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go
index aa9c5b728..ff66ba7a9 100644
--- a/core/state/iterator_test.go
+++ b/core/state/iterator_test.go
@@ -21,13 +21,12 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/ethdb"
)
// Tests that the node iterator indeed walks over the entire database contents.
func TestNodeIteratorCoverage(t *testing.T) {
// Create some arbitrary test state to iterate
- db, root, _ := makeTestState()
+ db, mem, root, _ := makeTestState()
state, err := New(root, db)
if err != nil {
@@ -40,13 +39,14 @@ func TestNodeIteratorCoverage(t *testing.T) {
hashes[it.Hash] = struct{}{}
}
}
+
// Cross check the hashes and the database itself
for hash := range hashes {
- if _, err := db.Get(hash.Bytes()); err != nil {
+ if _, err := mem.Get(hash.Bytes()); err != nil {
t.Errorf("failed to retrieve reported node %x: %v", hash, err)
}
}
- for _, key := range db.(*ethdb.MemDatabase).Keys() {
+ for _, key := range mem.Keys() {
if bytes.HasPrefix(key, []byte("secure-key-")) {
continue
}
diff --git a/core/state/managed_state_test.go b/core/state/managed_state_test.go
index ea5737a08..1cfdd3a89 100644
--- a/core/state/managed_state_test.go
+++ b/core/state/managed_state_test.go
@@ -27,7 +27,7 @@ var addr = common.BytesToAddress([]byte("test"))
func create() (*ManagedState, *account) {
db, _ := ethdb.NewMemDatabase()
- statedb, _ := New(common.Hash{}, db)
+ statedb, _ := New(common.Hash{}, NewDatabase(db))
ms := ManageState(statedb)
ms.StateDB.SetNonce(addr, 100)
ms.accounts[addr] = newAccount(ms.StateDB.getStateObject(addr))
diff --git a/core/state/state_object.go b/core/state/state_object.go
index dcad9d068..b2378c69c 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -62,9 +62,10 @@ func (self Storage) Copy() Storage {
// Account values can be accessed and modified through the object.
// Finally, call CommitTrie to write the modified storage trie into a database.
type stateObject struct {
- address common.Address // Ethereum address of this account
- data Account
- db *StateDB
+ address common.Address
+ addrHash common.Hash // hash of ethereum address of the account
+ data Account
+ db *StateDB
// DB error.
// State objects are used by the consensus core and VM which are
@@ -74,8 +75,8 @@ type stateObject struct {
dbErr error
// Write caches.
- trie *trie.SecureTrie // storage trie, which becomes non-nil on first access
- code Code // contract bytecode, which gets set when code is loaded
+ trie Trie // storage trie, which becomes non-nil on first access
+ code Code // contract bytecode, which gets set when code is loaded
cachedStorage Storage // Storage entry cache to avoid duplicate reads
dirtyStorage Storage // Storage entries that need to be flushed to disk
@@ -112,7 +113,15 @@ func newObject(db *StateDB, address common.Address, data Account, onDirty func(a
if data.CodeHash == nil {
data.CodeHash = emptyCodeHash
}
- return &stateObject{db: db, address: address, data: data, cachedStorage: make(Storage), dirtyStorage: make(Storage), onDirty: onDirty}
+ return &stateObject{
+ db: db,
+ address: address,
+ addrHash: crypto.Keccak256Hash(address[:]),
+ data: data,
+ cachedStorage: make(Storage),
+ dirtyStorage: make(Storage),
+ onDirty: onDirty,
+ }
}
// EncodeRLP implements rlp.Encoder.
@@ -148,12 +157,12 @@ func (c *stateObject) touch() {
c.touched = true
}
-func (c *stateObject) getTrie(db trie.Database) *trie.SecureTrie {
+func (c *stateObject) getTrie(db Database) Trie {
if c.trie == nil {
var err error
- c.trie, err = trie.NewSecure(c.data.Root, db, 0)
+ c.trie, err = db.OpenStorageTrie(c.addrHash, c.data.Root)
if err != nil {
- c.trie, _ = trie.NewSecure(common.Hash{}, db, 0)
+ c.trie, _ = db.OpenStorageTrie(c.addrHash, common.Hash{})
c.setError(fmt.Errorf("can't create storage trie: %v", err))
}
}
@@ -161,13 +170,18 @@ func (c *stateObject) getTrie(db trie.Database) *trie.SecureTrie {
}
// GetState returns a value in account storage.
-func (self *stateObject) GetState(db trie.Database, key common.Hash) common.Hash {
+func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
value, exists := self.cachedStorage[key]
if exists {
return value
}
// Load from DB in case it is missing.
- if enc := self.getTrie(db).Get(key[:]); len(enc) > 0 {
+ enc, err := self.getTrie(db).TryGet(key[:])
+ if err != nil {
+ self.setError(err)
+ return common.Hash{}
+ }
+ if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
self.setError(err)
@@ -181,7 +195,7 @@ func (self *stateObject) GetState(db trie.Database, key common.Hash) common.Hash
}
// SetState updates a value in account storage.
-func (self *stateObject) SetState(db trie.Database, key, value common.Hash) {
+func (self *stateObject) SetState(db Database, key, value common.Hash) {
self.db.journal = append(self.db.journal, storageChange{
account: &self.address,
key: key,
@@ -201,30 +215,30 @@ func (self *stateObject) setState(key, value common.Hash) {
}
// updateTrie writes cached storage modifications into the object's storage trie.
-func (self *stateObject) updateTrie(db trie.Database) *trie.SecureTrie {
+func (self *stateObject) updateTrie(db Database) Trie {
tr := self.getTrie(db)
for key, value := range self.dirtyStorage {
delete(self.dirtyStorage, key)
if (value == common.Hash{}) {
- tr.Delete(key[:])
+ self.setError(tr.TryDelete(key[:]))
continue
}
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
- tr.Update(key[:], v)
+ self.setError(tr.TryUpdate(key[:], v))
}
return tr
}
// UpdateRoot sets the trie root to the current root hash of
-func (self *stateObject) updateRoot(db trie.Database) {
+func (self *stateObject) updateRoot(db Database) {
self.updateTrie(db)
self.data.Root = self.trie.Hash()
}
// CommitTrie the storage trie of the object to dwb.
// This updates the trie root.
-func (self *stateObject) CommitTrie(db trie.Database, dbw trie.DatabaseWriter) error {
+func (self *stateObject) CommitTrie(db Database, dbw trie.DatabaseWriter) error {
self.updateTrie(db)
if self.dbErr != nil {
return self.dbErr
@@ -282,9 +296,7 @@ func (c *stateObject) ReturnGas(gas *big.Int) {}
func (self *stateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *stateObject {
stateObject := newObject(db, self.address, self.data, onDirty)
if self.trie != nil {
- // A shallow copy makes the two tries independent.
- cpy := *self.trie
- stateObject.trie = &cpy
+ stateObject.trie = db.db.CopyTrie(self.trie)
}
stateObject.code = self.code
stateObject.dirtyStorage = self.dirtyStorage.Copy()
@@ -305,14 +317,14 @@ func (c *stateObject) Address() common.Address {
}
// Code returns the contract code associated with this object, if any.
-func (self *stateObject) Code(db trie.Database) []byte {
+func (self *stateObject) Code(db Database) []byte {
if self.code != nil {
return self.code
}
if bytes.Equal(self.CodeHash(), emptyCodeHash) {
return nil
}
- code, err := db.Get(self.CodeHash())
+ code, err := db.ContractCode(self.addrHash, common.BytesToHash(self.CodeHash()))
if err != nil {
self.setError(fmt.Errorf("can't load code hash %x: %v", self.CodeHash(), err))
}
diff --git a/core/state/state_test.go b/core/state/state_test.go
index 3bc63c148..bbae3685b 100644
--- a/core/state/state_test.go
+++ b/core/state/state_test.go
@@ -21,14 +21,14 @@ import (
"math/big"
"testing"
- checker "gopkg.in/check.v1"
-
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
+ checker "gopkg.in/check.v1"
)
type StateSuite struct {
+ db *ethdb.MemDatabase
state *StateDB
}
@@ -48,7 +48,7 @@ func (s *StateSuite) TestDump(c *checker.C) {
// write some of them to the trie
s.state.updateStateObject(obj1)
s.state.updateStateObject(obj2)
- s.state.Commit(false)
+ s.state.CommitTo(s.db, false)
// check that dump contains the state objects that are in trie
got := string(s.state.Dump())
@@ -87,23 +87,20 @@ func (s *StateSuite) TestDump(c *checker.C) {
}
func (s *StateSuite) SetUpTest(c *checker.C) {
- db, _ := ethdb.NewMemDatabase()
- s.state, _ = New(common.Hash{}, db)
+ s.db, _ = ethdb.NewMemDatabase()
+ s.state, _ = New(common.Hash{}, NewDatabase(s.db))
}
-func TestNull(t *testing.T) {
- db, _ := ethdb.NewMemDatabase()
- state, _ := New(common.Hash{}, db)
-
+func (s *StateSuite) TestNull(c *checker.C) {
address := common.HexToAddress("0x823140710bf13990e4500136726d8b55")
- state.CreateAccount(address)
+ s.state.CreateAccount(address)
//value := common.FromHex("0x823140710bf13990e4500136726d8b55")
var value common.Hash
- state.SetState(address, common.Hash{}, value)
- state.Commit(false)
- value = state.GetState(address, common.Hash{})
+ s.state.SetState(address, common.Hash{}, value)
+ s.state.CommitTo(s.db, false)
+ value = s.state.GetState(address, common.Hash{})
if !common.EmptyHash(value) {
- t.Errorf("expected empty hash. got %x", value)
+ c.Errorf("expected empty hash. got %x", value)
}
}
@@ -129,17 +126,15 @@ func (s *StateSuite) TestSnapshot(c *checker.C) {
c.Assert(data1, checker.DeepEquals, res)
}
-func TestSnapshotEmpty(t *testing.T) {
- db, _ := ethdb.NewMemDatabase()
- state, _ := New(common.Hash{}, db)
- state.RevertToSnapshot(state.Snapshot())
+func (s *StateSuite) TestSnapshotEmpty(c *checker.C) {
+ s.state.RevertToSnapshot(s.state.Snapshot())
}
// use testing instead of checker because checker does not support
// printing/logging in tests (-check.vv does not work)
func TestSnapshot2(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
- state, _ := New(common.Hash{}, db)
+ state, _ := New(common.Hash{}, NewDatabase(db))
stateobjaddr0 := toAddr([]byte("so0"))
stateobjaddr1 := toAddr([]byte("so1"))
@@ -160,7 +155,7 @@ func TestSnapshot2(t *testing.T) {
so0.deleted = false
state.setStateObject(so0)
- root, _ := state.Commit(false)
+ root, _ := state.CommitTo(db, false)
state.Reset(root)
// and one with deleted == true
@@ -182,8 +177,8 @@ func TestSnapshot2(t *testing.T) {
so0Restored := state.getStateObject(stateobjaddr0)
// Update lazily-loaded values before comparing.
- so0Restored.GetState(db, storageaddr)
- so0Restored.Code(db)
+ so0Restored.GetState(state.db, storageaddr)
+ so0Restored.Code(state.db)
// non-deleted is equal (restored)
compareStateObjects(so0Restored, so0, t)
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 05869a0c8..694374f82 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -26,23 +26,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
- lru "github.com/hashicorp/golang-lru"
-)
-
-// Trie cache generation limit after which to evic trie nodes from memory.
-var MaxTrieCacheGen = uint16(120)
-
-const (
- // Number of past tries to keep. This value is chosen such that
- // reasonable chain reorg depths will hit an existing trie.
- maxPastTries = 12
-
- // Number of codehash->size associations to keep.
- codeSizeCacheSize = 100000
)
type revision struct {
@@ -56,16 +42,21 @@ type revision struct {
// * Contracts
// * Accounts
type StateDB struct {
- db ethdb.Database
- trie *trie.SecureTrie
- pastTries []*trie.SecureTrie
- codeSizeCache *lru.Cache
+ db Database
+ trie Trie
// This map holds 'live' objects, which will get modified while processing a state transition.
stateObjects map[common.Address]*stateObject
stateObjectsDirty map[common.Address]struct{}
stateObjectsDestructed map[common.Address]struct{}
+ // DB error.
+ // State objects are used by the consensus core and VM which are
+ // unable to deal with database-level errors. Any error that occurs
+ // during a database read is memoized here and will eventually be returned
+ // by StateDB.Commit.
+ dbErr error
+
// The refund counter, also used by state transitioning.
refund *big.Int
@@ -86,16 +77,14 @@ type StateDB struct {
}
// Create a new state from a given trie
-func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
- tr, err := trie.NewSecure(root, db, MaxTrieCacheGen)
+func New(root common.Hash, db Database) (*StateDB, error) {
+ tr, err := db.OpenTrie(root)
if err != nil {
return nil, err
}
- csc, _ := lru.New(codeSizeCacheSize)
return &StateDB{
db: db,
trie: tr,
- codeSizeCache: csc,
stateObjects: make(map[common.Address]*stateObject),
stateObjectsDirty: make(map[common.Address]struct{}),
stateObjectsDestructed: make(map[common.Address]struct{}),
@@ -105,36 +94,21 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
}, nil
}
-// New creates a new statedb by reusing any journalled tries to avoid costly
-// disk io.
-func (self *StateDB) New(root common.Hash) (*StateDB, error) {
- self.lock.Lock()
- defer self.lock.Unlock()
-
- tr, err := self.openTrie(root)
- if err != nil {
- return nil, err
+// setError remembers the first non-nil error it is called with.
+func (self *StateDB) setError(err error) {
+ if self.dbErr == nil {
+ self.dbErr = err
}
- return &StateDB{
- db: self.db,
- trie: tr,
- codeSizeCache: self.codeSizeCache,
- stateObjects: make(map[common.Address]*stateObject),
- stateObjectsDirty: make(map[common.Address]struct{}),
- stateObjectsDestructed: make(map[common.Address]struct{}),
- refund: new(big.Int),
- logs: make(map[common.Hash][]*types.Log),
- preimages: make(map[common.Hash][]byte),
- }, nil
+}
+
+func (self *StateDB) Error() error {
+ return self.dbErr
}
// Reset clears out all emphemeral state objects from the state db, but keeps
// the underlying state trie to avoid reloading data for the next operations.
func (self *StateDB) Reset(root common.Hash) error {
- self.lock.Lock()
- defer self.lock.Unlock()
-
- tr, err := self.openTrie(root)
+ tr, err := self.db.OpenTrie(root)
if err != nil {
return err
}
@@ -149,34 +123,9 @@ func (self *StateDB) Reset(root common.Hash) error {
self.logSize = 0
self.preimages = make(map[common.Hash][]byte)
self.clearJournalAndRefund()
-
return nil
}
-// openTrie creates a trie. It uses an existing trie if one is available
-// from the journal if available.
-func (self *StateDB) openTrie(root common.Hash) (*trie.SecureTrie, error) {
- for i := len(self.pastTries) - 1; i >= 0; i-- {
- if self.pastTries[i].Hash() == root {
- tr := *self.pastTries[i]
- return &tr, nil
- }
- }
- return trie.NewSecure(root, self.db, MaxTrieCacheGen)
-}
-
-func (self *StateDB) pushTrie(t *trie.SecureTrie) {
- self.lock.Lock()
- defer self.lock.Unlock()
-
- if len(self.pastTries) >= maxPastTries {
- copy(self.pastTries, self.pastTries[1:])
- self.pastTries[len(self.pastTries)-1] = t
- } else {
- self.pastTries = append(self.pastTries, t)
- }
-}
-
func (self *StateDB) AddLog(log *types.Log) {
self.journal = append(self.journal, addLogChange{txhash: self.thash})
@@ -254,10 +203,7 @@ func (self *StateDB) GetNonce(addr common.Address) uint64 {
func (self *StateDB) GetCode(addr common.Address) []byte {
stateObject := self.getStateObject(addr)
if stateObject != nil {
- code := stateObject.Code(self.db)
- key := common.BytesToHash(stateObject.CodeHash())
- self.codeSizeCache.Add(key, len(code))
- return code
+ return stateObject.Code(self.db)
}
return nil
}
@@ -267,13 +213,12 @@ func (self *StateDB) GetCodeSize(addr common.Address) int {
if stateObject == nil {
return 0
}
- key := common.BytesToHash(stateObject.CodeHash())
- if cached, ok := self.codeSizeCache.Get(key); ok {
- return cached.(int)
+ if stateObject.code != nil {
+ return len(stateObject.code)
}
- size := len(stateObject.Code(self.db))
- if stateObject.dbErr == nil {
- self.codeSizeCache.Add(key, size)
+ size, err := self.db.ContractCodeSize(stateObject.addrHash, common.BytesToHash(stateObject.CodeHash()))
+ if err != nil {
+ self.setError(err)
}
return size
}
@@ -296,7 +241,7 @@ func (self *StateDB) GetState(a common.Address, b common.Hash) common.Hash {
// StorageTrie returns the storage trie of an account.
// The return value is a copy and is nil for non-existent accounts.
-func (self *StateDB) StorageTrie(a common.Address) *trie.SecureTrie {
+func (self *StateDB) StorageTrie(a common.Address) Trie {
stateObject := self.getStateObject(a)
if stateObject == nil {
return nil
@@ -394,14 +339,14 @@ func (self *StateDB) updateStateObject(stateObject *stateObject) {
if err != nil {
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
}
- self.trie.Update(addr[:], data)
+ self.setError(self.trie.TryUpdate(addr[:], data))
}
// deleteStateObject removes the given object from the state trie.
func (self *StateDB) deleteStateObject(stateObject *stateObject) {
stateObject.deleted = true
addr := stateObject.Address()
- self.trie.Delete(addr[:])
+ self.setError(self.trie.TryDelete(addr[:]))
}
// Retrieve a state object given my the address. Returns nil if not found.
@@ -415,8 +360,9 @@ func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObje
}
// Load the object from the database.
- enc := self.trie.Get(addr[:])
+ enc, err := self.trie.TryGet(addr[:])
if len(enc) == 0 {
+ self.setError(err)
return nil
}
var data Account
@@ -512,8 +458,6 @@ func (self *StateDB) Copy() *StateDB {
state := &StateDB{
db: self.db,
trie: self.trie,
- pastTries: self.pastTries,
- codeSizeCache: self.codeSizeCache,
stateObjects: make(map[common.Address]*stateObject, len(self.stateObjectsDirty)),
stateObjectsDirty: make(map[common.Address]struct{}, len(self.stateObjectsDirty)),
stateObjectsDestructed: make(map[common.Address]struct{}, len(self.stateObjectsDestructed)),
@@ -636,23 +580,6 @@ func (s *StateDB) DeleteSuicides() {
}
}
-// Commit commits all state changes to the database.
-func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) {
- root, batch := s.CommitBatch(deleteEmptyObjects)
- return root, batch.Write()
-}
-
-// CommitBatch commits all state changes to a write batch but does not
-// execute the batch. It is used to validate state changes against
-// the root hash stored in a block.
-func (s *StateDB) CommitBatch(deleteEmptyObjects bool) (root common.Hash, batch ethdb.Batch) {
- batch = s.db.NewBatch()
- root, _ = s.CommitTo(batch, deleteEmptyObjects)
-
- log.Debug("Trie cache stats after commit", "misses", trie.CacheMisses(), "unloads", trie.CacheUnloads())
- return root, batch
-}
-
func (s *StateDB) clearJournalAndRefund() {
s.journal = nil
s.validRevisions = s.validRevisions[:0]
@@ -690,8 +617,6 @@ func (s *StateDB) CommitTo(dbw trie.DatabaseWriter, deleteEmptyObjects bool) (ro
}
// Write trie changes.
root, err = s.trie.CommitTo(dbw)
- if err == nil {
- s.pushTrie(s.trie)
- }
+ log.Debug("Trie cache stats after commit", "misses", trie.CacheMisses(), "unloads", trie.CacheUnloads())
return root, err
}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index 72b638f97..b2bd18e65 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -28,6 +28,8 @@ import (
"testing"
"testing/quick"
+ check "gopkg.in/check.v1"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
@@ -38,7 +40,7 @@ import (
func TestUpdateLeaks(t *testing.T) {
// Create an empty state database
db, _ := ethdb.NewMemDatabase()
- state, _ := New(common.Hash{}, db)
+ state, _ := New(common.Hash{}, NewDatabase(db))
// Update it with some accounts
for i := byte(0); i < 255; i++ {
@@ -66,8 +68,8 @@ func TestIntermediateLeaks(t *testing.T) {
// Create two state databases, one transitioning to the final state, the other final from the beginning
transDb, _ := ethdb.NewMemDatabase()
finalDb, _ := ethdb.NewMemDatabase()
- transState, _ := New(common.Hash{}, transDb)
- finalState, _ := New(common.Hash{}, finalDb)
+ transState, _ := New(common.Hash{}, NewDatabase(transDb))
+ finalState, _ := New(common.Hash{}, NewDatabase(finalDb))
modify := func(state *StateDB, addr common.Address, i, tweak byte) {
state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak)))
@@ -95,10 +97,10 @@ func TestIntermediateLeaks(t *testing.T) {
}
// Commit and cross check the databases.
- if _, err := transState.Commit(false); err != nil {
+ if _, err := transState.CommitTo(transDb, false); err != nil {
t.Fatalf("failed to commit transition state: %v", err)
}
- if _, err := finalState.Commit(false); err != nil {
+ if _, err := finalState.CommitTo(finalDb, false); err != nil {
t.Fatalf("failed to commit final state: %v", err)
}
for _, key := range finalDb.Keys() {
@@ -282,7 +284,7 @@ func (test *snapshotTest) run() bool {
// Run all actions and create snapshots.
var (
db, _ = ethdb.NewMemDatabase()
- state, _ = New(common.Hash{}, db)
+ state, _ = New(common.Hash{}, NewDatabase(db))
snapshotRevs = make([]int, len(test.snapshots))
sindex = 0
)
@@ -297,7 +299,7 @@ func (test *snapshotTest) run() bool {
// Revert all snapshots in reverse order. Each revert must yield a state
// that is equivalent to fresh state with all actions up the snapshot applied.
for sindex--; sindex >= 0; sindex-- {
- checkstate, _ := New(common.Hash{}, db)
+ checkstate, _ := New(common.Hash{}, NewDatabase(db))
for _, action := range test.actions[:test.snapshots[sindex]] {
action.fn(action, checkstate)
}
@@ -354,21 +356,19 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
return nil
}
-func TestTouchDelete(t *testing.T) {
- db, _ := ethdb.NewMemDatabase()
- state, _ := New(common.Hash{}, db)
- state.GetOrNewStateObject(common.Address{})
- root, _ := state.Commit(false)
- state.Reset(root)
+func (s *StateSuite) TestTouchDelete(c *check.C) {
+ s.state.GetOrNewStateObject(common.Address{})
+ root, _ := s.state.CommitTo(s.db, false)
+ s.state.Reset(root)
- snapshot := state.Snapshot()
- state.AddBalance(common.Address{}, new(big.Int))
- if len(state.stateObjectsDirty) != 1 {
- t.Fatal("expected one dirty state object")
+ snapshot := s.state.Snapshot()
+ s.state.AddBalance(common.Address{}, new(big.Int))
+ if len(s.state.stateObjectsDirty) != 1 {
+ c.Fatal("expected one dirty state object")
}
- state.RevertToSnapshot(snapshot)
- if len(state.stateObjectsDirty) != 0 {
- t.Fatal("expected no dirty state object")
+ s.state.RevertToSnapshot(snapshot)
+ if len(s.state.stateObjectsDirty) != 0 {
+ c.Fatal("expected no dirty state object")
}
}
diff --git a/core/state/sync_test.go b/core/state/sync_test.go
index 108ebb320..06c572ea6 100644
--- a/core/state/sync_test.go
+++ b/core/state/sync_test.go
@@ -36,9 +36,10 @@ type testAccount struct {
}
// makeTestState create a sample test state to test node-wise reconstruction.
-func makeTestState() (ethdb.Database, common.Hash, []*testAccount) {
+func makeTestState() (Database, *ethdb.MemDatabase, common.Hash, []*testAccount) {
// Create an empty state
- db, _ := ethdb.NewMemDatabase()
+ mem, _ := ethdb.NewMemDatabase()
+ db := NewDatabase(mem)
state, _ := New(common.Hash{}, db)
// Fill it with some arbitrary data
@@ -60,17 +61,17 @@ func makeTestState() (ethdb.Database, common.Hash, []*testAccount) {
state.updateStateObject(obj)
accounts = append(accounts, acc)
}
- root, _ := state.Commit(false)
+ root, _ := state.CommitTo(mem, false)
// Return the generated state
- return db, root, accounts
+ return db, mem, root, accounts
}
// checkStateAccounts cross references a reconstructed state with an expected
// account array.
func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accounts []*testAccount) {
// Check root availability and state contents
- state, err := New(root, db)
+ state, err := New(root, NewDatabase(db))
if err != nil {
t.Fatalf("failed to create state trie at %x: %v", root, err)
}
@@ -90,13 +91,28 @@ func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accou
}
}
-// checkStateConsistency checks that all nodes in a state trie are indeed present.
+// checkTrieConsistency checks that all nodes in a (sub-)trie are indeed present.
+func checkTrieConsistency(db ethdb.Database, root common.Hash) error {
+ if v, _ := db.Get(root[:]); v == nil {
+ return nil // Consider a non existent state consistent.
+ }
+ trie, err := trie.New(root, db)
+ if err != nil {
+ return err
+ }
+ it := trie.NodeIterator(nil)
+ for it.Next(true) {
+ }
+ return it.Error()
+}
+
+// checkStateConsistency checks that all data of a state root is present.
func checkStateConsistency(db ethdb.Database, root common.Hash) error {
// Create and iterate a state trie rooted in a sub-node
if _, err := db.Get(root.Bytes()); err != nil {
- return nil // Consider a non existent state consistent
+ return nil // Consider a non existent state consistent.
}
- state, err := New(root, db)
+ state, err := New(root, NewDatabase(db))
if err != nil {
return err
}
@@ -122,7 +138,7 @@ func TestIterativeStateSyncBatched(t *testing.T) { testIterativeStateSync(t,
func testIterativeStateSync(t *testing.T, batch int) {
// Create a random state to copy
- srcDb, srcRoot, srcAccounts := makeTestState()
+ _, srcMem, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase()
@@ -132,7 +148,7 @@ func testIterativeStateSync(t *testing.T, batch int) {
for len(queue) > 0 {
results := make([]trie.SyncResult, len(queue))
for i, hash := range queue {
- data, err := srcDb.Get(hash.Bytes())
+ data, err := srcMem.Get(hash.Bytes())
if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
}
@@ -154,7 +170,7 @@ func testIterativeStateSync(t *testing.T, batch int) {
// partial results are returned, and the others sent only later.
func TestIterativeDelayedStateSync(t *testing.T) {
// Create a random state to copy
- srcDb, srcRoot, srcAccounts := makeTestState()
+ _, srcMem, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase()
@@ -165,7 +181,7 @@ func TestIterativeDelayedStateSync(t *testing.T) {
// Sync only half of the scheduled nodes
results := make([]trie.SyncResult, len(queue)/2+1)
for i, hash := range queue[:len(results)] {
- data, err := srcDb.Get(hash.Bytes())
+ data, err := srcMem.Get(hash.Bytes())
if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
}
@@ -191,7 +207,7 @@ func TestIterativeRandomStateSyncBatched(t *testing.T) { testIterativeRandomS
func testIterativeRandomStateSync(t *testing.T, batch int) {
// Create a random state to copy
- srcDb, srcRoot, srcAccounts := makeTestState()
+ _, srcMem, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase()
@@ -205,7 +221,7 @@ func testIterativeRandomStateSync(t *testing.T, batch int) {
// Fetch all the queued nodes in a random order
results := make([]trie.SyncResult, 0, len(queue))
for hash := range queue {
- data, err := srcDb.Get(hash.Bytes())
+ data, err := srcMem.Get(hash.Bytes())
if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
}
@@ -231,7 +247,7 @@ func testIterativeRandomStateSync(t *testing.T, batch int) {
// partial results are returned (Even those randomly), others sent only later.
func TestIterativeRandomDelayedStateSync(t *testing.T) {
// Create a random state to copy
- srcDb, srcRoot, srcAccounts := makeTestState()
+ _, srcMem, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase()
@@ -247,7 +263,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
for hash := range queue {
delete(queue, hash)
- data, err := srcDb.Get(hash.Bytes())
+ data, err := srcMem.Get(hash.Bytes())
if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
}
@@ -276,7 +292,9 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
// the database.
func TestIncompleteStateSync(t *testing.T) {
// Create a random state to copy
- srcDb, srcRoot, srcAccounts := makeTestState()
+ _, srcMem, srcRoot, srcAccounts := makeTestState()
+
+ checkTrieConsistency(srcMem, srcRoot)
// Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase()
@@ -288,7 +306,7 @@ func TestIncompleteStateSync(t *testing.T) {
// Fetch a batch of state nodes
results := make([]trie.SyncResult, len(queue))
for i, hash := range queue {
- data, err := srcDb.Get(hash.Bytes())
+ data, err := srcMem.Get(hash.Bytes())
if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
}
@@ -304,21 +322,18 @@ func TestIncompleteStateSync(t *testing.T) {
for _, result := range results {
added = append(added, result.Hash)
}
- // Check that all known sub-tries in the synced state is complete
- for _, root := range added {
- // Skim through the accounts and make sure the root hash is not a code node
- codeHash := false
+ // Check that all known sub-tries added so far are complete or missing entirely.
+ checkSubtries:
+ for _, hash := range added {
for _, acc := range srcAccounts {
- if root == crypto.Keccak256Hash(acc.code) {
- codeHash = true
- break
+ if hash == crypto.Keccak256Hash(acc.code) {
+ continue checkSubtries // skip trie check of code nodes.
}
}
- // If the root is a real trie node, check consistency
- if !codeHash {
- if err := checkStateConsistency(dstDb, root); err != nil {
- t.Fatalf("state inconsistent: %v", err)
- }
+ // Can't use checkStateConsistency here because subtrie keys may have odd
+ // length and crash in LeafKey.
+ if err := checkTrieConsistency(dstDb, hash); err != nil {
+ t.Fatalf("state inconsistent: %v", err)
}
}
// Fetch the next batch to retrieve