Skip to content

Commit 710435b

Browse files
committed
core, eth, trie: reuse trie journals in all our code
1 parent cd791bd commit 710435b

File tree

9 files changed

+151
-25
lines changed

9 files changed

+151
-25
lines changed

core/blockchain.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,12 @@ func (self *BlockChain) AuxValidator() pow.PoW { return self.pow }
357357

358358
// State returns a new mutable state based on the current HEAD block.
359359
func (self *BlockChain) State() (*state.StateDB, error) {
360-
return state.New(self.CurrentBlock().Root(), self.chainDb)
360+
return self.StateAt(self.CurrentBlock().Root())
361+
}
362+
363+
// StateAt returns a new mutable state based on a particular point in time.
364+
func (self *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
365+
return self.stateCache.New(root)
361366
}
362367

363368
// Reset purges the entire blockchain, restoring it to its genesis state.

core/state/statedb.go

+42-9
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package state
2020
import (
2121
"fmt"
2222
"math/big"
23+
"sync"
2324

2425
"github.com/ethereum/go-ethereum/common"
2526
"github.com/ethereum/go-ethereum/core/vm"
@@ -66,6 +67,8 @@ type StateDB struct {
6667
txIndex int
6768
logs map[common.Hash]vm.Logs
6869
logSize uint
70+
71+
lock sync.Mutex
6972
}
7073

7174
// Create a new state from a given trie
@@ -86,32 +89,53 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
8689
}, nil
8790
}
8891

