Skip to content

Commit 3dd8767

Browse files
elizabethengelmani-norden
authored andcommitted
Statediff for full node (#6)
* Open a trie from the in-memory database * Use a node's LeafKey as an identifier instead of the address It was proving difficult to find look the address up from a given path with a full node (sometimes the value wouldn't exist in the disk db). So, instead, for now we are using the node's LeafKey with is a Keccak256 hash of the address, so if we know the address we can figure out which LeafKey it matches up to. * Make sure that statediff has been processed before pruning * Use blockchain stateCache.OpenTrie for storage diffs * Clean up log lines and remove unnecessary fields from builder * Apply go fmt changes * Add a sleep to the blockchain test * Address PR comments * Address PR comments
1 parent 3c2c96a commit 3dd8767

File tree

14 files changed

+241
-97
lines changed

14 files changed

+241
-97
lines changed

cmd/geth/config.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,14 @@ func makeFullNode(ctx *cli.Context) *node.Node {
157157
if ctx.GlobalIsSet(utils.ConstantinopleOverrideFlag.Name) {
158158
cfg.Eth.ConstantinopleOverride = new(big.Int).SetUint64(ctx.GlobalUint64(utils.ConstantinopleOverrideFlag.Name))
159159
}
160+
160161
utils.RegisterEthService(stack, &cfg.Eth)
161162

163+
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
164+
cfg.Eth.StateDiff = true
165+
utils.RegisterStateDiffService(stack, ctx)
166+
}
167+
162168
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
163169
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
164170
}
@@ -190,9 +196,6 @@ func makeFullNode(ctx *cli.Context) *node.Node {
190196
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
191197
}
192198

193-
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
194-
utils.RegisterStateDiffService(stack, ctx)
195-
}
196199
return stack
197200
}
198201

core/blockchain.go

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ type CacheConfig struct {
9595
TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk
9696
TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node)
9797
TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
98+
ProcessingStateDiffs bool // Whether statediffs processing should be taken into a account before a trie is pruned
9899
}
99100

100101
// BlockChain represents the canonical chain given a database with a genesis
@@ -156,6 +157,8 @@ type BlockChain struct {
156157

157158
badBlocks *lru.Cache // Bad block cache
158159
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
160+
161+
stateDiffsProcessed map[common.Hash]int
159162
}
160163

161164
// NewBlockChain returns a fully initialised block chain using information
@@ -175,23 +178,24 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
175178
blockCache, _ := lru.New(blockCacheLimit)
176179
futureBlocks, _ := lru.New(maxFutureBlocks)
177180
badBlocks, _ := lru.New(badBlockLimit)
178-
181+
stateDiffsProcessed := make(map[common.Hash]int)
179182
bc := &BlockChain{
180-
chainConfig: chainConfig,
181-
cacheConfig: cacheConfig,
182-
db: db,
183-
triegc: prque.New(nil),
184-
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
185-
quit: make(chan struct{}),
186-
shouldPreserve: shouldPreserve,
187-
bodyCache: bodyCache,
188-
bodyRLPCache: bodyRLPCache,
189-
receiptsCache: receiptsCache,
190-
blockCache: blockCache,
191-
futureBlocks: futureBlocks,
192-
engine: engine,
193-
vmConfig: vmConfig,
194-
badBlocks: badBlocks,
183+
chainConfig: chainConfig,
184+
cacheConfig: cacheConfig,
185+
db: db,
186+
triegc: prque.New(nil),
187+
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
188+
quit: make(chan struct{}),
189+
shouldPreserve: shouldPreserve,
190+
bodyCache: bodyCache,
191+
bodyRLPCache: bodyRLPCache,
192+
receiptsCache: receiptsCache,
193+
blockCache: blockCache,
194+
futureBlocks: futureBlocks,
195+
engine: engine,
196+
vmConfig: vmConfig,
197+
badBlocks: badBlocks,
198+
stateDiffsProcessed: stateDiffsProcessed,
195199
}
196200
bc.validator = NewBlockValidator(chainConfig, bc, engine)
197201
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
@@ -932,6 +936,11 @@ func (bc *BlockChain) WriteBlockWithoutState(block *types.Block, td *big.Int) (e
932936
return nil
933937
}
934938

939+
func (bc *BlockChain) AddToStateDiffProcessedCollection(hash common.Hash) {
940+
count := bc.stateDiffsProcessed[hash]
941+
bc.stateDiffsProcessed[hash] = count + 1
942+
}
943+
935944
// WriteBlockWithState writes the block and all associated state to the database.
936945
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) (status WriteStatus, err error) {
937946
bc.chainmu.Lock()
@@ -1016,6 +1025,16 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
10161025
bc.triegc.Push(root, number)
10171026
break
10181027
}
1028+
1029+
if bc.cacheConfig.ProcessingStateDiffs {
1030+
if !bc.allowedRootToBeDereferenced(root.(common.Hash)) {
1031+
bc.triegc.Push(root, number)
1032+
break
1033+
} else {
1034+
delete(bc.stateDiffsProcessed, root.(common.Hash))
1035+
}
1036+
}
1037+
10191038
triedb.Dereference(root.(common.Hash))
10201039
}
10211040
}
@@ -1070,6 +1089,15 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
10701089
return status, nil
10711090
}
10721091

