Skip to content

Commit 19ee8a0

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 * 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 * 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 * if statediffing is on, lock tries in triedb until the statediffing service signals they are done using them * 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 * 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 * 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}* * 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? * 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 * fixes after rebase onto 1.9.23 * fix API registration * 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. * "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 * update github actions; linting * add poststate and status to receipt ipld indexes * stateDiffFor endpoints for fetching or writing statediff object by blockhash; bump statediff version * fixes after rebase on to v1.10.1 * update github actions and version meta; go fmt * add leaf key to removed 'nodes' * include Postgres migrations and schema * service documentation * touching up update github actions after rebase fix connection leak (misplaced defer) and perform proper rollback on errs improve error logging; handle PushBlock internal err * build docker image and publish it to Docker Hub on release * add access list tx to unit tests * MarshalBinary and UnmarshalBinary methods for receipt * fix error caused by 2718 by using MarshalBinary instead of EncodeRLP methods * ipld encoding/decoding tests * update TxModel; add AccessListElementModel * index tx type and access lists * add access list metrics * unit tests for tx_type and access list table * unit tests for receipt marshal/unmarshal binary methods * improve documentation of the encoding methods * fix issue identified in linting
1 parent 991384a commit 19ee8a0

File tree

127 files changed

+15937
-39
lines changed

Some content is hidden

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

127 files changed

+15937
-39
lines changed

.github/workflows/on-master.yaml

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

.github/workflows/on-pr.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 .

.github/workflows/publish.yaml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
echo ::set-output name=tag::$(echo ${GITHUB_REF#refs/tags/})
15+
- name: Docker Login to Github Registry
16+
run: echo ${{ secrets.GITHUB_TOKEN }} | docker login https://docker.pkg.github.com -u vulcanize --password-stdin
17+
- name: Docker Pull
18+
run: docker pull docker.pkg.github.com/vulcanize/go-ethereum/go-ethereum:${{steps.vars.outputs.sha}}
19+
- name: Copy ethereum binary file
20+
run: docker run --rm --entrypoint cat docker.pkg.github.com/vulcanize/go-ethereum/go-ethereum:${{steps.vars.outputs.sha}} /usr/local/bin/geth > geth-linux-amd64
21+
- name: Docker Login to Docker Registry
22+
run: echo ${{ secrets.VULCANIZEJENKINS_PAT }} | docker login -u vulcanizejenkins --password-stdin
23+
- name: Tag docker image
24+
run: docker tag docker.pkg.github.com/vulcanize/go-ethereum/go-ethereum:${{steps.vars.outputs.sha}} vulcanize/vdb-geth:${{steps.vars.outputs.tag}}
25+
- name: Docker Push to Docker Hub
26+
run: docker push vulcanize/vdb-geth:${{steps.vars.outputs.tag}}
27+
- name: Get release
28+
id: get_release
29+
uses: bruceadams/[email protected]
30+
env:
31+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32+
- name: Upload Release Asset
33+
id: upload-release-asset
34+
uses: actions/upload-release-asset@v1
35+
env:
36+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37+
with:
38+
upload_url: ${{ steps.get_release.outputs.upload_url }}
39+
asset_path: geth-linux-amd64
40+
asset_name: geth-linux-amd64
41+
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: 52 additions & 0 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"
@@ -134,6 +136,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
134136
cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name)
135137
}
136138
applyMetricConfig(ctx, &cfg)
139+
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
140+
cfg.Eth.Diffing = true
141+
}
137142

138143
return stack, cfg
139144
}
@@ -144,6 +149,11 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
144149
if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) {
145150
cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name))
146151
}
152+
153+
if cfg.Eth.SyncMode == downloader.LightSync {
154+
return makeLightNode(ctx, stack, cfg)
155+
}
156+
147157
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
148158

149159
// Configure catalyst.
@@ -156,6 +166,34 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
156166
}
157167
}
158168

