Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
be37456
feat:add traces to blocksync
mcrakhman Oct 22, 2025
baa5a51
Merge branch 'main' into mcrakhman/block-sync-traces
mcrakhman Nov 4, 2025
fa769df
feat: implement changes to blocksync
mcrakhman Nov 4, 2025
9f0ece9
fix: peer eviction after timer reset
mcrakhman Nov 4, 2025
9a84413
debug: add traces for block requests
mcrakhman Nov 4, 2025
606d402
debug: increase peer diversity
mcrakhman Nov 5, 2025
590e6ad
feat: alternate peers
mcrakhman Nov 6, 2025
dc8f92e
feat: change blocksync logic
mcrakhman Nov 7, 2025
48e74e3
debug: remove debug prints
mcrakhman Nov 7, 2025
bbfa9dc
debug: add correct peer logging
mcrakhman Nov 7, 2025
fefcd76
Merge branch 'main' into mcrakhman/block-sync-traces
mcrakhman Nov 7, 2025
d9fb221
debug: check previous pool settings
mcrakhman Nov 7, 2025
be7f3d7
feat: dynamic pool size
mcrakhman Nov 8, 2025
ae326c2
debug: remove info logs on pool size changes
mcrakhman Nov 8, 2025
836d73a
revert: remove info logs on pool size changes
mcrakhman Nov 8, 2025
e53ac79
debug: change block limits for larger blocks
mcrakhman Nov 8, 2025
126ca04
debug: add number of requesters to log
mcrakhman Nov 8, 2025
97a2bff
feat: reduce requesters when blocks are large
mcrakhman Nov 8, 2025
08c841d
refactor: pool params calculation
mcrakhman Nov 9, 2025
080d21c
feat: use max block size instead of average
mcrakhman Nov 9, 2025
fe4291a
fix: dropped requesters
mcrakhman Nov 9, 2025
6cd7943
debug: log for dropped requester
mcrakhman Nov 9, 2025
b05d5a3
refactor: more tests and comments
mcrakhman Nov 10, 2025
f51d2f1
refactor: num pending
mcrakhman Nov 10, 2025
8b0abc0
refactor: simplify active peers
mcrakhman Nov 10, 2025
a9d117a
feat: add more traces for validate and save time to blocksync
mcrakhman Nov 10, 2025
64b126f
fix: lint and some review fixes
mcrakhman Nov 18, 2025
30576f0
fix: ignore peer id if the request failed
mcrakhman Nov 18, 2025
6e328ef
refactor: renames and change debug levels to trace
mcrakhman Nov 18, 2025
5be511c
fix: review comments
mcrakhman Nov 19, 2025
e78d705
fix: race in test
mcrakhman Nov 19, 2025
2f15854
lint: remove unused function
mcrakhman Nov 19, 2025
f12bbd7
Merge branch 'main' into mcrakhman/block-sync-traces
mcrakhman Nov 19, 2025
02f924d
feat: don't drop requesters until mem limit is reached
mcrakhman Nov 20, 2025
8401cf8
debug: limit requesters to some fixed value
mcrakhman Nov 20, 2025
9c8ca2a
debug: limit max pending per peer - hard cap
mcrakhman Nov 20, 2025
72d7cfb
refactor: simplify params
mcrakhman Nov 20, 2025
567e3d4
refactor: fix and simplify
mcrakhman Nov 20, 2025
0ea933c
refactor: add more comments
mcrakhman Nov 20, 2025
f31a42c
Merge branch 'mcrakhman/blocksync-hard-limit-cap' into mcrakhman/bloc…
mcrakhman Nov 20, 2025
19d1101
refactor: block stats
mcrakhman Nov 20, 2025
f59580f
fix: reset timeout on getting already committed block
mcrakhman Nov 21, 2025
b00f0fc
refactor: review comments
mcrakhman Nov 21, 2025
7aa07f9
feat: add verify_data_root
mcrakhman Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions blocksync/block_stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package blocksync

import "slices"

// blockStats is a circular blockSizes that holds at most n elements
// and provides O(1) average calculation and O(1) max retrieval
type blockStats struct {
blockSizes []int
capacity int
size int // current number of elements
head int // index where the next element will be written
max int // maximum value in the blockSizes
}

const defaultCapacity = 10

// newBlockStats creates a new rotating blockSizes with given capacity
func newBlockStats(capacity int) *blockStats {
if capacity <= 0 {
capacity = defaultCapacity
}
return &blockStats{
blockSizes: make([]int, capacity),
capacity: capacity,
size: 0,
head: 0,
}
}

// Add adds a new element to the blockSizes
// If the blockSizes is full, it removes the oldest element
func (rb *blockStats) Add(value int) {
needRecalculateMax := false

if rb.size < rb.capacity {
rb.blockSizes[rb.head] = value
rb.size++

if rb.size == 1 || value > rb.max {
rb.max = value
}
} else {
// Buffer is full, replace the oldest element
oldValue := rb.blockSizes[rb.head]
rb.blockSizes[rb.head] = value

if oldValue == rb.max {
needRecalculateMax = true
} else if value > rb.max {
rb.max = value
}
}

rb.head = (rb.head + 1) % rb.capacity

if needRecalculateMax {
rb.recalculateMax()
}
}