89-
// Reset clears out all emphemeral state objects from the state db, but keeps
90-
// the underlying state trie to avoid reloading data for the next operations.
91-
func (self *StateDB) Reset(root common.Hash) error {
92+
// New creates a new statedb by reusing any journalled tries to avoid costly
93+
// disk io.
94+
func (self *StateDB) New(root common.Hash) (*StateDB, error) {
95+
self.lock.Lock()
96+
defer self.lock.Unlock()
97+
9298
tr, err := self.openTrie(root)
9399
if err != nil {
94-
return err
100+
return nil, err
95101
}
96-
*self = StateDB{
102+
return &StateDB{
97103
db: self.db,
98104
trie: tr,
99-
pastTries: self.pastTries,
100105
codeSizeCache: self.codeSizeCache,
101106
stateObjects: make(map[common.Address]*StateObject),
102107
stateObjectsDirty: make(map[common.Address]struct{}),
103108
refund: new(big.Int),
104109
logs: make(map[common.Hash]vm.Logs),
110+
}, nil
111+
}
112+
113+
// Reset clears out all emphemeral state objects from the state db, but keeps
114+
// the underlying state trie to avoid reloading data for the next operations.
115+
func (self *StateDB) Reset(root common.Hash) error {
116+
self.lock.Lock()
117+
defer self.lock.Unlock()
118+
119+
tr, err := self.openTrie(root)
120+
if err != nil {
121+
return err
105122
}
123+
self.trie = tr
124+
self.stateObjects = make(map[common.Address]*StateObject)
125+
self.stateObjectsDirty = make(map[common.Address]struct{})
126+
self.refund = new(big.Int)
127+
self.thash = common.Hash{}
128+
self.bhash = common.Hash{}
129+
self.txIndex = 0
130+
self.logs = make(map[common.Hash]vm.Logs)
131+
self.logSize = 0
132+
106133
return nil
107134
}
108135

109136
// openTrie creates a trie. It uses an existing trie if one is available
110137
// from the journal if available.
111138
func (self *StateDB) openTrie(root common.Hash) (*trie.SecureTrie, error) {
112-
if self.trie != nil && self.trie.Hash() == root {
113-
return self.trie, nil
114-
}
115139
for i := len(self.pastTries) - 1; i >= 0; i-- {
116140
if self.pastTries[i].Hash() == root {
117141
tr := *self.pastTries[i]
@@ -122,6 +146,9 @@ func (self *StateDB) openTrie(root common.Hash) (*trie.SecureTrie, error) {
122146
}
123147

124148
func (self *StateDB) pushTrie(t *trie.SecureTrie) {
149+
self.lock.Lock()
150+
defer self.lock.Unlock()
151+
125152
if len(self.pastTries) >= maxJournalLength {
126153
copy(self.pastTries, self.pastTries[1:])
127154
self.pastTries[len(self.pastTries)-1] = t
@@ -381,6 +408,9 @@ func (self *StateDB) CreateAccount(addr common.Address) vm.Account {
381408
//
382409

383410
func (self *StateDB) Copy() *StateDB {
411+
self.lock.Lock()
412+
defer self.lock.Unlock()
413+
384414
// Copy all the basic fields, initialize the memory ones
385415
state := &StateDB{
386416
db: self.db,
@@ -406,6 +436,9 @@ func (self *StateDB) Copy() *StateDB {
406436
}
407437

408438
func (self *StateDB) Set(state *StateDB) {
439+
self.lock.Lock()
440+
defer self.lock.Unlock()
441+
409442
self.db = state.db
410443
self.trie = state.trie
411444
self.pastTries = state.pastTries

eth/api.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ func (api *PublicDebugAPI) DumpBlock(number uint64) (state.Dump, error) {
293293
if block == nil {
294294
return state.Dump{}, fmt.Errorf("block #%d not found", number)
295295
}
296-
stateDb, err := state.New(block.Root(), api.eth.ChainDb())
296+
stateDb, err := api.eth.BlockChain().StateAt(block.Root())
297297
if err != nil {
298298
return state.Dump{}, err
299299
}
@@ -406,7 +406,7 @@ func (api *PrivateDebugAPI) traceBlock(block *types.Block, logConfig *vm.LogConf
406406
if err := core.ValidateHeader(api.config, blockchain.AuxValidator(), block.Header(), blockchain.GetHeader(block.ParentHash(), block.NumberU64()-1), true, false); err != nil {
407407
return false, structLogger.StructLogs(), err
408408
}
409-
statedb, err := state.New(blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1).Root(), api.eth.ChainDb())
409+
statedb, err := blockchain.StateAt(blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1).Root())
410410
if err != nil {
411411
return false, structLogger.StructLogs(), err
412412
}
@@ -501,7 +501,7 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common.
501501
if parent == nil {
502502
return nil, fmt.Errorf("block parent %x not found", block.ParentHash())
503503
}
504-
stateDb, err := state.New(parent.Root(), api.eth.ChainDb())
504+
stateDb, err := api.eth.BlockChain().StateAt(parent.Root())
505505
if err != nil {
506506
return nil, err
507507
}

eth/api_backend.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (b *EthApiBackend) StateAndHeaderByNumber(blockNr rpc.BlockNumber) (ethapi.
8181
if header == nil {
8282
return nil, nil, nil
8383
}
84-
stateDb, err := state.New(header.Root, b.eth.chainDb)
84+
stateDb, err := b.eth.BlockChain().StateAt(header.Root)
8585
return EthApiState{stateDb}, header, err
8686
}
8787

ethdb/database.go

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/ethereum/go-ethereum/metrics"
2929
"github.com/syndtr/goleveldb/leveldb"
3030
"github.com/syndtr/goleveldb/leveldb/errors"
31+
"github.com/syndtr/goleveldb/leveldb/filter"
3132
"github.com/syndtr/goleveldb/leveldb/iterator"
3233
"github.com/syndtr/goleveldb/leveldb/opt"
3334

@@ -84,6 +85,7 @@ func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) {
8485
OpenFilesCacheCapacity: handles,
8586
BlockCacheCapacity: cache / 2 * opt.MiB,
8687
WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally
88+
Filter: filter.NewBloomFilter(10),
8789
})
8890
if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
8991
db, err = leveldb.RecoverFile(file, nil)

internal/ethapi/api.go

+2
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,8 @@ type CallArgs struct {
454454
}
455455

456456
func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (string, *big.Int, error) {
457+
defer func(start time.Time) { glog.V(logger.Debug).Infof("call took %v", time.Since(start)) }(time.Now())
458+
457459
state, header, err := s.b.StateAndHeaderByNumber(blockNr)
458460
if state == nil || err != nil {
459461
return "0x", common.Big0, err

miner/worker.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ func (self *worker) push(work *Work) {
361361

362362
// makeCurrent creates a new environment for the current cycle.
363363
func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error {
364-
state, err := state.New(parent.Root(), self.eth.ChainDb())
364+
state, err := self.chain.StateAt(parent.Root())
365365
if err != nil {
366366
return err
367367
}

trie/secure_trie.go

+23-10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424

2525
var secureKeyPrefix = []byte("secure-key-")
2626

27+
const secureKeyLength = 11 + 32 // Length of the above prefix + 32byte hash
28+
2729
// SecureTrie wraps a trie with key hashing. In a secure trie, all
2830
// access operations hash the key using keccak256. This prevents
2931
// calling code from creating long chains of nodes that
@@ -35,10 +37,11 @@ var secureKeyPrefix = []byte("secure-key-")
3537
//
3638
// SecureTrie is not safe for concurrent use.
3739
type SecureTrie struct {
38-
trie Trie
39-
hashKeyBuf []byte
40-
secKeyBuf [200]byte
41-
secKeyCache map[string][]byte
40+
trie Trie
41+
hashKeyBuf [secureKeyLength]byte
42+
secKeyBuf [200]byte
43+
secKeyCache map[string][]byte
44+
secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch
4245
}
4346

4447
// NewSecure creates a trie with an existing root node from db.
@@ -56,8 +59,7 @@ func NewSecure(root common.Hash, db Database) (*SecureTrie, error) {
5659
return nil, err
5760
}
5861
return &SecureTrie{
59-
trie: *trie,
60-
secKeyCache: make(map[string][]byte),
62+
trie: *trie,
6163
}, nil
6264
}
6365

@@ -104,7 +106,7 @@ func (t *SecureTrie) TryUpdate(key, value []byte) error {
104106
if err != nil {
105107
return err
106108
}
107-
t.secKeyCache[string(hk)] = common.CopyBytes(key)
109+
t.getSecKeyCache()[string(hk)] = common.CopyBytes(key)
108110
return nil
109111
}
110112

@@ -119,14 +121,14 @@ func (t *SecureTrie) Delete(key []byte) {
119121
// If a node was not found in the database, a MissingNodeError is returned.
120122
func (t *SecureTrie) TryDelete(key []byte) error {
121123
hk := t.hashKey(key)
122-
delete(t.secKeyCache, string(hk))
124+
delete(t.getSecKeyCache(), string(hk))
123125
return t.trie.TryDelete(hk)
124126
}
125127

126128
// GetKey returns the sha3 preimage of a hashed key that was
127129
// previously used to store a value.
128130
func (t *SecureTrie) GetKey(shaKey []byte) []byte {
129-
if key, ok := t.secKeyCache[string(shaKey)]; ok {
131+
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
130132
return key
131133
}
132134
key, _ := t.trie.db.Get(t.secKey(shaKey))
@@ -165,7 +167,7 @@ func (t *SecureTrie) NodeIterator() *NodeIterator {
165167
// the trie's database. Calling code must ensure that the changes made to db are
166168
// written back to the trie's attached database before using the trie.
167169
func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) {
168-
if len(t.secKeyCache) > 0 {
170+
if len(t.getSecKeyCache()) > 0 {
169171
for hk, key := range t.secKeyCache {
170172
if err := db.Put(t.secKey([]byte(hk)), key); err != nil {
171173
return common.Hash{}, err
@@ -196,3 +198,14 @@ func (t *SecureTrie) hashKey(key []byte) []byte {
196198
returnHasherToPool(h)
197199
return buf
198200
}
201+
202+
// getSecKeyCache returns the current secure key cache, creating a new one if
203+
// ownership changed (i.e. the current secure trie is a copy of another owning
204+
// the actual cache).
205+
func (t *SecureTrie) getSecKeyCache() map[string][]byte {
206+
if t != t.secKeyCacheOwner {
207+
t.secKeyCacheOwner = t
208+
t.secKeyCache = make(map[string][]byte)
209+
}
210+
return t.secKeyCache
211+
}

trie/secure_trie_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package trie
1818

1919
import (
2020
"bytes"
21+
"runtime"
22+
"sync"
2123
"testing"
2224

2325
"github.com/ethereum/go-ethereum/common"
@@ -31,6 +33,37 @@ func newEmptySecure() *SecureTrie {
3133
return trie
3234
}
3335

36+
// makeTestSecureTrie creates a large enough secure trie for testing.
37+
func makeTestSecureTrie() (ethdb.Database, *SecureTrie, map[string][]byte) {
38+
// Create an empty trie
39+
db, _ := ethdb.NewMemDatabase()
40+
trie, _ := NewSecure(common.Hash{}, db)
41+
42+
// Fill it with some arbitrary data
43+
content := make(map[string][]byte)
44+
for i := byte(0); i < 255; i++ {
45+
// Map the same data under multiple keys
46+
key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i}
47+
content[string(key)] = val
48+
trie.Update(key, val)
49+
50+
key, val = common.LeftPadBytes([]byte{2, i}, 32), []byte{i}
51+
content[string(key)] = val
52+
trie.Update(key, val)
53+
54+
// Add some other data to inflate th trie
55+
for j := byte(3); j < 13; j++ {
56+
key, val = common.LeftPadBytes([]byte{j, i}, 32), []byte{j, i}
57+
content[string(key)] = val
58+
trie.Update(key, val)
59+
}
60+
}
61+
trie.Commit()
62+
63+
// Return the generated trie
64+
return db, trie, content
65+
}
66+
3467
func TestSecureDelete(t *testing.T) {
3568
trie := newEmptySecure()
3669
vals := []struct{ k, v string }{
@@ -72,3 +105,41 @@ func TestSecureGetKey(t *testing.T) {
72105
t.Errorf("GetKey returned %q, want %q", k, key)
73106
}
74107
}
108+
109+
func TestSecureTrieConcurrency(t *testing.T) {
110+
// Create an initial trie and copy if for concurrent access
111+
_, trie, _ := makeTestSecureTrie()
112+
113+
threads := runtime.NumCPU()
114+
tries := make([]*SecureTrie, threads)
115+
for i := 0; i < threads; i++ {
116+
cpy := *trie
117+
tries[i] = &cpy
118+
}
119+
// Start a batch of goroutines interactng with the trie
120+
pend := new(sync.WaitGroup)
121+
pend.Add(threads)
122+
for i := 0; i < threads; i++ {
123+
go func(index int) {
124+
defer pend.Done()
125+
126+
for j := byte(0); j < 255; j++ {
127+
// Map the same data under multiple keys
128+
key, val := common.LeftPadBytes([]byte{byte(index), 1, j}, 32), []byte{j}
129+
tries[index].Update(key, val)
130+
131+
key, val = common.LeftPadBytes([]byte{byte(index), 2, j}, 32), []byte{j}
132+
tries[index].Update(key, val)
133+
134+
// Add some other data to inflate the trie
135+
for k := byte(3); k < 13; k++ {
136+
key, val = common.LeftPadBytes([]byte{byte(index), k, j}, 32), []byte{k, j}
137+
tries[index].Update(key, val)
138+
}
139+
}
140+
tries[index].Commit()
141+
}(i)
142+
}
143+
// Wait for all threads to finish
144+
pend.Wait()
145+
}

0 commit comments

Comments
 (0)