Skip to content

Commit 6e4725a

Browse files
elizabethengelmani-norden
authored andcommitted
Statediffing geth
* Write state diff to CSV (#2) * port statediff from https://github.com/jpmorganchase/quorum/blob/9b7fd9af8082795eeeb6863d9746f12b82dd5078/statediff/statediff.go; minor fixes * integrating state diff extracting, building, and persisting into geth processes * work towards persisting created statediffs in ipfs; based off github.com/vulcanize/eth-block-extractor * Add a state diff service * Remove diff extractor from blockchain * Update imports * Move statediff on/off check to geth cmd config * Update starting state diff service * Add debugging logs for creating diff * Add statediff extractor and builder tests and small refactoring * Start to write statediff to a CSV * Restructure statediff directory * Pull CSV publishing methods into their own file * Reformatting due to go fmt * Add gomega to vendor dir * Remove testing focuses * Update statediff tests to use golang test pkg instead of ginkgo - builder_test - extractor_test - publisher_test * Use hexutil.Encode instead of deprecated common.ToHex * Remove OldValue from DiffBigInt and DiffUint64 fields * Update builder test * Remove old storage value from updated accounts * Remove old values from created/deleted accounts * Update publisher to account for only storing current account values * Update service loop and fetching previous block * Update testing - remove statediff ginkgo test suite file - move mocks to their own dir * Updates per go fmt * Updates to tests * Pass statediff mode and path in through cli * Return filename from publisher * Remove some duplication in builder * Remove code field from state diff output this is the contract byte code, and it can still be obtained by querying the db by the codeHash * Consolidate acct diff structs for updated & updated/deleted accts * Include block number in csv filename * Clean up error logging * Cleanup formatting, spelling, etc * Address PR comments * Add contract address and storage value to csv * Refactor accumulating account row in csv publisher * Add DiffStorage struct * Add storage key to csv * Address PR comments * Fix publisher to include rows for accounts that don't have store updates * Update builder test after merging in release/1.8 * Update test contract to include storage on contract intialization - so that we're able to test that storage diffing works for created and deleted accounts (not just updated accounts). * Factor out a common trie iterator method in builder * Apply goimports to statediff * Apply gosimple changes to statediff * Gracefully exit geth command(#4) * 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 * refactoring/reorganizing packages * refactoring statediff builder and types and adjusted to relay proofs and paths (still need to make this optional) * refactoring state diff service and adding api which allows for streaming state diff payloads over an rpc websocket subscription * make proofs and paths optional + compress service loop into single for loop (may be missing something here) * option to process intermediate nodes * make state diff rlp serializable * cli parameter to limit statediffing to select account addresses + test * review fixes and fixes for issues ran into in integration * review fixes; proper method signature for api; adjust service so that statediff processing is halted/paused until there is at least one subscriber listening for the results * adjust buffering to improve stability; doc.go; fix notifier err handling * relay receipts with the rest of the data + review fixes/changes * rpc method to get statediff at specific block; requires archival node or the block be within the pruning range * review fixes * fixes after rebase * statediff verison meta * fix linter issues * include total difficulty to the payload * fix state diff builder: emit actual leaf nodes instead of value nodes; diff on the leaf not on the value; emit correct path for intermediate nodes * adjust statediff builder tests to changes and extend to test intermediate nodes; golint * add genesis block to test; handle block 0 in StateDiffAt * rlp files for mainnet blocks 0-3, for tests * builder test on mainnet blocks * common.BytesToHash(path) => crypto.Keaccak256(hash) in builder; BytesToHash produces same hash for e.g. []byte{} and []byte{\x00} - prefix \x00 steps are inconsequential to the hash result * complete tests for early mainnet blocks * diff type for representing deleted accounts * fix builder so that we handle account deletions properly and properly diff storage when an account is moved to a new path; update params * remove cli params; moving them to subscriber defined * remove unneeded bc methods * update service and api; statediffing params are now defined by user through api rather than by service provider by cli * update top level tests * add ability to watch specific storage slots (leaf keys) only * comments; explain logic * update mainnet blocks test * update api_test.go * storage leafkey filter test * cleanup chain maker * adjust chain maker for tests to add an empty account in block1 and switch to EIP-158 afterwards (now we just need to generate enough accounts until one causes the empty account to be touched and removed post-EIP-158 so we can simulate and test that process...); also added 2 new blocks where more contract storage is set and old slots are set to zero so they are removed so we can test that * found an account whose creation causes the empty account to be moved to a new path; this should count as 'touching; the empty account and cause it to be removed according to eip-158... but it doesn't * use new contract in unit tests that has self-destruct ability, so we can test eip-158 since simply moving an account to new path doesn't count as 'touchin' it * handle storage deletions * tests for eip-158 account removal and storage value deletions; there is one edge case left to test where we remove 1 account when only two exist such that the remaining account is moved up and replaces the root branch node * finish testing known edge cases * add endpoint to fetch all state and storage nodes at a given blockheight; useful for generating a recent atate cache/snapshot that we can diff forward from rather than needing to collect all diffs from genesis * test for state trie builder * minor changes/fixes * update version meta * if statediffing is on, lock tries in triedb until the statediffing service signals they are done using them * update version meta * fix mock blockchain; golint; bump patch * increase maxRequestContentLength; bump patch * log the sizes of the state objects we are sending * CI build (#20) * CI: run build on PR and on push to master * CI: debug building geth * CI: fix coping file * CI: fix coping file v2 * CI: temporary upload file to release asset * CI: get release upload_url by tag, upload asset to current relase * CI: fix tag name * fix ci build on statediff_at_anyblock-1.9.11 branch * fix publishing assets in release * bump version meta * use context deadline for timeout in eth_call * collect and emit codehash=>code mappings for state objects * subscription endpoint for retrieving all the codehash=>code mappings that exist at provided height * bump version meta * Implement WriteStateDiffAt * Writes state diffs directly to postgres * Adds CLI flags to configure PG * Refactors builder output with callbacks * Copies refactored postgres handling code from ipld-eth-indexer * rename PostgresCIDWriter.{index->upsert}* * less ambiguous * go.mod update * rm unused * cleanup * output code & codehash iteratively * had to rf some types for this * prometheus metrics output * duplicate recent eth-indexer changes * migrations and metrics... * [wip] prom.Init() here? another CLI flag? * cleanup * tidy & DRY * statediff WriteLoop service + CLI flag * [wip] update test mocks * todo - do something meaningful to test write loop * logging * use geth log * port tests to go testing * drop ginkgo/gomega * fix and cleanup tests * fail before defer statement * delete vendor/ dir * unused * bump version meta * fixes after rebase onto 1.9.23 * bump version meta * fix API registration * bump version meta * use golang 1.15.5 version (#34) * bump version meta; add 0.0.11 branch to actions * bump version meta; update github actions workflows * statediff: refactor metrics * Remove redundant statediff/indexer/prom tooling and use existing prometheus integration. * cleanup * "indexer" namespace for metrics * add reporting loop for db metrics * doc * metrics for statediff stats * metrics namespace/subsystem = statediff/{indexer,service} * statediff: use a worker pool (for direct writes) * fix test * fix chain event subscription * log tweaks * func name * unused import * intermediate chain event channel for metrics * cleanup * bump version meta * update github actions; linting * add poststate and status to receipt ipld indexes * bump statediff version * stateDiffFor endpoints for fetching or writing statediff object by blockhash; bump statediff version
1 parent c2d2f4e commit 6e4725a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+10934
-28
lines changed

.github/workflows/build.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: Docker Build
2+
3+
on: [pull_request]
4+
5+
jobs:
6+
build:
7+
name: Run docker build
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v2
11+
- name: Run docker build
12+
run: docker build -t vulcanize/go-ethereum -f Dockerfile.amd64 .

.github/workflows/on-master.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Docker Build and publish to Github
2+
3+
on:
4+
push:
5+
branches:
6+
- v1.9.25-statediff
7+
- v1.9.24-statediff
8+
- v1.9.23-statediff
9+
- v1.9.11-statediff
10+
11+
jobs:
12+
build:
13+
name: Run docker build and publish
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v2
17+
- name: Run docker build
18+
run: docker build -t vulcanize/go-ethereum -f Dockerfile.amd64 .
19+
- name: Get the version
20+
id: vars
21+
run: echo ::set-output name=sha::$(echo ${GITHUB_SHA:0:7})
22+
- name: Tag docker image
23+
run: docker tag vulcanize/go-ethereum docker.pkg.github.com/vulcanize/go-ethereum/go-ethereum:${{steps.vars.outputs.sha}}
24+
- name: Docker Login
25+
run: echo ${{ secrets.GITHUB_TOKEN }} | docker login https://docker.pkg.github.com -u vulcanize --password-stdin
26+
- name: Docker Push
27+
run: docker push docker.pkg.github.com/vulcanize/go-ethereum/go-ethereum:${{steps.vars.outputs.sha}}
28+

.github/workflows/publish.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Publish geth to release
2+
on:
3+
release:
4+
types: [published]
5+
jobs:
6+
push_to_registries:
7+
name: Publish assets to Release
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Get the version
11+
id: vars
12+
run: |
13+
echo ::set-output name=sha::$(echo ${GITHUB_SHA:0:7})
14+
- name: Docker Login to Github Registry
15+
run: echo ${{ secrets.GITHUB_TOKEN }} | docker login https://docker.pkg.github.com -u vulcanize --password-stdin
16+
- name: Docker Pull
17+
run: docker pull docker.pkg.github.com/vulcanize/go-ethereum/go-ethereum:${{steps.vars.outputs.sha}}
18+
- name: Copy ethereum binary file
19+
run: docker run --rm --entrypoint cat docker.pkg.github.com/vulcanize/go-ethereum/go-ethereum:${{steps.vars.outputs.sha}} /go-ethereum/build/bin/geth > geth-linux-amd64
20+
- name: Get release
21+
id: get_release
22+
uses: bruceadams/[email protected]
23+
env:
24+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25+
- name: Upload Release Asset
26+
id: upload-release-asset
27+
uses: actions/upload-release-asset@v1
28+
env:
29+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30+
with:
31+
upload_url: ${{ steps.get_release.outputs.upload_url }}
32+
asset_path: geth-linux-amd64
33+
asset_name: geth-linux-amd64
34+
asset_content_type: application/octet-stream

Dockerfile.amd64

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Build Geth in a stock Go builder container
2+
FROM golang:1.15.5 as builder
3+
4+
#RUN apk add --no-cache make gcc musl-dev linux-headers git
5+
6+
ADD . /go-ethereum
7+
RUN cd /go-ethereum && make geth

cmd/geth/config.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
"reflect"
2626
"unicode"
2727

28+
"github.com/ethereum/go-ethereum/eth/downloader"
29+
"github.com/ethereum/go-ethereum/statediff"
2830
"gopkg.in/urfave/cli.v1"
2931

3032
"github.com/ethereum/go-ethereum/cmd/utils"
@@ -133,6 +135,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
133135
cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name)
134136
}
135137
applyMetricConfig(ctx, &cfg)
138+
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
139+
cfg.Eth.Diffing = true
140+
}
136141

