Skip to content

Add minimum proposer window length #1667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 8 additions & 1 deletion utils/window/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type window[T any] struct {
ttl time.Duration
// max amount of elements allowed in the window
maxSize int
// min amount of elements required in the window before allowing removal
// based on time
minSize int

// mutex for synchronization
lock sync.RWMutex
Expand All @@ -39,6 +42,7 @@ type window[T any] struct {
type Config struct {
Clock *mockable.Clock
MaxSize int
MinSize int
TTL time.Duration
}

Expand All @@ -48,6 +52,7 @@ func New[T any](config Config) Window[T] {
clock: config.Clock,
ttl: config.TTL,
maxSize: config.MaxSize,
minSize: config.MinSize,
elements: buffer.NewUnboundedDeque[node[T]](config.MaxSize + 1),
}
}
Expand All @@ -74,6 +79,7 @@ func (w *window[T]) Add(value T) {
func (w *window[T]) Oldest() (T, bool) {
w.lock.RLock()
defer w.lock.RUnlock()

oldest, ok := w.elements.PeekLeft()
if !ok {
return utils.Zero[T](), false
Expand All @@ -85,6 +91,7 @@ func (w *window[T]) Oldest() (T, bool) {
func (w *window[T]) Length() int {
w.lock.RLock()
defer w.lock.RUnlock()

return w.elements.Len()
}

Expand All @@ -97,7 +104,7 @@ func (w *window[T]) removeStaleNodes() {
if !ok {
return
}
for {
for w.elements.Len() > w.minSize {
oldest, ok := w.elements.PeekLeft()
if !ok || newest.entryTime.Sub(oldest.entryTime) <= w.ttl {
return
Expand Down
83 changes: 64 additions & 19 deletions utils/window/window_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ func TestAdd(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)

clock := &mockable.Clock{}
clock.Set(time.Now())

window := New[int](
Config{
Clock: &mockable.Clock{},
Clock: clock,
MaxSize: testMaxSize,
TTL: testTTL,
},
Expand All @@ -70,16 +73,17 @@ func TestAdd(t *testing.T) {
func TestTTLAdd(t *testing.T) {
require := require.New(t)

clock := mockable.Clock{}
clock := &mockable.Clock{}
start := time.Now()
clock.Set(start)

window := New[int](
Config{
Clock: &clock,
Clock: clock,
MaxSize: testMaxSize,
TTL: testTTL,
},
)
epochStart := time.Unix(0, 0)
clock.Set(epochStart)

// Now the window looks like this:
// [1, 2, 3]
Expand All @@ -93,7 +97,7 @@ func TestTTLAdd(t *testing.T) {
require.Equal(1, oldest)
// Now we're one second past the ttl of 10 seconds as defined in testTTL,
// so all existing elements need to be evicted.
clock.Set(epochStart.Add(11 * time.Second))
clock.Set(start.Add(testTTL + time.Second))

// Now the window should look like this:
// [4]
Expand All @@ -106,7 +110,7 @@ func TestTTLAdd(t *testing.T) {
// Now we're one second before the ttl of 10 seconds of when [4] was added,
// no element should be evicted
// [4, 5]
clock.Set(epochStart.Add(20 * time.Second))
clock.Set(start.Add(2 * testTTL))
window.Add(5)
require.Equal(2, window.Length())
oldest, ok = window.Oldest()
Expand All @@ -115,7 +119,6 @@ func TestTTLAdd(t *testing.T) {

// Now the window is still containing 4:
// [4, 5]
clock.Set(epochStart.Add(20 * time.Second))
// we only evict on Add method because the window is calculated in the last element added
require.Equal(2, window.Length())

Expand All @@ -128,16 +131,17 @@ func TestTTLAdd(t *testing.T) {
func TestTTLLength(t *testing.T) {
require := require.New(t)

clock := mockable.Clock{}
clock := &mockable.Clock{}
start := time.Now()
clock.Set(start)

window := New[int](
Config{
Clock: &clock,
Clock: clock,
MaxSize: testMaxSize,
TTL: testTTL,
},
)
epochStart := time.Unix(0, 0)
clock.Set(epochStart)

// Now the window looks like this:
// [1, 2, 3]
Expand All @@ -149,7 +153,7 @@ func TestTTLLength(t *testing.T) {

// Now we're one second past the ttl of 10 seconds as defined in testTTL,
// so all existing elements need to be evicted.
clock.Set(epochStart.Add(11 * time.Second))
clock.Set(start.Add(testTTL + time.Second))

// No more elements should be present in the window.
require.Equal(3, window.Length())
Expand All @@ -159,18 +163,19 @@ func TestTTLLength(t *testing.T) {
func TestTTLOldest(t *testing.T) {
require := require.New(t)

clock := mockable.Clock{}
clock := &mockable.Clock{}
start := time.Now()
clock.Set(start)

windowIntf := New[int](
Config{
Clock: &clock,
Clock: clock,
MaxSize: testMaxSize,
TTL: testTTL,
},
)
require.IsType(&window[int]{}, windowIntf)
window := windowIntf.(*window[int])
epochStart := time.Unix(0, 0)
clock.Set(epochStart)

// Now the window looks like this:
// [1, 2, 3]
Expand All @@ -185,7 +190,7 @@ func TestTTLOldest(t *testing.T) {

// Now we're one second past the ttl of 10 seconds as defined in testTTL,
// so all existing elements shoud still exist.
clock.Set(epochStart.Add(11 * time.Second))
clock.Set(start.Add(testTTL + time.Second))

// Now there should be three elements in the window
oldest, ok = window.Oldest()
Expand All @@ -198,9 +203,12 @@ func TestTTLOldest(t *testing.T) {
func TestMaxCapacity(t *testing.T) {
require := require.New(t)

clock := &mockable.Clock{}
clock.Set(time.Now())

window := New[int](
Config{
Clock: &mockable.Clock{},
Clock: clock,
MaxSize: 3,
TTL: testTTL,
},
Expand Down Expand Up @@ -230,3 +238,40 @@ func TestMaxCapacity(t *testing.T) {
require.True(ok)
require.Equal(4, oldest)
}

// Tests that we do not evict past the minimum window size
func TestMinCapacity(t *testing.T) {
require := require.New(t)

clock := &mockable.Clock{}
start := time.Now()
clock.Set(start)

window := New[int](
Config{
Clock: clock,
MaxSize: 3,
MinSize: 2,
TTL: testTTL,
},
)

// Now the window looks like this:
// [1, 2, 3]
window.Add(1)
window.Add(2)
window.Add(3)

clock.Set(start.Add(testTTL + time.Second))

// All of [1, 2, 3] are past the ttl now, but we don't evict 3 because of
// the minimum length.
// Now the window should look like this:
// [3, 4]
window.Add(4)

require.Equal(2, window.Length())
oldest, ok := window.Oldest()
require.True(ok)
require.Equal(3, oldest)
}
2 changes: 2 additions & 0 deletions vms/platformvm/validators/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
const (
validatorSetsCacheSize = 64
maxRecentlyAcceptedWindowSize = 64
minRecentlyAcceptedWindowSize = 16
recentlyAcceptedWindowTTL = 2 * time.Minute
)

Expand Down Expand Up @@ -67,6 +68,7 @@ func NewManager(
window.Config{
Clock: clk,
MaxSize: maxRecentlyAcceptedWindowSize,
MinSize: minRecentlyAcceptedWindowSize,
TTL: recentlyAcceptedWindowTTL,
},
),
Expand Down