1092+
// since we need the state tries of the current block and its parent in-memory
1093+
// in order to process statediffs, we should avoid dereferencing roots until
1094+
// its statediff and its child have been processed
1095+
func (bc *BlockChain) allowedRootToBeDereferenced(root common.Hash) bool {
1096+
diffProcessedForSelfAndChildCount := 2
1097+
count := bc.stateDiffsProcessed[root]
1098+
return count >= diffProcessedForSelfAndChildCount
1099+
}
1100+
10731101
// addFutureBlock checks if the block is within the max allowed window to get
10741102
// accepted for future processing, and returns an error if the block is too far
10751103
// ahead and was not added.

core/blockchain_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,3 +1810,84 @@ func TestPrunedImportSide(t *testing.T) {
18101810
testSideImport(t, 1, 10)
18111811
testSideImport(t, 1, -10)
18121812
}
1813+
1814+
func TestProcessingStateDiffs(t *testing.T) {
1815+
defaultTrieCleanCache := 256
1816+
defaultTrieDirtyCache := 256
1817+
defaultTrieTimeout := 60 * time.Minute
1818+
cacheConfig := &CacheConfig{
1819+
Disabled: false,
1820+
TrieCleanLimit: defaultTrieCleanCache,
1821+
TrieDirtyLimit: defaultTrieDirtyCache,
1822+
TrieTimeLimit: defaultTrieTimeout,
1823+
ProcessingStateDiffs: true,
1824+
}
1825+
db := ethdb.NewMemDatabase()
1826+
genesis := new(Genesis).MustCommit(db)
1827+
numberOfBlocks := triesInMemory
1828+
engine := ethash.NewFaker()
1829+
blockchain, _ := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil)
1830+
blocks := makeBlockChain(genesis, numberOfBlocks+1, engine, db, canonicalSeed)
1831+
_, err := blockchain.InsertChain(blocks)
1832+
if err != nil {
1833+
t.Fatalf("failed to create pristine chain: %v", err)
1834+
}
1835+
defer blockchain.Stop()
1836+
1837+
//when adding a root hash to the collection, it will increment the count
1838+
firstStateRoot := blocks[0].Root()
1839+
blockchain.AddToStateDiffProcessedCollection(firstStateRoot)
1840+
value, ok := blockchain.stateDiffsProcessed[firstStateRoot]
1841+
if !ok {
1842+
t.Error("state root not found in collection")
1843+
}
1844+
if value != 1 {
1845+
t.Error("state root count not correct", "want", 1, "got", value)
1846+
}
1847+
1848+
blockchain.AddToStateDiffProcessedCollection(firstStateRoot)
1849+
value, ok = blockchain.stateDiffsProcessed[firstStateRoot]
1850+
if !ok {
1851+
t.Error("state root not found in collection")
1852+
}
1853+
if value != 2 {
1854+
t.Error("state root count not correct", "want", 2, "got", value)
1855+
}
1856+
1857+
moreBlocks := makeBlockChain(blocks[len(blocks)-1], 1, engine, db, canonicalSeed)
1858+
_, err = blockchain.InsertChain(moreBlocks)
1859+
1860+
//a root hash can be dereferenced when it's state diff and it's child's state diff have been processed
1861+
//(i.e. it has a count of 2 in stateDiffsProcessed)
1862+
nodes := blockchain.stateCache.TrieDB().Nodes()
1863+
if containsRootHash(nodes, firstStateRoot) {
1864+
t.Errorf("stateRoot %s in nodes, want: %t, got: %t", firstStateRoot.Hex(), false, true)
1865+
}
1866+
1867+
//a root hash should still be in the in-mem db if it's child's state diff hasn't yet been processed
1868+
//(i.e. it has a count of 1 stateDiffsProcessed)
1869+
secondStateRoot := blocks[1].Root()
1870+
blockchain.AddToStateDiffProcessedCollection(secondStateRoot)
1871+
if !containsRootHash(nodes, secondStateRoot) {
1872+
t.Errorf("stateRoot %s in nodes, want: %t, got: %t", secondStateRoot.Hex(), true, false)
1873+
}
1874+
1875+
//the stateDiffsProcessed collection is cleaned up once a hash has been dereferenced
1876+
_, ok = blockchain.stateDiffsProcessed[firstStateRoot]
1877+
if ok {
1878+
t.Errorf("stateRoot %s in stateDiffsProcessed collection, want: %t, got: %t",
1879+
firstStateRoot.Hex(),
1880+
false,
1881+
ok,
1882+
)
1883+
}
1884+
}
1885+
1886+
func containsRootHash(collection []common.Hash, hash common.Hash) bool {
1887+
for _, n := range collection {
1888+
if n == hash {
1889+
return true
1890+
}
1891+
}
1892+
return false
1893+
}