137142
return stack, cfg
138143
}
@@ -143,17 +148,64 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
143148
if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) {
144149
cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name))
145150
}
151+
152+
if cfg.Eth.SyncMode == downloader.LightSync {
153+
return makeLightNode(ctx, stack, cfg)
154+
}
155+
146156
backend := utils.RegisterEthService(stack, &cfg.Eth)
147157

158+
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
159+
var dbParams *statediff.DBParams
160+
if ctx.GlobalIsSet(utils.StateDiffDBFlag.Name) {
161+
dbParams = new(statediff.DBParams)
162+
dbParams.ConnectionURL = ctx.GlobalString(utils.StateDiffDBFlag.Name)
163+
if ctx.GlobalIsSet(utils.StateDiffDBNodeIDFlag.Name) {
164+
dbParams.ID = ctx.GlobalString(utils.StateDiffDBNodeIDFlag.Name)
165+
} else {
166+
utils.Fatalf("Must specify node ID for statediff DB output")
167+
}
168+
if ctx.GlobalIsSet(utils.StateDiffDBClientNameFlag.Name) {
169+
dbParams.ClientName = ctx.GlobalString(utils.StateDiffDBClientNameFlag.Name)
170+
} else {
171+
utils.Fatalf("Must specify client name for statediff DB output")
172+
}
173+
} else {
174+
if ctx.GlobalBool(utils.StateDiffWritingFlag.Name) {
175+
utils.Fatalf("Must pass DB parameters if enabling statediff write loop")
176+
}
177+
}
178+
params := statediff.ServiceParams{
179+
DBParams: dbParams,
180+
EnableWriteLoop: ctx.GlobalBool(utils.StateDiffWritingFlag.Name),
181+
NumWorkers: ctx.GlobalUint(utils.StateDiffWorkersFlag.Name),
182+
}
183+
utils.RegisterStateDiffService(stack, backend, params)
184+
}
185+
186+
// Configure GraphQL if requested
187+
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
188+
utils.RegisterGraphQLService(stack, backend.APIBackend, cfg.Node)
189+
}
190+
// Add the Ethereum Stats daemon if requested.
191+
if cfg.Ethstats.URL != "" {
192+
utils.RegisterEthStatsService(stack, backend.APIBackend, cfg.Ethstats.URL)
193+
}
194+
return stack, backend.APIBackend
195+
}
196+
197+
func makeLightNode(ctx *cli.Context, stack *node.Node, cfg gethConfig) (*node.Node, ethapi.Backend) {
198+
backend := utils.RegisterLesEthService(stack, &cfg.Eth)
199+
148200
// Configure GraphQL if requested
149201
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
150-
utils.RegisterGraphQLService(stack, backend, cfg.Node)
202+
utils.RegisterGraphQLService(stack, backend.ApiBackend, cfg.Node)
151203
}
152204
// Add the Ethereum Stats daemon if requested.
153205
if cfg.Ethstats.URL != "" {
154-
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
206+
utils.RegisterEthStatsService(stack, backend.ApiBackend, cfg.Ethstats.URL)
155207
}
156-
return stack, backend
208+
return stack, backend.ApiBackend
157209
}
158210

