mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Operations pool for BLS-to-execution-changes (#11631)
Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
This commit is contained in:
33
beacon-chain/operations/blstoexec/BUILD.bazel
Normal file
33
beacon-chain/operations/blstoexec/BUILD.bazel
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"pool.go",
|
||||||
|
],
|
||||||
|
importpath = "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/blstoexec",
|
||||||
|
visibility = [
|
||||||
|
"//beacon-chain:__subpackages__",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//config/params:go_default_library",
|
||||||
|
"//consensus-types/primitives:go_default_library",
|
||||||
|
"//container/doubly-linked-list:go_default_library",
|
||||||
|
"//proto/prysm/v1alpha1:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
size = "small",
|
||||||
|
srcs = ["pool_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//config/params:go_default_library",
|
||||||
|
"//consensus-types/primitives:go_default_library",
|
||||||
|
"//proto/prysm/v1alpha1:go_default_library",
|
||||||
|
"//testing/assert:go_default_library",
|
||||||
|
"//testing/require:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
7
beacon-chain/operations/blstoexec/doc.go
Normal file
7
beacon-chain/operations/blstoexec/doc.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Package blstoexecchanges defines an in-memory pool of received BLS-to-ETH1 change objects.
|
||||||
|
// Internally it uses a combination of doubly-linked list and map to handle pool objects.
|
||||||
|
// The linked list approach has an advantage over a slice in terms of memory usage.
|
||||||
|
// For our scenario, we will mostly append objects to the end and remove objects from arbitrary positions,
|
||||||
|
// and during block proposal we will remove objects from the front.
|
||||||
|
// The map is used for looking up the list node that needs to be removed when we mark an object as included in a block.
|
||||||
|
package blstoexec
|
||||||
109
beacon-chain/operations/blstoexec/pool.go
Normal file
109
beacon-chain/operations/blstoexec/pool.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package blstoexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/prysmaticlabs/prysm/v3/config/params"
|
||||||
|
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
||||||
|
doublylinkedlist "github.com/prysmaticlabs/prysm/v3/container/doubly-linked-list"
|
||||||
|
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PoolManager maintains pending and seen BLS-to-execution-change objects.
|
||||||
|
// This pool is used by proposers to insert BLS-to-execution-change objects into new blocks.
|
||||||
|
type PoolManager interface {
|
||||||
|
PendingBLSToExecChanges() ([]*ethpb.SignedBLSToExecutionChange, error)
|
||||||
|
BLSToExecChangesForInclusion() ([]*ethpb.SignedBLSToExecutionChange, error)
|
||||||
|
InsertBLSToExecChange(change *ethpb.SignedBLSToExecutionChange)
|
||||||
|
MarkIncluded(change *ethpb.SignedBLSToExecutionChange) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool is a concrete implementation of PoolManager.
|
||||||
|
type Pool struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
pending doublylinkedlist.List[*ethpb.SignedBLSToExecutionChange]
|
||||||
|
m map[types.ValidatorIndex]*doublylinkedlist.Node[*ethpb.SignedBLSToExecutionChange]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPool returns an initialized pool.
|
||||||
|
func NewPool() *Pool {
|
||||||
|
return &Pool{
|
||||||
|
pending: doublylinkedlist.List[*ethpb.SignedBLSToExecutionChange]{},
|
||||||
|
m: make(map[types.ValidatorIndex]*doublylinkedlist.Node[*ethpb.SignedBLSToExecutionChange]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PendingBLSToExecChanges returns all objects from the pool.
|
||||||
|
func (p *Pool) PendingBLSToExecChanges() ([]*ethpb.SignedBLSToExecutionChange, error) {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
|
||||||
|
result := make([]*ethpb.SignedBLSToExecutionChange, p.pending.Len())
|
||||||
|
node := p.pending.First()
|
||||||
|
var err error
|
||||||
|
for i := 0; node != nil; i++ {
|
||||||
|
result[i], err = node.Value()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
node, err = node.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BLSToExecChangesForInclusion returns objects that are ready for inclusion at the given slot.
|
||||||
|
// This method will not return more than the block enforced MaxBlsToExecutionChanges.
|
||||||
|
func (p *Pool) BLSToExecChangesForInclusion() ([]*ethpb.SignedBLSToExecutionChange, error) {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
|
||||||
|
length := int(math.Min(float64(params.BeaconConfig().MaxBlsToExecutionChanges), float64(p.pending.Len())))
|
||||||
|
result := make([]*ethpb.SignedBLSToExecutionChange, length)
|
||||||
|
node := p.pending.First()
|
||||||
|
var err error
|
||||||
|
for i := 0; node != nil && i < length; i++ {
|
||||||
|
result[i], err = node.Value()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
node, err = node.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertBLSToExecChange inserts an object into the pool.
|
||||||
|
func (p *Pool) InsertBLSToExecChange(change *ethpb.SignedBLSToExecutionChange) {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
_, exists := p.m[change.Message.ValidatorIndex]
|
||||||
|
if exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pending.Append(doublylinkedlist.NewNode(change))
|
||||||
|
p.m[change.Message.ValidatorIndex] = p.pending.Last()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkIncluded is used when an object has been included in a beacon block. Every block seen by this
|
||||||
|
// listNode should call this method to include the object. This will remove the object from the pool.
|
||||||
|
func (p *Pool) MarkIncluded(change *ethpb.SignedBLSToExecutionChange) error {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
node := p.m[change.Message.ValidatorIndex]
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(p.m, change.Message.ValidatorIndex)
|
||||||
|
p.pending.Remove(node)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
258
beacon-chain/operations/blstoexec/pool_test.go
Normal file
258
beacon-chain/operations/blstoexec/pool_test.go
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
package blstoexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prysmaticlabs/prysm/v3/config/params"
|
||||||
|
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
||||||
|
eth "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||||
|
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||||
|
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPendingBLSToExecChanges(t *testing.T) {
|
||||||
|
t.Run("empty pool", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
changes, err := pool.PendingBLSToExecChanges()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(changes))
|
||||||
|
})
|
||||||
|
t.Run("non-empty pool", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
pool.InsertBLSToExecChange(ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
pool.InsertBLSToExecChange(ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
changes, err := pool.PendingBLSToExecChanges()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(changes))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBLSToExecChangesForInclusion(t *testing.T) {
|
||||||
|
t.Run("empty pool", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
for i := uint64(0); i < params.BeaconConfig().MaxBlsToExecutionChanges-1; i++ {
|
||||||
|
pool.InsertBLSToExecChange(ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(i),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
changes, err := pool.BLSToExecChangesForInclusion()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int(params.BeaconConfig().MaxBlsToExecutionChanges-1), len(changes))
|
||||||
|
})
|
||||||
|
t.Run("MaxBlsToExecutionChanges in pool", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
for i := uint64(0); i < params.BeaconConfig().MaxBlsToExecutionChanges; i++ {
|
||||||
|
pool.InsertBLSToExecChange(ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(i),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
changes, err := pool.BLSToExecChangesForInclusion()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int(params.BeaconConfig().MaxBlsToExecutionChanges), len(changes))
|
||||||
|
})
|
||||||
|
t.Run("more than MaxBlsToExecutionChanges in pool", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
for i := uint64(0); i < params.BeaconConfig().MaxBlsToExecutionChanges+1; i++ {
|
||||||
|
pool.InsertBLSToExecChange(ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(i),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
changes, err := pool.BLSToExecChangesForInclusion()
|
||||||
|
require.NoError(t, err)
|
||||||
|
// We want FIFO semantics, which means validator with index 16 shouldn't be returned
|
||||||
|
assert.Equal(t, int(params.BeaconConfig().MaxBlsToExecutionChanges), len(changes))
|
||||||
|
for _, ch := range changes {
|
||||||
|
assert.NotEqual(t, types.ValidatorIndex(16), ch.Message.ValidatorIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertBLSToExecChange(t *testing.T) {
|
||||||
|
t.Run("empty pool", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
change := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pool.InsertBLSToExecChange(change)
|
||||||
|
require.Equal(t, 1, pool.pending.Len())
|
||||||
|
require.Equal(t, 1, len(pool.m))
|
||||||
|
n, ok := pool.m[0]
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
v, err := n.Value()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.DeepEqual(t, change, v)
|
||||||
|
})
|
||||||
|
t.Run("item in pool", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
old := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
change := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pool.InsertBLSToExecChange(old)
|
||||||
|
pool.InsertBLSToExecChange(change)
|
||||||
|
require.Equal(t, 2, pool.pending.Len())
|
||||||
|
require.Equal(t, 2, len(pool.m))
|
||||||
|
n, ok := pool.m[0]
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
v, err := n.Value()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.DeepEqual(t, old, v)
|
||||||
|
n, ok = pool.m[1]
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
v, err = n.Value()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.DeepEqual(t, change, v)
|
||||||
|
})
|
||||||
|
t.Run("validator index already exists", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
old := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(0),
|
||||||
|
},
|
||||||
|
Signature: []byte("old"),
|
||||||
|
}
|
||||||
|
change := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(0),
|
||||||
|
},
|
||||||
|
Signature: []byte("change"),
|
||||||
|
}
|
||||||
|
pool.InsertBLSToExecChange(old)
|
||||||
|
pool.InsertBLSToExecChange(change)
|
||||||
|
assert.Equal(t, 1, pool.pending.Len())
|
||||||
|
require.Equal(t, 1, len(pool.m))
|
||||||
|
n, ok := pool.m[0]
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
v, err := n.Value()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.DeepEqual(t, old, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarkIncluded(t *testing.T) {
|
||||||
|
t.Run("one element in pool", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
change := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(0),
|
||||||
|
}}
|
||||||
|
pool.InsertBLSToExecChange(change)
|
||||||
|
require.NoError(t, pool.MarkIncluded(change))
|
||||||
|
assert.Equal(t, 0, pool.pending.Len())
|
||||||
|
_, ok := pool.m[0]
|
||||||
|
assert.Equal(t, false, ok)
|
||||||
|
})
|
||||||
|
t.Run("first of multiple elements", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
first := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(0),
|
||||||
|
}}
|
||||||
|
second := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(1),
|
||||||
|
}}
|
||||||
|
third := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(2),
|
||||||
|
}}
|
||||||
|
pool.InsertBLSToExecChange(first)
|
||||||
|
pool.InsertBLSToExecChange(second)
|
||||||
|
pool.InsertBLSToExecChange(third)
|
||||||
|
require.NoError(t, pool.MarkIncluded(first))
|
||||||
|
require.Equal(t, 2, pool.pending.Len())
|
||||||
|
_, ok := pool.m[0]
|
||||||
|
assert.Equal(t, false, ok)
|
||||||
|
})
|
||||||
|
t.Run("last of multiple elements", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
first := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(0),
|
||||||
|
}}
|
||||||
|
second := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(1),
|
||||||
|
}}
|
||||||
|
third := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(2),
|
||||||
|
}}
|
||||||
|
pool.InsertBLSToExecChange(first)
|
||||||
|
pool.InsertBLSToExecChange(second)
|
||||||
|
pool.InsertBLSToExecChange(third)
|
||||||
|
require.NoError(t, pool.MarkIncluded(third))
|
||||||
|
require.Equal(t, 2, pool.pending.Len())
|
||||||
|
_, ok := pool.m[2]
|
||||||
|
assert.Equal(t, false, ok)
|
||||||
|
})
|
||||||
|
t.Run("in the middle of multiple elements", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
first := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(0),
|
||||||
|
}}
|
||||||
|
second := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(1),
|
||||||
|
}}
|
||||||
|
third := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(2),
|
||||||
|
}}
|
||||||
|
pool.InsertBLSToExecChange(first)
|
||||||
|
pool.InsertBLSToExecChange(second)
|
||||||
|
pool.InsertBLSToExecChange(third)
|
||||||
|
require.NoError(t, pool.MarkIncluded(second))
|
||||||
|
require.Equal(t, 2, pool.pending.Len())
|
||||||
|
_, ok := pool.m[1]
|
||||||
|
assert.Equal(t, false, ok)
|
||||||
|
})
|
||||||
|
t.Run("not in pool", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
first := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(0),
|
||||||
|
}}
|
||||||
|
second := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(1),
|
||||||
|
}}
|
||||||
|
change := ð.SignedBLSToExecutionChange{
|
||||||
|
Message: ð.BLSToExecutionChange{
|
||||||
|
ValidatorIndex: types.ValidatorIndex(2),
|
||||||
|
}}
|
||||||
|
pool.InsertBLSToExecChange(first)
|
||||||
|
pool.InsertBLSToExecChange(second)
|
||||||
|
require.NoError(t, pool.MarkIncluded(change))
|
||||||
|
require.Equal(t, 2, pool.pending.Len())
|
||||||
|
_, ok := pool.m[0]
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
assert.NotNil(t, pool.m[0])
|
||||||
|
_, ok = pool.m[1]
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
assert.NotNil(t, pool.m[1])
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -104,6 +104,7 @@ func TestGetSpec(t *testing.T) {
|
|||||||
config.TerminalTotalDifficulty = "73"
|
config.TerminalTotalDifficulty = "73"
|
||||||
config.DefaultFeeRecipient = common.HexToAddress("DefaultFeeRecipient")
|
config.DefaultFeeRecipient = common.HexToAddress("DefaultFeeRecipient")
|
||||||
config.MaxWithdrawalsPerPayload = 74
|
config.MaxWithdrawalsPerPayload = 74
|
||||||
|
config.MaxBlsToExecutionChanges = 75
|
||||||
|
|
||||||
var dbp [4]byte
|
var dbp [4]byte
|
||||||
copy(dbp[:], []byte{'0', '0', '0', '1'})
|
copy(dbp[:], []byte{'0', '0', '0', '1'})
|
||||||
@@ -136,7 +137,7 @@ func TestGetSpec(t *testing.T) {
|
|||||||
resp, err := server.GetSpec(context.Background(), &emptypb.Empty{})
|
resp, err := server.GetSpec(context.Background(), &emptypb.Empty{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 103, len(resp.Data))
|
assert.Equal(t, 104, len(resp.Data))
|
||||||
for k, v := range resp.Data {
|
for k, v := range resp.Data {
|
||||||
switch k {
|
switch k {
|
||||||
case "CONFIG_NAME":
|
case "CONFIG_NAME":
|
||||||
@@ -355,9 +356,11 @@ func TestGetSpec(t *testing.T) {
|
|||||||
assert.Equal(t, "40", v)
|
assert.Equal(t, "40", v)
|
||||||
case "INTERVALS_PER_SLOT":
|
case "INTERVALS_PER_SLOT":
|
||||||
assert.Equal(t, "3", v)
|
assert.Equal(t, "3", v)
|
||||||
case "SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY":
|
case "MAX_BLS_TO_EXECUTION_CHANGES":
|
||||||
|
assert.Equal(t, "75", v)
|
||||||
case "MAX_WITHDRAWALS_PER_PAYLOAD":
|
case "MAX_WITHDRAWALS_PER_PAYLOAD":
|
||||||
assert.Equal(t, "74", v)
|
assert.Equal(t, "74", v)
|
||||||
|
case "SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY":
|
||||||
default:
|
default:
|
||||||
t.Errorf("Incorrect key: %s", k)
|
t.Errorf("Incorrect key: %s", k)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ const (
|
|||||||
SyncAggregateSyncCommitteeBytesLength = 64 // SyncAggregateSyncCommitteeBytesLength defines the length of sync committee bytes in a sync aggregate.
|
SyncAggregateSyncCommitteeBytesLength = 64 // SyncAggregateSyncCommitteeBytesLength defines the length of sync committee bytes in a sync aggregate.
|
||||||
MaxWithdrawalsPerPayload = 16 // MaxWithdrawalsPerPayloadLength defines the maximum number of withdrawals that can be included in a payload.
|
MaxWithdrawalsPerPayload = 16 // MaxWithdrawalsPerPayloadLength defines the maximum number of withdrawals that can be included in a payload.
|
||||||
WithdrawalQueueLimit = 1099511627776 // WithdrawalQueueLimit defines the maximum number of withdrawals queued in the state.
|
WithdrawalQueueLimit = 1099511627776 // WithdrawalQueueLimit defines the maximum number of withdrawals queued in the state.
|
||||||
|
ExecutionAddressLength = 20 // ExecutionAddressLength defines the length of an execution layer address.
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -94,12 +94,13 @@ type BeaconChainConfig struct {
|
|||||||
ProportionalSlashingMultiplier uint64 `yaml:"PROPORTIONAL_SLASHING_MULTIPLIER" spec:"true"` // ProportionalSlashingMultiplier is used as a multiplier on slashed penalties.
|
ProportionalSlashingMultiplier uint64 `yaml:"PROPORTIONAL_SLASHING_MULTIPLIER" spec:"true"` // ProportionalSlashingMultiplier is used as a multiplier on slashed penalties.
|
||||||
|
|
||||||
// Max operations per block constants.
|
// Max operations per block constants.
|
||||||
MaxProposerSlashings uint64 `yaml:"MAX_PROPOSER_SLASHINGS" spec:"true"` // MaxProposerSlashings defines the maximum number of slashings of proposers possible in a block.
|
MaxProposerSlashings uint64 `yaml:"MAX_PROPOSER_SLASHINGS" spec:"true"` // MaxProposerSlashings defines the maximum number of slashings of proposers possible in a block.
|
||||||
MaxAttesterSlashings uint64 `yaml:"MAX_ATTESTER_SLASHINGS" spec:"true"` // MaxAttesterSlashings defines the maximum number of casper FFG slashings possible in a block.
|
MaxAttesterSlashings uint64 `yaml:"MAX_ATTESTER_SLASHINGS" spec:"true"` // MaxAttesterSlashings defines the maximum number of casper FFG slashings possible in a block.
|
||||||
MaxAttestations uint64 `yaml:"MAX_ATTESTATIONS" spec:"true"` // MaxAttestations defines the maximum allowed attestations in a beacon block.
|
MaxAttestations uint64 `yaml:"MAX_ATTESTATIONS" spec:"true"` // MaxAttestations defines the maximum allowed attestations in a beacon block.
|
||||||
MaxDeposits uint64 `yaml:"MAX_DEPOSITS" spec:"true"` // MaxDeposits defines the maximum number of validator deposits in a block.
|
MaxDeposits uint64 `yaml:"MAX_DEPOSITS" spec:"true"` // MaxDeposits defines the maximum number of validator deposits in a block.
|
||||||
MaxVoluntaryExits uint64 `yaml:"MAX_VOLUNTARY_EXITS" spec:"true"` // MaxVoluntaryExits defines the maximum number of validator exits in a block.
|
MaxVoluntaryExits uint64 `yaml:"MAX_VOLUNTARY_EXITS" spec:"true"` // MaxVoluntaryExits defines the maximum number of validator exits in a block.
|
||||||
MaxWithdrawalsPerPayload uint64 `yaml:"MAX_WITHDRAWALS_PER_PAYLOAD" spec:"true"` // MaxWithdrawalsPerPayload defines the maximum number of withdrawals in a block.
|
MaxWithdrawalsPerPayload uint64 `yaml:"MAX_WITHDRAWALS_PER_PAYLOAD" spec:"true"` // MaxWithdrawalsPerPayload defines the maximum number of withdrawals in a block.
|
||||||
|
MaxBlsToExecutionChanges uint64 `yaml:"MAX_BLS_TO_EXECUTION_CHANGES" spec:"true"` // MaxBlsToExecutionChanges defines the maximum number of BLS-to-execution-change objects in a block.
|
||||||
|
|
||||||
// BLS domain values.
|
// BLS domain values.
|
||||||
DomainBeaconProposer [4]byte `yaml:"DOMAIN_BEACON_PROPOSER" spec:"true"` // DomainBeaconProposer defines the BLS signature domain for beacon proposal verification.
|
DomainBeaconProposer [4]byte `yaml:"DOMAIN_BEACON_PROPOSER" spec:"true"` // DomainBeaconProposer defines the BLS signature domain for beacon proposal verification.
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ var mainnetBeaconConfig = &BeaconChainConfig{
|
|||||||
MaxDeposits: 16,
|
MaxDeposits: 16,
|
||||||
MaxVoluntaryExits: 16,
|
MaxVoluntaryExits: 16,
|
||||||
MaxWithdrawalsPerPayload: 16,
|
MaxWithdrawalsPerPayload: 16,
|
||||||
|
MaxBlsToExecutionChanges: 16,
|
||||||
|
|
||||||
// BLS domain values.
|
// BLS domain values.
|
||||||
DomainBeaconProposer: bytesutil.Uint32ToBytes4(0x00000000),
|
DomainBeaconProposer: bytesutil.Uint32ToBytes4(0x00000000),
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ func MinimalSpecConfig() *BeaconChainConfig {
|
|||||||
minimalConfig.MaxDeposits = 16
|
minimalConfig.MaxDeposits = 16
|
||||||
minimalConfig.MaxVoluntaryExits = 16
|
minimalConfig.MaxVoluntaryExits = 16
|
||||||
minimalConfig.MaxWithdrawalsPerPayload = 4
|
minimalConfig.MaxWithdrawalsPerPayload = 4
|
||||||
|
minimalConfig.MaxBlsToExecutionChanges = 4
|
||||||
|
|
||||||
// Signature domains
|
// Signature domains
|
||||||
minimalConfig.DomainBeaconProposer = bytesutil.ToBytes4(bytesutil.Bytes4(0))
|
minimalConfig.DomainBeaconProposer = bytesutil.ToBytes4(bytesutil.Bytes4(0))
|
||||||
|
|||||||
19
container/doubly-linked-list/BUILD.bazel
Normal file
19
container/doubly-linked-list/BUILD.bazel
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["list.go"],
|
||||||
|
importpath = "github.com/prysmaticlabs/prysm/v3/container/doubly-linked-list",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["@com_github_pkg_errors//:go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["list_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//testing/assert:go_default_library",
|
||||||
|
"//testing/require:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
113
container/doubly-linked-list/list.go
Normal file
113
container/doubly-linked-list/list.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package doublylinkedlist
|
||||||
|
|
||||||
|
import "github.com/pkg/errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNextOnNil = errors.New("cannot get next node of nil node")
|
||||||
|
errPrevOnNil = errors.New("cannot get previous node of nil node")
|
||||||
|
errValueOnNil = errors.New("cannot get value of nil node")
|
||||||
|
)
|
||||||
|
|
||||||
|
// List is a generic doubly-linked list whose nodes can store any data type.
|
||||||
|
// It allows retrieving pointers to the first and last nodes.
|
||||||
|
type List[T any] struct {
|
||||||
|
first *Node[T]
|
||||||
|
last *Node[T]
|
||||||
|
len int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node is a generic data structure that contains three fields:
|
||||||
|
// references to the previous and to the next node in the list, and one data field.
|
||||||
|
type Node[T any] struct {
|
||||||
|
value T
|
||||||
|
prev *Node[T]
|
||||||
|
next *Node[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
// First gets the reference to the first node in the list.
|
||||||
|
func (l *List[T]) First() *Node[T] {
|
||||||
|
return l.first
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last gets the reference to the last node in the list.
|
||||||
|
func (l *List[T]) Last() *Node[T] {
|
||||||
|
return l.last
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len gets the length of the list.
|
||||||
|
func (l *List[T]) Len() int {
|
||||||
|
return l.len
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append adds the passed in node to the end of the list.
|
||||||
|
func (l *List[T]) Append(n *Node[T]) {
|
||||||
|
if l.first == nil {
|
||||||
|
l.first = n
|
||||||
|
} else {
|
||||||
|
n.prev = l.last
|
||||||
|
l.last.next = n
|
||||||
|
}
|
||||||
|
l.last = n
|
||||||
|
l.len++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the passed in node from the list.
|
||||||
|
func (l *List[T]) Remove(n *Node[T]) {
|
||||||
|
if n == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == l.First() {
|
||||||
|
if n == l.last {
|
||||||
|
l.first = nil
|
||||||
|
l.last = nil
|
||||||
|
} else {
|
||||||
|
n.next.prev = nil
|
||||||
|
l.first = n.next
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if n == l.last {
|
||||||
|
n.prev.next = nil
|
||||||
|
l.last = n.prev
|
||||||
|
} else {
|
||||||
|
if n.next == nil || n.prev == nil {
|
||||||
|
// The node is not in the list,
|
||||||
|
// otherwise it would be in the middle of two nodes.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.prev.next = n.next
|
||||||
|
n.next.prev = n.prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n = nil
|
||||||
|
l.len--
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNode creates a new node with the passed in value.
|
||||||
|
func NewNode[T any](value T) *Node[T] {
|
||||||
|
return &Node[T]{value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next gets the node's successor node.
|
||||||
|
func (n *Node[T]) Next() (*Node[T], error) {
|
||||||
|
if n == nil {
|
||||||
|
return nil, errNextOnNil
|
||||||
|
}
|
||||||
|
return n.next, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prev gets the node's predecessor node.
|
||||||
|
func (n *Node[T]) Prev() (*Node[T], error) {
|
||||||
|
if n == nil {
|
||||||
|
return nil, errPrevOnNil
|
||||||
|
}
|
||||||
|
return n.prev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value gets the value stored in the node.
|
||||||
|
func (n *Node[T]) Value() (T, error) {
|
||||||
|
if n == nil {
|
||||||
|
return *new(T), errValueOnNil
|
||||||
|
}
|
||||||
|
return n.value, nil
|
||||||
|
}
|
||||||
125
container/doubly-linked-list/list_test.go
Normal file
125
container/doubly-linked-list/list_test.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package doublylinkedlist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||||
|
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppend(t *testing.T) {
|
||||||
|
t.Run("empty list", func(t *testing.T) {
|
||||||
|
list := &List[int]{}
|
||||||
|
i := 1
|
||||||
|
list.Append(NewNode(i))
|
||||||
|
require.Equal(t, i, list.len)
|
||||||
|
require.NotNil(t, list.first)
|
||||||
|
assert.Equal(t, i, list.first.value)
|
||||||
|
require.NotNil(t, list.last)
|
||||||
|
assert.DeepEqual(t, i, list.last.value)
|
||||||
|
})
|
||||||
|
t.Run("one node in list", func(t *testing.T) {
|
||||||
|
list := &List[int]{}
|
||||||
|
old := 1
|
||||||
|
i := 2
|
||||||
|
list.Append(NewNode(old))
|
||||||
|
list.Append(NewNode(i))
|
||||||
|
require.Equal(t, 2, list.len)
|
||||||
|
require.NotNil(t, list.first)
|
||||||
|
assert.DeepEqual(t, old, list.first.value)
|
||||||
|
require.NotNil(t, list.first.next)
|
||||||
|
assert.Equal(t, i, list.first.next.value)
|
||||||
|
require.NotNil(t, list.last)
|
||||||
|
assert.DeepEqual(t, i, list.last.value)
|
||||||
|
require.NotNil(t, list.last.prev)
|
||||||
|
assert.Equal(t, old, list.last.prev.value)
|
||||||
|
})
|
||||||
|
t.Run("multiple nodes in list", func(t *testing.T) {
|
||||||
|
list := &List[int]{}
|
||||||
|
first := 1
|
||||||
|
second := 2
|
||||||
|
i := 3
|
||||||
|
list.Append(NewNode(first))
|
||||||
|
list.Append(NewNode(second))
|
||||||
|
list.Append(NewNode(i))
|
||||||
|
require.Equal(t, 3, list.len)
|
||||||
|
require.NotNil(t, list.first)
|
||||||
|
assert.DeepEqual(t, first, list.first.value)
|
||||||
|
require.NotNil(t, list.first.next)
|
||||||
|
assert.Equal(t, second, list.first.next.value)
|
||||||
|
assert.Equal(t, first, list.first.next.prev.value)
|
||||||
|
require.NotNil(t, list.last)
|
||||||
|
assert.DeepEqual(t, i, list.last.value)
|
||||||
|
require.NotNil(t, list.first.next.next)
|
||||||
|
assert.DeepEqual(t, i, list.first.next.next.value)
|
||||||
|
require.NotNil(t, list.last.prev)
|
||||||
|
assert.Equal(t, second, list.last.prev.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemove(t *testing.T) {
|
||||||
|
t.Run("one node in list", func(t *testing.T) {
|
||||||
|
list := &List[int]{}
|
||||||
|
n := NewNode(1)
|
||||||
|
list.Append(n)
|
||||||
|
list.Remove(n)
|
||||||
|
assert.Equal(t, 0, list.len)
|
||||||
|
assert.Equal(t, (*Node[int])(nil), list.first)
|
||||||
|
assert.Equal(t, (*Node[int])(nil), list.last)
|
||||||
|
})
|
||||||
|
t.Run("first of multiple nodes", func(t *testing.T) {
|
||||||
|
list := &List[int]{}
|
||||||
|
first := NewNode(1)
|
||||||
|
second := NewNode(2)
|
||||||
|
third := NewNode(3)
|
||||||
|
list.Append(first)
|
||||||
|
list.Append(second)
|
||||||
|
list.Append(third)
|
||||||
|
list.Remove(first)
|
||||||
|
require.Equal(t, 2, list.len)
|
||||||
|
require.NotNil(t, list.first)
|
||||||
|
assert.Equal(t, 2, list.first.value)
|
||||||
|
assert.Equal(t, (*Node[int])(nil), list.first.prev)
|
||||||
|
})
|
||||||
|
t.Run("last of multiple nodes", func(t *testing.T) {
|
||||||
|
list := &List[int]{}
|
||||||
|
first := NewNode(1)
|
||||||
|
second := NewNode(2)
|
||||||
|
third := NewNode(3)
|
||||||
|
list.Append(first)
|
||||||
|
list.Append(second)
|
||||||
|
list.Append(third)
|
||||||
|
list.Remove(third)
|
||||||
|
require.Equal(t, 2, list.len)
|
||||||
|
require.NotNil(t, list.last)
|
||||||
|
assert.Equal(t, 2, list.last.value)
|
||||||
|
assert.Equal(t, (*Node[int])(nil), list.last.next)
|
||||||
|
})
|
||||||
|
t.Run("in the middle of multiple nodes", func(t *testing.T) {
|
||||||
|
list := &List[int]{}
|
||||||
|
first := NewNode(1)
|
||||||
|
second := NewNode(2)
|
||||||
|
third := NewNode(3)
|
||||||
|
list.Append(first)
|
||||||
|
list.Append(second)
|
||||||
|
list.Append(third)
|
||||||
|
list.Remove(second)
|
||||||
|
require.Equal(t, 2, list.len)
|
||||||
|
require.NotNil(t, list.first)
|
||||||
|
require.NotNil(t, list.last)
|
||||||
|
assert.Equal(t, 1, list.first.value)
|
||||||
|
assert.Equal(t, 3, list.last.value)
|
||||||
|
assert.Equal(t, 3, list.first.next.value)
|
||||||
|
assert.Equal(t, 1, list.last.prev.value)
|
||||||
|
})
|
||||||
|
t.Run("not in list", func(t *testing.T) {
|
||||||
|
list := &List[int]{}
|
||||||
|
first := NewNode(1)
|
||||||
|
second := NewNode(2)
|
||||||
|
n := NewNode(3)
|
||||||
|
list.Append(first)
|
||||||
|
list.Append(second)
|
||||||
|
list.Remove(n)
|
||||||
|
require.Equal(t, 2, list.len)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user