169+
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
170+
var dbParams *statediff.DBParams
171+
if ctx.GlobalIsSet(utils.StateDiffDBFlag.Name) {
172+
dbParams = new(statediff.DBParams)
173+
dbParams.ConnectionURL = ctx.GlobalString(utils.StateDiffDBFlag.Name)
174+
if ctx.GlobalIsSet(utils.StateDiffDBNodeIDFlag.Name) {
175+
dbParams.ID = ctx.GlobalString(utils.StateDiffDBNodeIDFlag.Name)
176+
} else {
177+
utils.Fatalf("Must specify node ID for statediff DB output")
178+
}
179+
if ctx.GlobalIsSet(utils.StateDiffDBClientNameFlag.Name) {
180+
dbParams.ClientName = ctx.GlobalString(utils.StateDiffDBClientNameFlag.Name)
181+
} else {
182+
utils.Fatalf("Must specify client name for statediff DB output")
183+
}
184+
} else {
185+
if ctx.GlobalBool(utils.StateDiffWritingFlag.Name) {
186+
utils.Fatalf("Must pass DB parameters if enabling statediff write loop")
187+
}
188+
}
189+
p := statediff.ServiceParams{
190+
DBParams: dbParams,
191+
EnableWriteLoop: ctx.GlobalBool(utils.StateDiffWritingFlag.Name),
192+
NumWorkers: ctx.GlobalUint(utils.StateDiffWorkersFlag.Name),
193+
}
194+
utils.RegisterStateDiffService(stack, eth, &cfg.Eth, p)
195+
}
196+
159197
// Configure GraphQL if requested
160198
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
161199
utils.RegisterGraphQLService(stack, backend, cfg.Node)
@@ -167,6 +205,20 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
167205
return stack, backend
168206
}
169207

208+
func makeLightNode(ctx *cli.Context, stack *node.Node, cfg gethConfig) (*node.Node, ethapi.Backend) {
209+
backend := utils.RegisterLesEthService(stack, &cfg.Eth)
210+
211+
// Configure GraphQL if requested
212+
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
213+
utils.RegisterGraphQLService(stack, backend.ApiBackend, cfg.Node)
214+
}
215+
// Add the Ethereum Stats daemon if requested.
216+
if cfg.Ethstats.URL != "" {
217+
utils.RegisterEthStatsService(stack, backend.ApiBackend, cfg.Ethstats.URL)
218+
}
219+
return stack, backend.ApiBackend
220+
}
221+
170222
// dumpConfig is the dumpconfig command.
171223
func dumpConfig(ctx *cli.Context) error {
172224
_, cfg := makeConfigNode(ctx)

cmd/geth/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ var (
150150
utils.EWASMInterpreterFlag,
151151
utils.EVMInterpreterFlag,
152152
utils.MinerNotifyFullFlag,
153+
utils.StateDiffFlag,
154+
utils.StateDiffDBFlag,
155+
utils.StateDiffDBNodeIDFlag,
156+
utils.StateDiffDBClientNameFlag,
157+
utils.StateDiffWritingFlag,
158+
utils.StateDiffWorkersFlag,
153159
configFileFlag,
154160
utils.CatalystFlag,
155161
}

cmd/geth/usage.go

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

cmd/utils/flags.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ import (
6666
"github.com/ethereum/go-ethereum/p2p/nat"
6767
"github.com/ethereum/go-ethereum/p2p/netutil"
6868
"github.com/ethereum/go-ethereum/params"
69+
"github.com/ethereum/go-ethereum/statediff"
70+
6971
pcsclite "github.com/gballet/go-libpcsclite"
7072
gopsutil "github.com/shirou/gopsutil/mem"
7173
"gopkg.in/urfave/cli.v1"
@@ -760,6 +762,30 @@ var (
760762
Name: "catalyst",
761763
Usage: "Catalyst mode (eth2 integration testing)",
762764
}
765+
StateDiffFlag = cli.BoolFlag{
766+
Name: "statediff",
767+
Usage: "Enables the processing of state diffs between each block",
768+
}
769+
StateDiffDBFlag = cli.StringFlag{
770+
Name: "statediff.db",
771+
Usage: "PostgreSQL database connection string for writing state diffs",
772+
}
773+
StateDiffDBNodeIDFlag = cli.StringFlag{
774+
Name: "statediff.dbnodeid",
775+
Usage: "Node ID to use when writing state diffs to database",
776+
}
777+
StateDiffDBClientNameFlag = cli.StringFlag{
778+
Name: "statediff.dbclientname",
779+
Usage: "Client name to use when writing state diffs to database",
780+
}
781+
StateDiffWritingFlag = cli.BoolFlag{
782+
Name: "statediff.writing",
783+
Usage: "Activates progressive writing of state diffs to database as new block are synced",
784+
}
785+
StateDiffWorkersFlag = cli.UintFlag{
786+
Name: "statediff.workers",
787+
Usage: "Number of concurrent workers to use during statediff processing (0 = 1)",
788+
}
763789
)
764790

765791
// MakeDataDir retrieves the currently requested data directory, terminating
@@ -1000,6 +1026,10 @@ func setWS(ctx *cli.Context, cfg *node.Config) {
10001026
if ctx.GlobalIsSet(WSPathPrefixFlag.Name) {
10011027
cfg.WSPathPrefix = ctx.GlobalString(WSPathPrefixFlag.Name)
10021028
}
1029+
1030+
if ctx.GlobalBool(StateDiffFlag.Name) {
1031+
cfg.WSModules = append(cfg.WSModules, "statediff")
1032+
}
10031033
}
10041034

10051035
// setIPC creates an IPC path configuration from the set command line flags,
@@ -1720,6 +1750,15 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend
17201750
return backend.APIBackend, backend
17211751
}
17221752