159211
// dumpConfig is the dumpconfig command.

cmd/geth/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ var (
152152
utils.GpoMaxGasPriceFlag,
153153
utils.EWASMInterpreterFlag,
154154
utils.EVMInterpreterFlag,
155+
utils.StateDiffFlag,
156+
utils.StateDiffDBFlag,
157+
utils.StateDiffDBNodeIDFlag,
158+
utils.StateDiffDBClientNameFlag,
159+
utils.StateDiffWritingFlag,
160+
utils.StateDiffWorkersFlag,
155161
configFileFlag,
156162
}
157163

cmd/geth/usage.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,17 @@ var AppHelpFlagGroups = []flags.FlagGroup{
228228
utils.LegacyRPCApiFlag,
229229
},
230230
},
231+
{
232+
Name: "STATE DIFF",
233+
Flags: []cli.Flag{
234+
utils.StateDiffFlag,
235+
utils.StateDiffDBFlag,
236+
utils.StateDiffDBNodeIDFlag,
237+
utils.StateDiffDBClientNameFlag,
238+
utils.StateDiffWritingFlag,
239+
utils.StateDiffWorkersFlag,
240+
},
241+
},
231242
{
232243
Name: "MISC",
233244
Flags: []cli.Flag{

cmd/utils/flags.go

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ import (
6464
"github.com/ethereum/go-ethereum/p2p/nat"
6565
"github.com/ethereum/go-ethereum/p2p/netutil"
6666
"github.com/ethereum/go-ethereum/params"
67+
"github.com/ethereum/go-ethereum/statediff"
68+
6769
pcsclite "github.com/gballet/go-libpcsclite"
6870
"gopkg.in/urfave/cli.v1"
6971
)
@@ -748,6 +750,31 @@ var (
748750
Usage: "External EVM configuration (default = built-in interpreter)",
749751
Value: "",
750752
}
753+
754+
StateDiffFlag = cli.BoolFlag{
755+
Name: "statediff",
756+
Usage: "Enables the processing of state diffs between each block",
757+
}
758+
StateDiffDBFlag = cli.StringFlag{
759+
Name: "statediff.db",
760+
Usage: "PostgreSQL database connection string for writing state diffs",
761+
}
762+
StateDiffDBNodeIDFlag = cli.StringFlag{
763+
Name: "statediff.dbnodeid",
764+
Usage: "Node ID to use when writing state diffs to database",
765+
}
766+
StateDiffDBClientNameFlag = cli.StringFlag{
767+
Name: "statediff.dbclientname",
768+
Usage: "Client name to use when writing state diffs to database",
769+
}
770+
StateDiffWritingFlag = cli.BoolFlag{
771+
Name: "statediff.writing",
772+
Usage: "Activates progressive writing of state diffs to database as new block are synced",
773+
}
774+
StateDiffWorkersFlag = cli.UintFlag{
775+
Name: "statediff.workers",
776+
Usage: "Number of concurrent workers to use during statediff processing (0 = 1)",
777+
}
751778
)
752779

753780
// MakeDataDir retrieves the currently requested data directory, terminating
@@ -988,6 +1015,10 @@ func setWS(ctx *cli.Context, cfg *node.Config) {
9881015
if ctx.GlobalIsSet(WSPathPrefixFlag.Name) {
9891016
cfg.WSPathPrefix = ctx.GlobalString(WSPathPrefixFlag.Name)
9901017
}
1018+
1019+
if ctx.GlobalBool(StateDiffFlag.Name) {
1020+
cfg.WSModules = append(cfg.WSModules, "statediff")
1021+
}
9911022
}
9921023

9931024
// setIPC creates an IPC path configuration from the set command line flags,
@@ -1665,15 +1696,7 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
16651696
}
16661697