// recalculateMax recalculates the maximum value by scanning the blockSizes
// This is only called when the previous max value is removed
func (rb *blockStats) recalculateMax() {
if rb.size == 0 {
rb.max = 0
return
}

rb.max = slices.Max(rb.blockSizes)
}

// GetMax returns the maximum value in the blockSizes
func (rb *blockStats) GetMax() int {
return rb.max
}

// GetSize returns the current number of elements in the blockSizes
func (rb *blockStats) GetSize() int {
return rb.size
}

// GetCapacity returns the maximum capacity of the blockSizes
func (rb *blockStats) GetCapacity() int {
return rb.capacity
}
139 changes: 139 additions & 0 deletions blocksync/block_stats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package blocksync

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewBlockStats(t *testing.T) {
t.Run("positive capacity", func(t *testing.T) {
rb := newBlockStats(5)
require.NotNil(t, rb)

assert.Equal(t, 5, rb.GetCapacity())
assert.Equal(t, 0, rb.GetSize())
assert.Equal(t, 0, rb.GetMax())
})

t.Run("non-positive capacity is default", func(t *testing.T) {
require.Equal(t, defaultCapacity, newBlockStats(0).capacity)
require.Equal(t, defaultCapacity, newBlockStats(-1).capacity)
})
}

func TestAddAndGetMax_BelowCapacity(t *testing.T) {
rb := newBlockStats(3)

t.Run("add first element", func(t *testing.T) {
rb.Add(5)
assert.Equal(t, 1, rb.GetSize())
assert.Equal(t, 3, rb.GetCapacity())
assert.Equal(t, 5, rb.GetMax())
})

t.Run("add smaller element", func(t *testing.T) {
rb.Add(3)
assert.Equal(t, 2, rb.GetSize())
assert.Equal(t, 5, rb.GetMax())
})

t.Run("add larger element", func(t *testing.T) {
rb.Add(10)
assert.Equal(t, 3, rb.GetSize())
assert.Equal(t, 10, rb.GetMax())
})
}

func TestAddAndGetMax_AtAndBeyondCapacity(t *testing.T) {
rb := newBlockStats(3)

// Fill to capacity: [5, 4, 3]
rb.Add(5)
rb.Add(4)
rb.Add(3)

assert.Equal(t, 3, rb.GetSize())
assert.Equal(t, 3, rb.GetCapacity())
assert.Equal(t, 5, rb.GetMax())

t.Run("overwrite non-max element", func(t *testing.T) {
// Current underlying slice indices (head == 0):
// [5, 4, 3]
// Overwrite index 0 (value 5) next, so we first overwrite a non-max
// by advancing head manually via add.
// To be precise: head is now 0 (next write), and oldValue is 5 (max).
// To test non-max overwrite, recreate a situation:
rb2 := newBlockStats(3)
rb2.Add(5) // [5, 0, 0] head=1, max=5
rb2.Add(4) // [5, 4, 0] head=2
rb2.Add(3) // [5, 4, 3] head=0

// Now overwrite index 0 with a *larger* value.
rb2.Add(6) // oldValue=5 (max), value=6 -> need recalc? no, value>max so max=6

assert.Equal(t, 3, rb2.GetCapacity())
assert.Equal(t, 3, rb2.GetSize())
assert.Equal(t, 6, rb2.GetMax())
})

t.Run("remove current max triggers recalculation", func(t *testing.T) {
rb3 := newBlockStats(3)

// [5, 4, 3], head=0, max=5
rb3.Add(5)
rb3.Add(4)
rb3.Add(3)

// Next Add overwrites index 0 (value 5, which is max).
// Use a value that is not larger than the remaining ones to force recalc.
rb3.Add(2) // now buffer is [2,4,3] in some rotation; max should be 4.

assert.Equal(t, 3, rb3.GetCapacity())
assert.Equal(t, 3, rb3.GetSize())
assert.Equal(t, 4, rb3.GetMax())
})

t.Run("max updates when new max added after recalc", func(t *testing.T) {
rb4 := newBlockStats(3)

// [5, 4, 3]
rb4.Add(5)
rb4.Add(4)
rb4.Add(3)

// Overwrite 5 with 2 -> recalc, max should become 4
rb4.Add(2)
assert.Equal(t, 3, rb4.GetCapacity())
assert.Equal(t, 3, rb4.GetSize())
assert.Equal(t, 4, rb4.GetMax())

// Now add a new bigger value, should update max immediately
rb4.Add(10)
assert.Equal(t, 3, rb4.GetCapacity())
assert.Equal(t, 3, rb4.GetSize())
assert.Equal(t, 10, rb4.GetMax())
})
}

func TestSizeNeverExceedsCapacity(t *testing.T) {
rb := newBlockStats(2)

rb.Add(1)
assert.Equal(t, 2, rb.GetCapacity())
assert.Equal(t, 1, rb.GetSize())

rb.Add(2)
assert.Equal(t, 2, rb.GetCapacity())
assert.Equal(t, 2, rb.GetSize())

// Now the buffer is full; further adds should keep size == capacity
rb.Add(3)
assert.Equal(t, 2, rb.GetCapacity())
assert.Equal(t, 2, rb.GetSize())

rb.Add(4)
assert.Equal(t, 2, rb.GetCapacity())
assert.Equal(t, 2, rb.GetSize())
}
Loading
Loading