Skip to content

Commit

Permalink
BCI-3668 Optimise HeadTracker's memory usage (#14130)
Browse files Browse the repository at this point in the history
* base benchmarks

* Reduce allocs noise from mocks in Backfill Benchmark

* Optimize HeadTracker's memory usage

* avoid .Parent race in confirmer

* optimise block history estimator heads usage

* update telemetry test to use new Parent and IsFinalized

* avoid redundant allocations

* Revert "avoid redundant allocations"

This reverts commit 76343e5.

* lint fixes

* remove redundant files

* drop cycle detection logic from head methods. Heads cycle prevention should be enough

* drop unused test

* nits
  • Loading branch information
dhaidashenko committed Sep 23, 2024
1 parent 27d5cbf commit 31874ba
Show file tree
Hide file tree
Showing 30 changed files with 681 additions and 584 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-shoes-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

Optimize HeadTracker's memory usage #internal
5 changes: 3 additions & 2 deletions common/txmgr/confirmer.go
Original file line number Diff line number Diff line change
Expand Up @@ -1148,10 +1148,11 @@ func hasReceiptInLongestChain[
}
}
}
if head.GetParent() == nil {

head = head.GetParent()
if head == nil {
return false
}
head = head.GetParent()
}
}

Expand Down
10 changes: 9 additions & 1 deletion core/chains/evm/client/simulated_backend_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,15 @@ func (c *SimulatedBackendClient) SubscribeNewHead(
case h := <-ch:
var head *evmtypes.Head
if h != nil {
head = &evmtypes.Head{Difficulty: h.Difficulty, Timestamp: time.Unix(int64(h.Time), 0), Number: h.Number.Int64(), Hash: h.Hash(), ParentHash: h.ParentHash, Parent: lastHead, EVMChainID: ubig.New(c.chainId)}
head = &evmtypes.Head{
Difficulty: h.Difficulty,
Timestamp: time.Unix(int64(h.Time), 0), //nolint:gosec
Number: h.Number.Int64(),
Hash: h.Hash(),
ParentHash: h.ParentHash,
EVMChainID: ubig.New(c.chainId),
}
head.Parent.Store(lastHead)
lastHead = head
}
select {
Expand Down
3 changes: 2 additions & 1 deletion core/chains/evm/gas/block_history_estimator.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,12 +655,13 @@ func (b *BlockHistoryEstimator) FetchBlocks(ctx context.Context, head *evmtypes.
}

blocks := make(map[int64]evmtypes.Block)
earliestInChain := head.EarliestInChain()
for _, block := range b.getBlocks() {
// Make a best-effort to be re-org resistant using the head
// chain, refetch blocks that got re-org'd out.
// NOTE: Any blocks in the history that are older than the oldest block
// in the provided chain will be assumed final.
if block.Number < head.EarliestInChain().BlockNumber() {
if block.Number < earliestInChain.BlockNumber() {
blocks[block.Number] = block
} else if head.IsInChain(block.Hash) {
blocks[block.Number] = block
Expand Down
6 changes: 3 additions & 3 deletions core/chains/evm/gas/block_history_estimator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) {

head2 := evmtypes.NewHead(big.NewInt(2), b2.Hash, b1.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID))
head3 := evmtypes.NewHead(big.NewInt(3), b3.Hash, b2.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID))
head3.Parent = &head2
head3.Parent.Store(&head2)
err := bhe.FetchBlocks(tests.Context(t), &head3)
require.NoError(t, err)

Expand Down Expand Up @@ -570,7 +570,7 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) {
// RE-ORG, head2 and head3 have different hash than saved b2 and b3
head2 := evmtypes.NewHead(big.NewInt(2), utils.NewHash(), b1.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID))
head3 := evmtypes.NewHead(big.NewInt(3), utils.NewHash(), head2.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID))
head3.Parent = &head2
head3.Parent.Store(&head2)

ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
return len(b) == 2 &&
Expand Down Expand Up @@ -643,7 +643,7 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) {
// head2 and head3 have identical hash to saved blocks
head2 := evmtypes.NewHead(big.NewInt(2), b2.Hash, b1.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID))
head3 := evmtypes.NewHead(big.NewInt(3), b3.Hash, head2.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID))
head3.Parent = &head2
head3.Parent.Store(&head2)

err := bhe.FetchBlocks(tests.Context(t), &head3)
require.NoError(t, err)
Expand Down
12 changes: 7 additions & 5 deletions core/chains/evm/headtracker/head_saver.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ func NewHeadSaver(lggr logger.Logger, orm ORM, config commontypes.Config, htConf
}

func (hs *headSaver) Save(ctx context.Context, head *evmtypes.Head) error {
if err := hs.orm.IdempotentInsertHead(ctx, head); err != nil {
// adding new head might form a cycle, so it's better to validate cached chain before persisting it
if err := hs.heads.AddHeads(head); err != nil {
return err
}

hs.heads.AddHeads(head)

return nil
return hs.orm.IdempotentInsertHead(ctx, head)
}

func (hs *headSaver) Load(ctx context.Context, latestFinalized int64) (chain *evmtypes.Head, err error) {
Expand All @@ -51,7 +50,10 @@ func (hs *headSaver) Load(ctx context.Context, latestFinalized int64) (chain *ev
return nil, err
}

hs.heads.AddHeads(heads...)
err = hs.heads.AddHeads(heads...)
if err != nil {
return nil, fmt.Errorf("failed to populate cache with loaded heads: %w", err)
}
return hs.heads.LatestHead(), nil
}

Expand Down
3 changes: 1 addition & 2 deletions core/chains/evm/headtracker/head_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ import (

"github.com/smartcontractkit/chainlink/v2/common/headtracker"
commontypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types"
evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types"
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
)

func NewHeadTracker(
lggr logger.Logger,
ethClient evmclient.Client,
ethClient httypes.Client,
config commontypes.Config,
htConfig commontypes.HeadTrackerConfig,
headBroadcaster httypes.HeadBroadcaster,
Expand Down
Loading

0 comments on commit 31874ba

Please sign in to comment.