16671698
// RegisterEthService adds an Ethereum client to the stack.
1668-
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) ethapi.Backend {
1669-
if cfg.SyncMode == downloader.LightSync {
1670-
backend, err := les.New(stack, cfg)
1671-
if err != nil {
1672-
Fatalf("Failed to register the Ethereum service: %v", err)
1673-
}
1674-
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend))
1675-
return backend.ApiBackend
1676-
}
1699+
func RegisterEthService(stack *node.Node, cfg *eth.Config) *eth.Ethereum {
16771700
backend, err := eth.New(stack, cfg)
16781701
if err != nil {
16791702
Fatalf("Failed to register the Ethereum service: %v", err)
@@ -1684,8 +1707,16 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) ethapi.Backend
16841707
Fatalf("Failed to create the LES server: %v", err)
16851708
}
16861709
}
1687-
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
1688-
return backend.APIBackend
1710+
return backend
1711+
}
1712+
1713+
// RegisterLesEthService adds an Ethereum les client to the stack.
1714+
func RegisterLesEthService(stack *node.Node, cfg *eth.Config) *les.LightEthereum {
1715+
backend, err := les.New(stack, cfg)
1716+
if err != nil {
1717+
Fatalf("Failed to register the Ethereum service: %v", err)
1718+
}
1719+
return backend
16891720
}
16901721

