Skip to content

Commit

Permalink
added comments
Browse files Browse the repository at this point in the history
  • Loading branch information
dhaidashenko committed May 17, 2024
1 parent c5c0b14 commit f17bd16
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 25 deletions.
7 changes: 6 additions & 1 deletion .changeset/early-shoes-sit.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@
"chainlink": patch
---

Fixed CPU usage issues caused by inefficiencies in HeadTracker #bugfix
Fixed CPU usage issues caused by inefficiencies in HeadTracker.

HeadTracker's support of finality tags caused a drastic increase in the number of tracked blocks on the Arbitrum chain (from 50 to 12,000), which has led to a 30% increase in CPU usage.

The fix improves the data structure for tracking blocks and makes lookup more efficient. BenchmarkHeadTracker_Backfill shows 40x time reduction.
#bugfix
7 changes: 5 additions & 2 deletions core/chains/evm/headtracker/head_tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,8 @@ func TestHeadTracker_Backfill(t *testing.T) {
})
}

// BenchmarkHeadTracker_Backfill - benchmarks HeadTracker's Backfill with focus on efficiency after initial
// backfill on start up
func BenchmarkHeadTracker_Backfill(b *testing.B) {
cfg := configtest.NewGeneralConfig(b, nil)

Expand Down Expand Up @@ -1012,9 +1014,10 @@ func BenchmarkHeadTracker_Backfill(b *testing.B) {
err := ht.headTracker.Backfill(ctx, latest, finalized)
require.NoError(b, err)
b.ResetTimer()
// focus benchmark on processing of a new latest block
for i := 0; i < b.N; i++ {
latest = makeBlock(finalityDepth)
finalized = makeBlock(1)
latest = makeBlock(int64(finalityDepth + i))
finalized = makeBlock(int64(i + 1))
err := ht.headTracker.Backfill(ctx, latest, finalized)
require.NoError(b, err)
}
Expand Down
54 changes: 32 additions & 22 deletions core/chains/evm/headtracker/heads.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ type Heads interface {
}

type heads struct {
heads []*evmtypes.Head
headsMap map[common.Hash]*evmtypes.Head
mu sync.RWMutex
heads []*evmtypes.Head
mu sync.RWMutex
}

func NewHeads() Heads {
Expand All @@ -49,11 +48,12 @@ func (h *heads) HeadByHash(hash common.Hash) *evmtypes.Head {
h.mu.RLock()
defer h.mu.RUnlock()

if h.headsMap == nil {
return nil
for _, head := range h.heads {
if head.Hash == hash {
return head
}
}

return h.headsMap[hash]
return nil
}

func (h *heads) Count() int {
Expand All @@ -74,23 +74,26 @@ func (h *heads) MarkFinalized(finalized common.Hash, minBlockToKeep int64) bool
}

// deep copy to avoid race on head.Parent
h.heads, h.headsMap = deepCopy(h.heads, minBlockToKeep)
h.heads = deepCopy(h.heads, minBlockToKeep)

finalizedHead, ok := h.headsMap[finalized]
if !ok {
return false
}
for finalizedHead != nil {
finalizedHead.IsFinalized = true
finalizedHead = finalizedHead.Parent
head := h.heads[0]
foundFinalized := false
for head != nil {
if head.Hash == finalized {
foundFinalized = true
}

// we might see finalized to move back in chain due to request to lagging RPC,
// we should not override the flag in such cases
head.IsFinalized = head.IsFinalized || foundFinalized
head = head.Parent
}

return true
return foundFinalized
}

func deepCopy(oldHeads []*evmtypes.Head, minBlockToKeep int64) ([]*evmtypes.Head, map[common.Hash]*evmtypes.Head) {
func deepCopy(oldHeads []*evmtypes.Head, minBlockToKeep int64) []*evmtypes.Head {
headsMap := make(map[common.Hash]*evmtypes.Head, len(oldHeads))
heads := make([]*evmtypes.Head, 0, len(headsMap))
for _, head := range oldHeads {
if head.Hash == head.ParentHash {
// shouldn't happen but it is untrusted input
Expand All @@ -108,11 +111,18 @@ func deepCopy(oldHeads []*evmtypes.Head, minBlockToKeep int64) ([]*evmtypes.Head
// prefer head that was already in heads as it might have been marked as finalized on previous run
if _, ok := headsMap[head.Hash]; !ok {
headsMap[head.Hash] = &headCopy
heads = append(heads, &headCopy)
}
}

// sort the heads as original slice might be out of order
heads := make([]*evmtypes.Head, 0, len(headsMap))
// unsorted unique heads
{
for _, head := range headsMap {
heads = append(heads, head)
}
}

// sort the heads
sort.SliceStable(heads, func(i, j int) bool {
// sorting from the highest number to lowest
return heads[i].Number > heads[j].Number
Expand All @@ -127,13 +137,13 @@ func deepCopy(oldHeads []*evmtypes.Head, minBlockToKeep int64) ([]*evmtypes.Head
}
}

return heads, headsMap
return heads
}

func (h *heads) AddHeads(newHeads ...*evmtypes.Head) {
h.mu.Lock()
defer h.mu.Unlock()

// deep copy to avoid race on head.Parent
h.heads, h.headsMap = deepCopy(append(h.heads, newHeads...), 0)
h.heads = deepCopy(append(h.heads, newHeads...), 0)
}

0 comments on commit f17bd16

Please sign in to comment.