1753+
// RegisterLesEthService adds an Ethereum les client to the stack.
1754+
func RegisterLesEthService(stack *node.Node, cfg *eth.Config) *les.LightEthereum {
1755+
backend, err := les.New(stack, cfg)
1756+
if err != nil {
1757+
Fatalf("Failed to register the Ethereum service: %v", err)
1758+
}
1759+
return backend
1760+
}
1761+
17231762
// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to
17241763
// the given node.
17251764
func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url string) {
@@ -1735,6 +1774,13 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.C
17351774
}
17361775
}
17371776

1777+
// RegisterStateDiffService configures and registers a service to stream state diff data over RPC
1778+
func RegisterStateDiffService(stack *node.Node, ethServ *eth.Ethereum, cfg *ethconfig.Config, params statediff.ServiceParams) {
1779+
if err := statediff.New(stack, ethServ, cfg, params); err != nil {
1780+
Fatalf("Failed to register the Statediff service: %v", err)
1781+
}
1782+
}
1783+
17381784
func SetupMetrics(ctx *cli.Context) {
17391785
if metrics.Enabled {
17401786
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
@@ -209,6 +210,10 @@ type BlockChain struct {
209210

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.
213+
214+
// Locked roots and their mutex
215+
trieLock sync.Mutex
216+
lockedRoots map[common.Hash]bool
212217
}
213218

214219
// NewBlockChain returns a fully initialised block chain using information
@@ -245,6 +250,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
245250
futureBlocks: futureBlocks,
246251
engine: engine,
247252
vmConfig: vmConfig,
253+
lockedRoots: make(map[common.Hash]bool),
248254
}
249255
bc.validator = NewBlockValidator(chainConfig, bc, engine)
250256
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
@@ -1031,7 +1037,10 @@ func (bc *BlockChain) Stop() {
10311037
}
10321038
}
10331039
for !bc.triegc.Empty() {
1034-
triedb.Dereference(bc.triegc.PopItem().(common.Hash))
1040+
pruneRoot := bc.triegc.PopItem().(common.Hash)
1041+
if !bc.TrieLocked(pruneRoot) {
1042+
triedb.Dereference(pruneRoot)
1043+
}
10351044
}
10361045
if size, _ := triedb.Size(); size != 0 {
10371046
log.Error("Dangling trie nodes after full cleanup")
@@ -1488,6 +1497,11 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
14881497
triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
14891498
bc.triegc.Push(root, -int64(block.NumberU64()))
14901499

1500+
// If we are statediffing, lock the trie until the statediffing service is done using it
1501+
if bc.cacheConfig.StateDiffing {
1502+
bc.LockTrie(root)
1503+
}
1504+
14911505
if current := block.NumberU64(); current > TriesInMemory {
14921506
// If we exceeded our memory allowance, flush matured singleton nodes to disk
14931507
var (
@@ -1526,7 +1540,11 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
15261540
bc.triegc.Push(root, number)
15271541
break
15281542
}
1529-
triedb.Dereference(root.(common.Hash))
1543+
pruneRoot := root.(common.Hash)
1544+
if !bc.TrieLocked(pruneRoot) {
1545+
log.Debug("Dereferencing", "root", root.(common.Hash).Hex())
1546+
triedb.Dereference(pruneRoot)
1547+
}
15301548
}
15311549
}
15321550
}
@@ -2510,3 +2528,28 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript
25102528
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
25112529
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
25122530
}
2531+
2532+
// TrieLocked returns whether the trie associated with the provided root is locked for use
2533+
func (bc *BlockChain) TrieLocked(root common.Hash) bool {
2534+
bc.trieLock.Lock()
2535+
locked, ok := bc.lockedRoots[root]
2536+
bc.trieLock.Unlock()
2537+
if !ok {
2538+
return false
2539+
}
2540+
return locked
2541+
}
2542+
2543+
// LockTrie prevents dereferencing of the provided root
2544+
func (bc *BlockChain) LockTrie(root common.Hash) {
2545+
bc.trieLock.Lock()
2546+
bc.lockedRoots[root] = true
2547+
bc.trieLock.Unlock()
2548+
}
2549+
2550+
// UnlockTrie allows dereferencing of the provided root- provided it was previously locked
2551+
func (bc *BlockChain) UnlockTrie(root common.Hash) {
2552+
bc.trieLock.Lock()
2553+
bc.lockedRoots[root] = false
2554+
bc.trieLock.Unlock()
2555+
}

0 commit comments

Comments
 (0)