prysm-pulse/beacon-chain/sync/rpc_blob_sidecars_by_range_test.go
kasey 34593d34d4
allow blob by root within da period (#13757)
Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2024-03-18 03:15:17 +00:00

352 lines
9.8 KiB
Go

package sync
import (
"testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
types "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
func (c *blobsTestCase) defaultOldestSlotByRange(t *testing.T) types.Slot {
currentEpoch := slots.ToEpoch(c.chain.CurrentSlot())
oldestEpoch := currentEpoch - params.BeaconConfig().MinEpochsForBlobsSidecarsRequest
if oldestEpoch < params.BeaconConfig().DenebForkEpoch {
oldestEpoch = params.BeaconConfig().DenebForkEpoch
}
oldestSlot, err := slots.EpochStart(oldestEpoch)
require.NoError(t, err)
return oldestSlot
}
func blobRangeRequestFromSidecars(scs []blocks.ROBlob) interface{} {
maxBlobs := fieldparams.MaxBlobsPerBlock
count := uint64(len(scs) / maxBlobs)
return &ethpb.BlobSidecarsByRangeRequest{
StartSlot: scs[0].Slot(),
Count: count,
}
}
func (c *blobsTestCase) filterExpectedByRange(t *testing.T, scs []blocks.ROBlob, req interface{}) []*expectedBlobChunk {
var expect []*expectedBlobChunk
blockOffset := 0
lastRoot := scs[0].BlockRoot()
rreq, ok := req.(*ethpb.BlobSidecarsByRangeRequest)
require.Equal(t, true, ok)
var writes uint64
for i := range scs {
sc := scs[i]
root := sc.BlockRoot()
if root != lastRoot {
blockOffset += 1
}
lastRoot = root
if sc.Slot() < c.oldestSlot(t) {
continue
}
if sc.Slot() < rreq.StartSlot || sc.Slot() > rreq.StartSlot+types.Slot(rreq.Count)-1 {
continue
}
if writes == params.BeaconConfig().MaxRequestBlobSidecars {
continue
}
expect = append(expect, &expectedBlobChunk{
sidecar: &sc,
code: responseCodeSuccess,
message: "",
})
writes += 1
}
return expect
}
func (c *blobsTestCase) runTestBlobSidecarsByRange(t *testing.T) {
if c.serverHandle == nil {
c.serverHandle = func(s *Service) rpcHandler { return s.blobSidecarsByRangeRPCHandler }
}
if c.defineExpected == nil {
c.defineExpected = c.filterExpectedByRange
}
if c.requestFromSidecars == nil {
c.requestFromSidecars = blobRangeRequestFromSidecars
}
if c.topic == "" {
c.topic = p2p.RPCBlobSidecarsByRangeTopicV1
}
if c.oldestSlot == nil {
c.oldestSlot = c.defaultOldestSlotByRange
}
if c.streamReader == nil {
c.streamReader = defaultExpectedRequirer
}
c.run(t)
}
func TestBlobByRangeOK(t *testing.T) {
origNC := params.BeaconConfig()
// restore network config after test completes
defer func() {
params.OverrideBeaconConfig(origNC)
}()
// set MaxRequestBlobSidecars to a low-ish value so the test doesn't timeout.
nc := params.BeaconConfig().Copy()
nc.MaxRequestBlobSidecars = 100
params.OverrideBeaconConfig(nc)
cases := []*blobsTestCase{
{
name: "beginning of window + 10",
nblocks: 10,
},
{
name: "10 slots before window, 10 slots after, count = 20",
nblocks: 10,
requestFromSidecars: func(scs []blocks.ROBlob) interface{} {
return &ethpb.BlobSidecarsByRangeRequest{
StartSlot: scs[0].Slot() - 10,
Count: 20,
}
},
},
{
name: "request before window, empty response",
nblocks: 10,
requestFromSidecars: func(scs []blocks.ROBlob) interface{} {
return &ethpb.BlobSidecarsByRangeRequest{
StartSlot: scs[0].Slot() - 10,
Count: 10,
}
},
total: func() *int { x := 0; return &x }(),
},
{
name: "10 blocks * 4 blobs = 40",
nblocks: 10,
requestFromSidecars: func(scs []blocks.ROBlob) interface{} {
return &ethpb.BlobSidecarsByRangeRequest{
StartSlot: scs[0].Slot() - 10,
Count: 20,
}
},
total: func() *int { x := fieldparams.MaxBlobsPerBlock * 10; return &x }(), // 10 blocks * 4 blobs = 40
},
{
name: "when request count > MAX_REQUEST_BLOCKS_DENEB, MAX_REQUEST_BLOBS_SIDECARS sidecars in response",
nblocks: int(params.BeaconConfig().MaxRequestBlocksDeneb) + 10,
requestFromSidecars: func(scs []blocks.ROBlob) interface{} {
return &ethpb.BlobSidecarsByRangeRequest{
StartSlot: scs[0].Slot(),
Count: params.BeaconConfig().MaxRequestBlocksDeneb + 1,
}
},
total: func() *int { x := int(params.BeaconConfig().MaxRequestBlobSidecars); return &x }(),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
c.runTestBlobSidecarsByRange(t)
})
}
}
func TestBlobsByRangeValidation(t *testing.T) {
cfg := params.BeaconConfig()
repositionFutureEpochs(cfg)
undo, err := params.SetActiveWithUndo(cfg)
require.NoError(t, err)
defer func() {
require.NoError(t, undo())
}()
denebSlot, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch)
require.NoError(t, err)
minReqEpochs := params.BeaconConfig().MinEpochsForBlobsSidecarsRequest
minReqSlots, err := slots.EpochStart(minReqEpochs)
require.NoError(t, err)
// spec criteria for mix,max bound checking
/*
Clients MUST keep a record of signed blobs sidecars seen on the epoch range
[max(current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, DENEB_FORK_EPOCH), current_epoch]
where current_epoch is defined by the current wall-clock time,
and clients MUST support serving requests of blobs on this range.
*/
defaultCurrent := denebSlot + 100 + minReqSlots
defaultMinStart, err := BlobRPCMinValidSlot(defaultCurrent)
require.NoError(t, err)
cases := []struct {
name string
current types.Slot
req *ethpb.BlobSidecarsByRangeRequest
// chain := defaultMockChain(t)
start types.Slot
end types.Slot
batch uint64
err error
}{
{
name: "start at current",
current: denebSlot + 100,
req: &ethpb.BlobSidecarsByRangeRequest{
StartSlot: denebSlot + 100,
Count: 10,
},
start: denebSlot + 100,
end: denebSlot + 100,
batch: 10,
},
{
name: "start after current",
current: denebSlot,
req: &ethpb.BlobSidecarsByRangeRequest{
StartSlot: denebSlot + 100,
Count: 10,
},
start: denebSlot,
end: denebSlot,
batch: 0,
},
{
name: "start before current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS",
current: defaultCurrent,
req: &ethpb.BlobSidecarsByRangeRequest{
StartSlot: defaultMinStart - 100,
Count: 10,
},
start: defaultMinStart,
end: defaultMinStart,
batch: 10,
},
{
name: "start before current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS - end still valid",
current: defaultCurrent,
req: &ethpb.BlobSidecarsByRangeRequest{
StartSlot: defaultMinStart - 10,
Count: 20,
},
start: defaultMinStart,
end: defaultMinStart + 9,
batch: blobBatchLimit(),
},
{
name: "count > MAX_REQUEST_BLOB_SIDECARS",
current: defaultCurrent,
req: &ethpb.BlobSidecarsByRangeRequest{
StartSlot: defaultMinStart - 10,
Count: 1000,
},
start: defaultMinStart,
end: defaultMinStart - 10 + 999,
// a large count is ok, we just limit the amount of actual responses
batch: blobBatchLimit(),
},
{
name: "start + count > current",
current: defaultCurrent,
req: &ethpb.BlobSidecarsByRangeRequest{
StartSlot: defaultCurrent + 100,
Count: 100,
},
start: defaultCurrent,
end: defaultCurrent,
batch: 0,
},
{
name: "start before deneb",
current: defaultCurrent - minReqSlots + 100,
req: &ethpb.BlobSidecarsByRangeRequest{
StartSlot: denebSlot - 10,
Count: 100,
},
start: denebSlot,
end: denebSlot + 89,
batch: blobBatchLimit(),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
rp, err := validateBlobsByRange(c.req, c.current)
if c.err != nil {
require.ErrorIs(t, err, c.err)
return
} else {
require.NoError(t, err)
}
require.Equal(t, c.start, rp.start)
require.Equal(t, c.end, rp.end)
require.Equal(t, c.batch, rp.size)
})
}
}
func TestBlobRPCMinValidSlot(t *testing.T) {
denebSlot, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch)
require.NoError(t, err)
cases := []struct {
name string
current func(t *testing.T) types.Slot
expected types.Slot
err error
}{
{
name: "before deneb",
current: func(t *testing.T) types.Slot {
st, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch - 1)
// note: we no longer need to deal with deneb fork epoch being far future
require.NoError(t, err)
return st
},
expected: denebSlot,
},
{
name: "equal to deneb",
current: func(t *testing.T) types.Slot {
st, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch)
// note: we no longer need to deal with deneb fork epoch being far future
require.NoError(t, err)
return st
},
expected: denebSlot,
},
{
name: "after deneb, before expiry starts",
current: func(t *testing.T) types.Slot {
st, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch + params.BeaconConfig().MinEpochsForBlobsSidecarsRequest)
// note: we no longer need to deal with deneb fork epoch being far future
require.NoError(t, err)
return st
},
expected: denebSlot,
},
{
name: "expiry starts one epoch after deneb + MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS",
current: func(t *testing.T) types.Slot {
st, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch + params.BeaconConfig().MinEpochsForBlobsSidecarsRequest + 1)
// note: we no longer need to deal with deneb fork epoch being far future
require.NoError(t, err)
return st
},
expected: denebSlot + params.BeaconConfig().SlotsPerEpoch,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
current := c.current(t)
got, err := BlobRPCMinValidSlot(current)
if c.err != nil {
require.ErrorIs(t, err, c.err)
return
}
require.NoError(t, err)
require.Equal(t, c.expected, got)
})
}
}