16911722
// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to
@@ -1703,6 +1734,13 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.C
17031734
}
17041735
}
17051736

1737+
// RegisterStateDiffService configures and registers a service to stream state diff data over RPC
1738+
func RegisterStateDiffService(stack *node.Node, ethServ *eth.Ethereum, params statediff.ServiceParams) {
1739+
if err := statediff.New(stack, ethServ, params); err != nil {
1740+
Fatalf("Failed to register the Statediff service: %v", err)
1741+
}
1742+
}
1743+
17061744
func SetupMetrics(ctx *cli.Context) {
17071745
if metrics.Enabled {
17081746
log.Info("Enabling metrics collection")

core/blockchain.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ type CacheConfig struct {
131131
Preimages bool // Whether to store preimage of trie key to the disk
132132

133133
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
134+
StateDiffing bool // Whether or not the statediffing service is running
134135
}
135136

136137
// defaultCacheConfig are the default caching values if none are specified by the
@@ -210,6 +211,10 @@ type BlockChain struct {
210211
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
211212
terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion.
212213
writeLegacyJournal bool // Testing flag used to flush the snapshot journal in legacy format.
214+
215+
// Locked roots and their mutex
216+
trieLock sync.Mutex
217+
lockedRoots map[common.Hash]bool
213218
}
214219

215220
// NewBlockChain returns a fully initialised block chain using information
@@ -246,6 +251,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
246251
futureBlocks: futureBlocks,
247252
engine: engine,
248253
vmConfig: vmConfig,
254+
lockedRoots: make(map[common.Hash]bool),
249255
}
250256
bc.validator = NewBlockValidator(chainConfig, bc, engine)
251257
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
@@ -1037,7 +1043,10 @@ func (bc *BlockChain) Stop() {
10371043
}
10381044
}
10391045
for !bc.triegc.Empty() {
1040-
triedb.Dereference(bc.triegc.PopItem().(common.Hash))
1046+
pruneRoot := bc.triegc.PopItem().(common.Hash)
1047+
if !bc.TrieLocked(pruneRoot) {
1048+
triedb.Dereference(pruneRoot)
1049+
}
10411050
}
10421051
if size, _ := triedb.Size(); size != 0 {
10431052
log.Error("Dangling trie nodes after full cleanup")
@@ -1543,6 +1552,11 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
15431552
triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
15441553
bc.triegc.Push(root, -int64(block.NumberU64()))
15451554

1555+
// If we are statediffing, lock the trie until the statediffing service is done using it
1556+
if bc.cacheConfig.StateDiffing {
1557+
bc.LockTrie(root)
1558+
}
1559+
15461560
if current := block.NumberU64(); current > TriesInMemory {
15471561
// If we exceeded our memory allowance, flush matured singleton nodes to disk
15481562
var (
@@ -1581,7 +1595,11 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
15811595
bc.triegc.Push(root, number)
15821596
break
15831597
}
1584-
triedb.Dereference(root.(common.Hash))
1598+
pruneRoot := root.(common.Hash)
1599+
if !bc.TrieLocked(pruneRoot) {
1600+
log.Debug("Dereferencing", "root", root.(common.Hash).Hex())
1601+
triedb.Dereference(pruneRoot)
1602+
}
15851603
}
15861604
}
15871605
}
@@ -2550,3 +2568,28 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript
25502568
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
25512569
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
25522570
}
2571+
2572+
// TrieLocked returns whether the trie associated with the provided root is locked for use
2573+
func (bc *BlockChain) TrieLocked(root common.Hash) bool {
2574+
bc.trieLock.Lock()
2575+
locked, ok := bc.lockedRoots[root]
2576+
bc.trieLock.Unlock()
2577+
if !ok {
2578+
return false
2579+
}
2580+
return locked
2581+
}
2582+
2583+
// LockTrie prevents dereferencing of the provided root
2584+
func (bc *BlockChain) LockTrie(root common.Hash) {
2585+
bc.trieLock.Lock()
2586+
bc.lockedRoots[root] = true
2587+
bc.trieLock.Unlock()
2588+
}
2589+
2590+
// UnlockTrie allows dereferencing of the provided root- provided it was previously locked
2591+
func (bc *BlockChain) UnlockTrie(root common.Hash) {
2592+
bc.trieLock.Lock()
2593+
bc.lockedRoots[root] = false
2594+
bc.trieLock.Unlock()
2595+
}

0 commit comments

Comments
 (0)