Skip to content

Commit 6395c15

Browse files
authored
core/state, eth, trie: stabilize memory use, fix memory leak ethereum#21491 (#1040)
1 parent b94b29b commit 6395c15

File tree

11 files changed

+66
-37
lines changed

11 files changed

+66
-37
lines changed

XDCx/tradingstate/state_liquidationprice.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func (l *liquidationPriceState) updateRoot(db Database) error {
136136
if l.dbErr != nil {
137137
return l.dbErr
138138
}
139-
root, err := l.trie.Commit(func(leaf []byte, parent common.Hash) error {
139+
root, err := l.trie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
140140
var orderList orderList
141141
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
142142
return nil

XDCx/tradingstate/state_orderbook.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func (te *tradingExchanges) CommitAsksTrie(db Database) error {
245245
if te.dbErr != nil {
246246
return te.dbErr
247247
}
248-
root, err := te.asksTrie.Commit(func(leaf []byte, parent common.Hash) error {
248+
root, err := te.asksTrie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
249249
var orderList orderList
250250
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
251251
return nil
@@ -307,7 +307,7 @@ func (te *tradingExchanges) CommitBidsTrie(db Database) error {
307307
if te.dbErr != nil {
308308
return te.dbErr
309309
}
310-
root, err := te.bidsTrie.Commit(func(leaf []byte, parent common.Hash) error {
310+
root, err := te.bidsTrie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
311311
var orderList orderList
312312
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
313313
return nil
@@ -783,7 +783,7 @@ func (t *tradingExchanges) CommitLiquidationPriceTrie(db Database) error {
783783
if t.dbErr != nil {
784784
return t.dbErr
785785
}
786-
root, err := t.liquidationPriceTrie.Commit(func(leaf []byte, parent common.Hash) error {
786+
root, err := t.liquidationPriceTrie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
787787
var orderList orderList
788788
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
789789
return nil

XDCx/tradingstate/statedb.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ func (t *TradingStateDB) Commit() (root common.Hash, err error) {
589589
}
590590
}
591591
// Write trie changes.
592-
root, err = t.trie.Commit(func(leaf []byte, parent common.Hash) error {
592+
root, err = t.trie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
593593
var exchange tradingExchangeObject
594594
if err := rlp.DecodeBytes(leaf, &exchange); err != nil {
595595
return nil

XDCxlending/lendingstate/state_lendingbook.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ func (le *lendingExchangeState) CommitInvestingTrie(db Database) error {
472472
if le.dbErr != nil {
473473
return le.dbErr
474474
}
475-
root, err := le.investingTrie.Commit(func(leaf []byte, parent common.Hash) error {
475+
root, err := le.investingTrie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
476476
var orderList itemList
477477
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
478478
return nil
@@ -493,7 +493,7 @@ func (le *lendingExchangeState) CommitBorrowingTrie(db Database) error {
493493
if le.dbErr != nil {
494494
return le.dbErr
495495
}
496-
root, err := le.borrowingTrie.Commit(func(leaf []byte, parent common.Hash) error {
496+
root, err := le.borrowingTrie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
497497
var orderList itemList
498498
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
499499
return nil
@@ -514,7 +514,7 @@ func (le *lendingExchangeState) CommitLiquidationTimeTrie(db Database) error {
514514
if le.dbErr != nil {
515515
return le.dbErr
516516
}
517-
root, err := le.liquidationTimeTrie.Commit(func(leaf []byte, parent common.Hash) error {
517+
root, err := le.liquidationTimeTrie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
518518
var orderList itemList
519519
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
520520
return nil

XDCxlending/lendingstate/statedb.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ func (ls *LendingStateDB) Commit() (root common.Hash, err error) {
578578
}
579579
}
580580
// Write trie changes.
581-
root, err = ls.trie.Commit(func(leaf []byte, parent common.Hash) error {
581+
root, err = ls.trie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
582582
var exchange lendingObject
583583
if err := rlp.DecodeBytes(leaf, &exchange); err != nil {
584584
return nil

core/state/statedb.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
840840
// Write the account trie changes, measuing the amount of wasted time
841841
defer func(start time.Time) { s.AccountCommits += time.Since(start) }(time.Now())
842842

843-
return s.trie.Commit(func(leaf []byte, parent common.Hash) error {
843+
return s.trie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
844844
var account Account
845845
if err := rlp.DecodeBytes(leaf, &account); err != nil {
846846
return nil

core/state/sync.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ import (
2828
// NewStateSync create a new state trie download scheduler.
2929
func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.SyncBloom) *trie.Sync {
3030
var syncer *trie.Sync
31-
callback := func(leaf []byte, parent common.Hash) error {
31+
callback := func(path []byte, leaf []byte, parent common.Hash) error {
3232
var obj Account
3333
if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil {
3434
return err
3535
}
36-
syncer.AddSubTrie(obj.Root, 64, parent, nil)
37-
syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), 64, parent)
36+
syncer.AddSubTrie(obj.Root, path, parent, nil)
37+
syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), path, parent)
3838
return nil
3939
}
4040
syncer = trie.NewSync(root, database, callback, bloom)

eth/downloader/downloader.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,7 +1545,13 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error {
15451545
// Start syncing state of the reported head block. This should get us most of
15461546
// the state of the pivot block.
15471547
sync := d.syncState(latest.Root)
1548-
defer sync.Cancel()
1548+
defer func() {
1549+
// The `sync` object is replaced every time the pivot moves. We need to
1550+
// defer close the very last active one, hence the lazy evaluation vs.
1551+
// calling defer sync.Cancel() !!!
1552+
sync.Cancel()
1553+
}()
1554+
15491555
closeOnErr := func(s *stateSync) {
15501556
if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled {
15511557
d.queue.Close() // wake up Results
@@ -1603,9 +1609,8 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error {
16031609
// If new pivot block found, cancel old state retrieval and restart
16041610
if oldPivot != P {
16051611
sync.Cancel()
1606-
16071612
sync = d.syncState(P.Header.Root)
1608-
defer sync.Cancel()
1613+
16091614
go closeOnErr(sync)
16101615
oldPivot = P
16111616
}

trie/committer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,13 @@ func (c *committer) commitLoop(db *Database) {
218218
switch n := n.(type) {
219219
case *shortNode:
220220
if child, ok := n.Val.(valueNode); ok {
221-
c.onleaf(child, hash)
221+
c.onleaf(nil, child, hash)
222222
}
223223
case *fullNode:
224224
// For children in range [0, 15], it's impossible
225225
// to contain valuenode. Only check the 17th child.
226226
if n.Children[16] != nil {
227-
c.onleaf(n.Children[16].(valueNode), hash)
227+
c.onleaf(nil, n.Children[16].(valueNode), hash)
228228
}
229229
}
230230
}

trie/sync.go

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,19 @@ var ErrNotRequested = errors.New("not requested")
3535
// Node it already processed previously.
3636
var ErrAlreadyProcessed = errors.New("already processed")
3737

38+
// maxFetchesPerDepth is the maximum number of pending trie nodes per depth. The
39+
// role of this value is to limit the number of trie nodes that get expanded in
40+
// memory if the node was configured with a significant number of peers.
41+
const maxFetchesPerDepth = 16384
42+
3843
// request represents a scheduled or already in-flight state retrieval request.
3944
type request struct {
45+
path []byte // Merkle path leading to this node for prioritization
4046
hash common.Hash // Hash of the Node data content to retrieve
4147
data []byte // Data content of the Node, cached until all subtrees complete
4248
code bool // Whether this is a code entry
4349

4450
parents []*request // Parent state nodes referencing this entry (notify all upon completion)
45-
depth int // Depth level within the trie the Node is located to prioritise DFS
4651
deps int // Number of dependencies before allowed to commit this Node
4752

4853
callback LeafCallback // Callback to invoke if a leaf Node it reached on this branch
@@ -90,6 +95,7 @@ type Sync struct {
9095
nodeReqs map[common.Hash]*request // Pending requests pertaining to a trie node hash
9196
codeReqs map[common.Hash]*request // Pending requests pertaining to a code hash
9297
queue *prque.Prque[int64, any] // Priority queue with the pending requests
98+
fetches map[int]int // Number of active fetches per trie node depth
9399
bloom *SyncBloom // Bloom filter for fast state existence checks
94100
}
95101

@@ -101,14 +107,15 @@ func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallb
101107
nodeReqs: make(map[common.Hash]*request),
102108
codeReqs: make(map[common.Hash]*request),
103109
queue: prque.New[int64, any](nil), // Ugh, can contain both string and hash, whyyy
110+
fetches: make(map[int]int),
104111
bloom: bloom,
105112
}
106-
ts.AddSubTrie(root, 0, common.Hash{}, callback)
113+
ts.AddSubTrie(root, nil, common.Hash{}, callback)
107114
return ts
108115
}
109116

110117
// AddSubTrie registers a new trie to the sync code, rooted at the designated parent.
111-
func (s *Sync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callback LeafCallback) {
118+
func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, callback LeafCallback) {
112119
// Short circuit if the trie is empty or already known
113120
if root == types.EmptyRootHash {
114121
return
@@ -129,8 +136,8 @@ func (s *Sync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callb
129136
}
130137
// Assemble the new sub-trie sync request
131138
req := &request{
139+
path: path,
132140
hash: root,
133-
depth: depth,
134141
callback: callback,
135142
}
136143
// If this sub-trie has a designated parent, link them together
@@ -148,7 +155,7 @@ func (s *Sync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callb
148155
// AddCodeEntry schedules the direct retrieval of a contract code that should not
149156
// be interpreted as a trie node, but rather accepted and stored into the database
150157
// as is.
151-
func (s *Sync) AddCodeEntry(hash common.Hash, depth int, parent common.Hash) {
158+
func (s *Sync) AddCodeEntry(hash common.Hash, path []byte, parent common.Hash) {
152159
// Short circuit if the entry is empty or already known
153160
if hash == types.EmptyCodeHash {
154161
return
@@ -171,9 +178,9 @@ func (s *Sync) AddCodeEntry(hash common.Hash, depth int, parent common.Hash) {
171178
}
172179
// Assemble the new sub-trie sync request
173180
req := &request{
174-
hash: hash,
175-
code: true,
176-
depth: depth,
181+
path: path,
182+
hash: hash,
183+
code: true,
177184
}
178185
// If this sub-trie has a designated parent, link them together
179186
if parent != (common.Hash{}) {
@@ -191,7 +198,18 @@ func (s *Sync) AddCodeEntry(hash common.Hash, depth int, parent common.Hash) {
191198
func (s *Sync) Missing(max int) []common.Hash {
192199
var requests []common.Hash
193200
for !s.queue.Empty() && (max == 0 || len(requests) < max) {
194-
requests = append(requests, s.queue.PopItem().(common.Hash))
201+
// Retrieve th enext item in line
202+
item, prio := s.queue.Peek()
203+
204+
// If we have too many already-pending tasks for this depth, throttle
205+
depth := int(prio >> 56)
206+
if s.fetches[depth] > maxFetchesPerDepth {
207+
break
208+
}
209+
// Item is allowed to be scheduled, add it to the task list
210+
s.queue.Pop()
211+
s.fetches[depth]++
212+
requests = append(requests, item.(common.Hash))
195213
}
196214
return requests
197215
}
@@ -286,31 +304,35 @@ func (s *Sync) schedule(req *request) {
286304
// is a trie node and code has same hash. In this case two elements
287305
// with same hash and same or different depth will be pushed. But it's
288306
// ok the worst case is the second response will be treated as duplicated.
289-
s.queue.Push(req.hash, int64(req.depth))
307+
prio := int64(len(req.path)) << 56 // depth >= 128 will never happen, storage leaves will be included in their parents
308+
for i := 0; i < 14 && i < len(req.path); i++ {
309+
prio |= int64(15-req.path[i]) << (52 - i*4) // 15-nibble => lexicographic order
310+
}
311+
s.queue.Push(req.hash, prio)
290312
}
291313

292314
// children retrieves all the missing children of a state trie entry for future
293315
// retrieval scheduling.
294316
func (s *Sync) children(req *request, object node) ([]*request, error) {
295317
// Gather all the children of the Node, irrelevant whether known or not
296318
type child struct {
297-
node node
298-
depth int
319+
path []byte
320+
node node
299321
}
300322
var children []child
301323

302324
switch node := (object).(type) {
303325
case *shortNode:
304326
children = []child{{
305-
node: node.Val,
306-
depth: req.depth + len(node.Key),
327+
node: node.Val,
328+
path: append(append([]byte(nil), req.path...), node.Key...),
307329
}}
308330
case *fullNode:
309331
for i := 0; i < 17; i++ {
310332
if node.Children[i] != nil {
311333
children = append(children, child{
312-
node: node.Children[i],
313-
depth: req.depth + 1,
334+
node: node.Children[i],
335+
path: append(append([]byte(nil), req.path...), byte(i)),
314336
})
315337
}
316338
}
@@ -323,7 +345,7 @@ func (s *Sync) children(req *request, object node) ([]*request, error) {
323345
// Notify any external watcher of a new key/value Node
324346
if req.callback != nil {
325347
if node, ok := (child.node).(valueNode); ok {
326-
if err := req.callback(node, req.hash); err != nil {
348+
if err := req.callback(req.path, node, req.hash); err != nil {
327349
return nil, err
328350
}
329351
}
@@ -347,9 +369,9 @@ func (s *Sync) children(req *request, object node) ([]*request, error) {
347369
}
348370
// Locally unknown Node, schedule for retrieval
349371
requests = append(requests, &request{
372+
path: child.path,
350373
hash: hash,
351374
parents: []*request{req},
352-
depth: child.depth,
353375
callback: req.callback,
354376
})
355377
}
@@ -365,9 +387,11 @@ func (s *Sync) commit(req *request) (err error) {
365387
if req.code {
366388
s.membatch.codes[req.hash] = req.data
367389
delete(s.codeReqs, req.hash)
390+
s.fetches[len(req.path)]--
368391
} else {
369392
s.membatch.nodes[req.hash] = req.data
370393
delete(s.nodeReqs, req.hash)
394+
s.fetches[len(req.path)]--
371395
}
372396
// Check all parents for completion
373397
for _, parent := range req.parents {

0 commit comments

Comments
 (0)