Skip to content

Commit c1e8003

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 a59f4c5 commit c1e8003

File tree

14 files changed

+242
-99
lines changed

14 files changed

+242
-99
lines changed

cmd/geth/config.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ func makeFullNode(ctx *cli.Context) *node.Node {
152152
stack, cfg := makeConfigNode(ctx)
153153
utils.RegisterEthService(stack, &cfg.Eth)
154154

155+
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
156+
cfg.Eth.StateDiff = true
157+
utils.RegisterStateDiffService(stack, ctx)
158+
}
159+
155160
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
156161
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
157162
}
@@ -179,9 +184,6 @@ func makeFullNode(ctx *cli.Context) *node.Node {
179184
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
180185
}
181186

182-
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
183-
utils.RegisterStateDiffService(stack, ctx)
184-
}
185187
return stack
186188
}
187189

core/blockchain.go

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

116117
// BlockChain represents the canonical chain given a database with a genesis
@@ -172,6 +173,8 @@ type BlockChain struct {
172173
badBlocks *lru.Cache // Bad block cache
173174
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
174175
terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion.
176+
177+
stateDiffsProcessed map[common.Hash]int
175178
}
176179

177180
// NewBlockChain returns a fully initialised block chain using information
@@ -191,23 +194,24 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
191194
blockCache, _ := lru.New(blockCacheLimit)
192195
futureBlocks, _ := lru.New(maxFutureBlocks)
193196
badBlocks, _ := lru.New(badBlockLimit)
194-
197+
stateDiffsProcessed := make(map[common.Hash]int)
195198
bc := &BlockChain{
196-
chainConfig: chainConfig,
197-
cacheConfig: cacheConfig,
198-
db: db,
199-
triegc: prque.New(nil),
200-
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
201-
quit: make(chan struct{}),
202-
shouldPreserve: shouldPreserve,
203-
bodyCache: bodyCache,
204-
bodyRLPCache: bodyRLPCache,
205-
receiptsCache: receiptsCache,
206-
blockCache: blockCache,
207-
futureBlocks: futureBlocks,
208-
engine: engine,
209-
vmConfig: vmConfig,
210-
badBlocks: badBlocks,
199+
chainConfig: chainConfig,
200+
cacheConfig: cacheConfig,
201+
db: db,
202+
triegc: prque.New(nil),
203+
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
204+
quit: make(chan struct{}),
205+
shouldPreserve: shouldPreserve,
206+
bodyCache: bodyCache,
207+
bodyRLPCache: bodyRLPCache,
208+
receiptsCache: receiptsCache,
209+
blockCache: blockCache,
210+
futureBlocks: futureBlocks,
211+
engine: engine,
212+
vmConfig: vmConfig,
213+
badBlocks: badBlocks,
214+
stateDiffsProcessed: stateDiffsProcessed,
211215
}
212216
bc.validator = NewBlockValidator(chainConfig, bc, engine)
213217
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
@@ -1243,6 +1247,11 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
12431247
return nil
12441248
}
12451249

1250+
func (bc *BlockChain) AddToStateDiffProcessedCollection(hash common.Hash) {
1251+
count := bc.stateDiffsProcessed[hash]
1252+
bc.stateDiffsProcessed[hash] = count + 1
1253+
}
1254+
12461255
// WriteBlockWithState writes the block and all associated state to the database.
12471256
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) (status WriteStatus, err error) {
12481257
bc.chainmu.Lock()
@@ -1327,6 +1336,16 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
13271336
bc.triegc.Push(root, number)
13281337
break
13291338
}
1339+
1340+
if bc.cacheConfig.ProcessingStateDiffs {
1341+
if !bc.allowedRootToBeDereferenced(root.(common.Hash)) {
1342+
bc.triegc.Push(root, number)
1343+
break
1344+
} else {
1345+
delete(bc.stateDiffsProcessed, root.(common.Hash))
1346+
}
1347+
}
1348+
13301349
triedb.Dereference(root.(common.Hash))
13311350
}
13321351
}
@@ -1381,6 +1400,15 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
13811400
return status, nil
13821401
}
13831402

1403+
// since we need the state tries of the current block and its parent in-memory
1404+
// in order to process statediffs, we should avoid dereferencing roots until
1405+
// its statediff and its child have been processed
1406+
func (bc *BlockChain) allowedRootToBeDereferenced(root common.Hash) bool {
1407+
diffProcessedForSelfAndChildCount := 2
1408+
count := bc.stateDiffsProcessed[root]
1409+
return count >= diffProcessedForSelfAndChildCount
1410+
}
1411+
13841412
// addFutureBlock checks if the block is within the max allowed window to get
13851413
// accepted for future processing, and returns an error if the block is too far
13861414
// ahead and was not added.