eth/backend.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
171171
TrieDirtyLimit: config.TrieDirtyCache,
172172
TrieDirtyDisabled: config.NoPruning,
173173
TrieTimeLimit: config.TrieTimeout,
174+
ProcessingStateDiffs: config.StateDiff,
174175
}
175176
)
176177
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve)

eth/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ var DefaultConfig = Config{
5959
Blocks: 20,
6060
Percentile: 60,
6161
},
62+
63+
StateDiff: false,
6264
}
6365

6466
func init() {
@@ -154,6 +156,8 @@ type Config struct {
154156

155157
// RPCGasCap is the global gas cap for eth-call variants.
156158
RPCGasCap *big.Int `toml:",omitempty"`
159+
160+
StateDiff bool
157161
}
158162

159163
type configMarshaling struct {

statediff/builder/builder.go

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package builder
2222
import (
2323
"github.com/ethereum/go-ethereum/common"
2424
"github.com/ethereum/go-ethereum/common/hexutil"
25+
"github.com/ethereum/go-ethereum/core"
2526
"github.com/ethereum/go-ethereum/core/state"
2627
"github.com/ethereum/go-ethereum/ethdb"
2728
"github.com/ethereum/go-ethereum/log"
@@ -35,25 +36,27 @@ type Builder interface {
3536

3637
type builder struct {
3738
chainDB ethdb.Database
38-
trieDB *trie.Database
39-
cachedTrie *trie.Trie
39+
blockChain *core.BlockChain
4040
}
4141

42-
func NewBuilder(db ethdb.Database) *builder {
42+
type AccountsMap map[common.Hash]*state.Account
43+
44+
func NewBuilder(db ethdb.Database, blockChain *core.BlockChain) *builder {
4345
return &builder{
44-
chainDB: db,
45-
trieDB: trie.NewDatabase(db),
46+
chainDB: db,
47+
blockChain: blockChain,
4648
}
4749
}
4850

4951
func (sdb *builder) BuildStateDiff(oldStateRoot, newStateRoot common.Hash, blockNumber int64, blockHash common.Hash) (*StateDiff, error) {
5052
// Generate tries for old and new states
51-
oldTrie, err := trie.New(oldStateRoot, sdb.trieDB)
53+
stateCache := sdb.blockChain.StateCache()
54+
oldTrie, err := stateCache.OpenTrie(oldStateRoot)
5255
if err != nil {
5356
log.Error("Error creating trie for oldStateRoot", "error", err)
5457
return nil, err
5558
}
56-
newTrie, err := trie.New(newStateRoot, sdb.trieDB)
59+
newTrie, err := stateCache.OpenTrie(newStateRoot)
5760
if err != nil {
5861
log.Error("Error creating trie for newStateRoot", "error", err)
5962
return nil, err
@@ -108,33 +111,27 @@ func (sdb *builder) BuildStateDiff(oldStateRoot, newStateRoot common.Hash, block
108111
}, nil
109112
}
110113

111-
func (sdb *builder) collectDiffNodes(a, b trie.NodeIterator) (map[common.Address]*state.Account, error) {
112-
var diffAccounts = make(map[common.Address]*state.Account)
114+
func (sdb *builder) collectDiffNodes(a, b trie.NodeIterator) (AccountsMap, error) {
115+
var diffAccounts = make(AccountsMap)
113116
it, _ := trie.NewDifferenceIterator(a, b)
114117

115118
for {
116119
log.Debug("Current Path and Hash", "path", pathToStr(it), "hashold", it.Hash())
117120
if it.Leaf() {
118-
119-
// lookup address
120-
path := make([]byte, len(it.Path())-1)
121-
copy(path, it.Path())
122-
addr, err := sdb.addressByPath(path)
123-
if err != nil {
124-
log.Error("Error looking up address via path", "path", path, "error", err)
125-
return nil, err
126-
}
121+
leafKey := make([]byte, len(it.LeafKey()))
122+
copy(leafKey, it.LeafKey())
123+
leafKeyHash := common.BytesToHash(leafKey)
127124

128125
// lookup account state
129126
var account state.Account
130127
if err := rlp.DecodeBytes(it.LeafBlob(), &account); err != nil {
131-
log.Error("Error looking up account via address", "address", addr, "error", err)
128+
log.Error("Error looking up account via address", "address", leafKeyHash, "error", err)
132129
return nil, err
133130
}
134131

135132
// record account to diffs (creation if we are looking at new - old; deletion if old - new)
136-
log.Debug("Account lookup successful", "address", addr, "account", account)
137-
diffAccounts[*addr] = &account
133+
log.Debug("Account lookup successful", "address", leafKeyHash, "account", account)
134+
diffAccounts[leafKeyHash] = &account
138135
}
139136
cont := it.Next(true)
140137
if !cont {
@@ -145,8 +142,8 @@ func (sdb *builder) collectDiffNodes(a, b trie.NodeIterator) (map[common.Address
145142
return diffAccounts, nil
146143
}
147144

148-
func (sdb *builder) buildDiffEventual(accounts map[common.Address]*state.Account) (map[common.Address]AccountDiff, error) {
149-
accountDiffs := make(map[common.Address]AccountDiff)
145+
func (sdb *builder) buildDiffEventual(accounts AccountsMap) (AccountDiffsMap, error) {
146+
accountDiffs := make(AccountDiffsMap)
150147
for addr, val := range accounts {
151148
sr := val.Root
152149
storageDiffs, err := sdb.buildStorageDiffsEventual(sr)
@@ -172,11 +169,11 @@ func (sdb *builder) buildDiffEventual(accounts map[common.Address]*state.Account
172169
return accountDiffs, nil
173170
}
174171

175-
func (sdb *builder) buildDiffIncremental(creations map[common.Address]*state.Account, deletions map[common.Address]*state.Account, updatedKeys []string) (map[common.Address]AccountDiff, error) {
176-
updatedAccounts := make(map[common.Address]AccountDiff)
172+
func (sdb *builder) buildDiffIncremental(creations AccountsMap, deletions AccountsMap, updatedKeys []string) (AccountDiffsMap, error) {
173+
updatedAccounts := make(AccountDiffsMap)
177174
for _, val := range updatedKeys {
178-
createdAcc := creations[common.HexToAddress(val)]
179-
deletedAcc := deletions[common.HexToAddress(val)]
175+
createdAcc := creations[common.HexToHash(val)]
176+
deletedAcc := deletions[common.HexToHash(val)]
180177
oldSR := deletedAcc.Root
181178
newSR := createdAcc.Root
182179
if storageDiffs, err := sdb.buildStorageDiffsIncremental(oldSR, newSR); err != nil {
@@ -190,23 +187,24 @@ func (sdb *builder) buildDiffIncremental(creations map[common.Address]*state.Acc
190187
nHexRoot := createdAcc.Root.Hex()
191188
contractRoot := DiffString{Value: &nHexRoot}
192189

193-
updatedAccounts[common.HexToAddress(val)] = AccountDiff{
190+
updatedAccounts[common.HexToHash(val)] = AccountDiff{
194191
Nonce: nonce,
195192
Balance: balance,
196193
CodeHash: codeHash,
197194
ContractRoot: contractRoot,
198195
Storage: storageDiffs,
199196
}
200-
delete(creations, common.HexToAddress(val))
201-
delete(deletions, common.HexToAddress(val))
197+
delete(creations, common.HexToHash(val))
198+
delete(deletions, common.HexToHash(val))
202199
}
203200
}
204201
return updatedAccounts, nil
205202
}
206203

207204
func (sdb *builder) buildStorageDiffsEventual(sr common.Hash) (map[string]DiffStorage, error) {
208205
log.Debug("Storage Root For Eventual Diff", "root", sr.Hex())
209-
sTrie, err := trie.New(sr, sdb.trieDB)
206+
stateCache := sdb.blockChain.StateCache()
207+
sTrie, err := stateCache.OpenTrie(sr)
210208
if err != nil {
211209
log.Info("error in build storage diff eventual", "error", err)
212210
return nil, err
@@ -218,11 +216,13 @@ func (sdb *builder) buildStorageDiffsEventual(sr common.Hash) (map[string]DiffSt
218216

219217
func (sdb *builder) buildStorageDiffsIncremental(oldSR common.Hash, newSR common.Hash) (map[string]DiffStorage, error) {
220218
log.Debug("Storage Roots for Incremental Diff", "old", oldSR.Hex(), "new", newSR.Hex())
221-
oldTrie, err := trie.New(oldSR, sdb.trieDB)
219+
stateCache := sdb.blockChain.StateCache()
220+
221+
oldTrie, err := stateCache.OpenTrie(oldSR)
222222
if err != nil {
223223
return nil, err
224224
}
225-
newTrie, err := trie.New(newSR, sdb.trieDB)
225+
newTrie, err := stateCache.OpenTrie(newSR)
226226
if err != nil {
227227
return nil, err
228228
}

0 commit comments

Comments
 (0)