core/blockchain_test.go

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2227,8 +2227,8 @@ func BenchmarkBlockChain_1x1000ValueTransferToExisting(b *testing.B) {
22272227

22282228
func BenchmarkBlockChain_1x1000Executions(b *testing.B) {
22292229
var (
2230-
numTxs = 1000
2231-
numBlocks = 1
2230+
numTxs= 1000
2231+
numBlocks= 1
22322232
)
22332233
b.StopTimer()
22342234
b.ResetTimer()
@@ -2241,3 +2241,84 @@ func BenchmarkBlockChain_1x1000Executions(b *testing.B) {
22412241
}
22422242
benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn)
22432243
}
2244+
2245+
func TestProcessingStateDiffs(t *testing.T) {
2246+
defaultTrieCleanCache := 256
2247+
defaultTrieDirtyCache := 256
2248+
defaultTrieTimeout := 60 * time.Minute
2249+
cacheConfig := &CacheConfig{
2250+
TrieDirtyDisabled: false,
2251+
TrieCleanLimit: defaultTrieCleanCache,
2252+
TrieDirtyLimit: defaultTrieDirtyCache,
2253+
TrieTimeLimit: defaultTrieTimeout,
2254+
ProcessingStateDiffs: true,
2255+
}
2256+
db := rawdb.NewMemoryDatabase()
2257+
genesis := new(Genesis).MustCommit(db)
2258+
numberOfBlocks := TriesInMemory
2259+
engine := ethash.NewFaker()
2260+
blockchain, _ := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil)
2261+
blocks := makeBlockChain(genesis, numberOfBlocks+1, engine, db, canonicalSeed)
2262+
_, err := blockchain.InsertChain(blocks)
2263+
if err != nil {
2264+
t.Fatalf("failed to create pristine chain: %v", err)
2265+
}
2266+
defer blockchain.Stop()
2267+
2268+
//when adding a root hash to the collection, it will increment the count
2269+
firstStateRoot := blocks[0].Root()
2270+
blockchain.AddToStateDiffProcessedCollection(firstStateRoot)
2271+
value, ok := blockchain.stateDiffsProcessed[firstStateRoot]
2272+
if !ok {
2273+
t.Error("state root not found in collection")
2274+
}
2275+
if value != 1 {
2276+
t.Error("state root count not correct", "want", 1, "got", value)
2277+
}
2278+
2279+
blockchain.AddToStateDiffProcessedCollection(firstStateRoot)
2280+
value, ok = blockchain.stateDiffsProcessed[firstStateRoot]
2281+
if !ok {
2282+
t.Error("state root not found in collection")
2283+
}
2284+
if value != 2 {
2285+
t.Error("state root count not correct", "want", 2, "got", value)
2286+
}
2287+
2288+
moreBlocks := makeBlockChain(blocks[len(blocks)-1], 1, engine, db, canonicalSeed)
2289+
_, err = blockchain.InsertChain(moreBlocks)
2290+
2291+
//a root hash can be dereferenced when it's state diff and it's child's state diff have been processed
2292+
//(i.e. it has a count of 2 in stateDiffsProcessed)
2293+
nodes := blockchain.stateCache.TrieDB().Nodes()
2294+
if containsRootHash(nodes, firstStateRoot) {
2295+
t.Errorf("stateRoot %s in nodes, want: %t, got: %t", firstStateRoot.Hex(), false, true)
2296+
}
2297+
2298+
//a root hash should still be in the in-mem db if it's child's state diff hasn't yet been processed
2299+
//(i.e. it has a count of 1 stateDiffsProcessed)
2300+
secondStateRoot := blocks[1].Root()
2301+
blockchain.AddToStateDiffProcessedCollection(secondStateRoot)
2302+
if !containsRootHash(nodes, secondStateRoot) {
2303+
t.Errorf("stateRoot %s in nodes, want: %t, got: %t", secondStateRoot.Hex(), true, false)
2304+
}
2305+
2306+
//the stateDiffsProcessed collection is cleaned up once a hash has been dereferenced
2307+
_, ok = blockchain.stateDiffsProcessed[firstStateRoot]
2308+
if ok {
2309+
t.Errorf("stateRoot %s in stateDiffsProcessed collection, want: %t, got: %t",
2310+
firstStateRoot.Hex(),
2311+
false,
2312+
ok,
2313+
)
2314+
}
2315+
}
2316+
2317+
func containsRootHash(collection []common.Hash, hash common.Hash) bool {
2318+
for _, n := range collection {
2319+
if n == hash {
2320+
return true
2321+
}
2322+
}
2323+
return false
2324+
}

eth/backend.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
184184
TrieDirtyLimit: config.TrieDirtyCache,
185185
TrieDirtyDisabled: config.NoPruning,
186186
TrieTimeLimit: config.TrieTimeout,
187+
ProcessingStateDiffs: config.StateDiff,
187188
}
188189
)
189190
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
@@ -61,6 +61,8 @@ var DefaultConfig = Config{
6161
Blocks: 20,
6262
Percentile: 60,
6363
},
64+
65+
StateDiff: false,
6466
}
6567

6668
func init() {
@@ -149,6 +151,8 @@ type Config struct {
149151
// RPCGasCap is the global gas cap for eth-call variants.
150152
RPCGasCap *big.Int `toml:",omitempty"`
151153

154+
StateDiff bool
155+
152156
// Checkpoint is a hardcoded checkpoint which can be nil.
153157
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
154158

0 commit comments

Comments
 (0)