Files
prysm/beacon-chain/verification/data_column_test.go
james-prysm e1b98a4ca1 optimize get blobs (#15902)
* init

* reverting some functions

* rolling back a change and fixing linting

* wip

* wip

* fixing test

* breaking up proofs and cells for cleaner code

* fixing test and type

* fixing safe conversion

* fixing test

* fixing more tests

* fixing even more tests

* fix the 0 indices option

* adding a test for coverage

* small test update

* changelog

* radek's suggestions

* Update beacon-chain/core/peerdas/validator.go

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>

* addressing comments on kzg package

* addressing suggestions for reconstruction

* more manu feedback items

* removing unneeded files

* removing unneeded setter

---------

Co-authored-by: james-prysm <jhe@offchainlabs.com>
Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>
2025-11-12 19:53:39 +00:00

934 lines
26 KiB
Go

package verification
import (
"reflect"
"sync"
"testing"
"time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
forkchoicetypes "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/types"
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/pkg/errors"
)
func GenerateTestDataColumns(t *testing.T, parent [fieldparams.RootLength]byte, slot primitives.Slot, blobCount int) []blocks.RODataColumn {
roBlock, roBlobs := util.GenerateTestDenebBlockWithSidecar(t, parent, slot, blobCount)
blobs := make([]kzg.Blob, 0, len(roBlobs))
for i := range roBlobs {
blobs = append(blobs, kzg.Blob(roBlobs[i].Blob))
}
cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs)
roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock))
require.NoError(t, err)
return roDataColumnSidecars
}
func TestColumnSatisfyRequirement(t *testing.T) {
const (
columnSlot = 1
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
intializer := Initializer{}
v := intializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
require.Equal(t, false, v.results.executed(RequireValidProposerSignature))
v.SatisfyRequirement(RequireValidProposerSignature)
require.Equal(t, true, v.results.executed(RequireValidProposerSignature))
}
func TestValid(t *testing.T) {
var initializer Initializer
t.Run("one invalid column", func(t *testing.T) {
columns := GenerateTestDataColumns(t, [fieldparams.RootLength]byte{}, 1, 1)
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.ValidFields()
require.NotNil(t, err)
require.NotNil(t, verifier.results.result(RequireValidFields))
})
t.Run("nominal", func(t *testing.T) {
const maxBlobsPerBlock = 2
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.FuluForkEpoch = 0
cfg.BlobSchedule = []params.BlobScheduleEntry{{Epoch: 0, MaxBlobsPerBlock: maxBlobsPerBlock}}
params.OverrideBeaconConfig(cfg)
columns := GenerateTestDataColumns(t, [fieldparams.RootLength]byte{}, 1, 1)
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.ValidFields()
require.NoError(t, err)
require.IsNil(t, verifier.results.result(RequireValidFields))
err = verifier.ValidFields()
require.NoError(t, err)
})
}
func TestCorrectSubnet(t *testing.T) {
const dataColumnSidecarSubTopic = "/data_column_sidecar_%d/"
var initializer Initializer
t.Run("lengths mismatch", func(t *testing.T) {
columns := GenerateTestDataColumns(t, [fieldparams.RootLength]byte{}, 1, 1)
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.CorrectSubnet(dataColumnSidecarSubTopic, []string{})
require.ErrorIs(t, err, errBadTopicLength)
require.NotNil(t, verifier.results.result(RequireCorrectSubnet))
})
t.Run("wrong topic", func(t *testing.T) {
columns := GenerateTestDataColumns(t, [fieldparams.RootLength]byte{}, 1, 1)
verifier := initializer.NewDataColumnsVerifier(columns[:2], GossipDataColumnSidecarRequirements)
err := verifier.CorrectSubnet(
dataColumnSidecarSubTopic,
[]string{
"/eth2/9dc47cc6/data_column_sidecar_1/ssz_snappy",
"/eth2/9dc47cc6/data_column_sidecar_0/ssz_snappy",
})
require.ErrorIs(t, err, errBadTopic)
require.NotNil(t, verifier.results.result(RequireCorrectSubnet))
})
t.Run("nominal", func(t *testing.T) {
subnets := []string{
"/eth2/9dc47cc6/data_column_sidecar_0/ssz_snappy",
"/eth2/9dc47cc6/data_column_sidecar_1",
}
columns := GenerateTestDataColumns(t, [fieldparams.RootLength]byte{}, 1, 1)
verifier := initializer.NewDataColumnsVerifier(columns[:2], GossipDataColumnSidecarRequirements)
err := verifier.CorrectSubnet(dataColumnSidecarSubTopic, subnets)
require.NoError(t, err)
require.IsNil(t, verifier.results.result(RequireCorrectSubnet))
err = verifier.CorrectSubnet(dataColumnSidecarSubTopic, subnets)
require.NoError(t, err)
})
}
func TestNotFromFutureSlot(t *testing.T) {
maximumGossipClockDisparity := params.BeaconConfig().MaximumGossipClockDisparityDuration()
testCases := []struct {
name string
currentSlot, columnSlot primitives.Slot
timeBeforeCurrentSlot time.Duration
isError bool
}{
{
name: "column slot == current slot",
currentSlot: 42,
columnSlot: 42,
timeBeforeCurrentSlot: 0,
isError: false,
},
{
name: "within maximum gossip clock disparity",
currentSlot: 42,
columnSlot: 42,
timeBeforeCurrentSlot: maximumGossipClockDisparity / 2,
isError: false,
},
{
name: "outside maximum gossip clock disparity",
currentSlot: 42,
columnSlot: 42,
timeBeforeCurrentSlot: maximumGossipClockDisparity * 2,
isError: true,
},
{
name: "too far in the future",
currentSlot: 10,
columnSlot: 42,
timeBeforeCurrentSlot: 0,
isError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const blobCount = 1
now := time.Now()
secondsPerSlot := time.Duration(params.BeaconConfig().SecondsPerSlot)
genesis := now.Add(-time.Duration(tc.currentSlot) * secondsPerSlot * time.Second)
clock := startup.NewClock(
genesis,
[fieldparams.RootLength]byte{},
startup.WithNower(func() time.Time {
return now.Add(-tc.timeBeforeCurrentSlot)
}),
)
parentRoot := [fieldparams.RootLength]byte{}
initializer := Initializer{shared: &sharedResources{clock: clock}}
columns := GenerateTestDataColumns(t, parentRoot, tc.columnSlot, blobCount)
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.NotFromFutureSlot()
require.Equal(t, true, verifier.results.executed(RequireNotFromFutureSlot))
if tc.isError {
require.ErrorIs(t, err, errFromFutureSlot)
require.NotNil(t, verifier.results.result(RequireNotFromFutureSlot))
return
}
require.NoError(t, err)
require.NoError(t, verifier.results.result(RequireNotFromFutureSlot))
err = verifier.NotFromFutureSlot()
require.NoError(t, err)
})
}
}
func TestColumnSlotAboveFinalized(t *testing.T) {
testCases := []struct {
name string
finalizedSlot, columnSlot primitives.Slot
isErr bool
}{
{
name: "finalized epoch < column epoch",
finalizedSlot: 10,
columnSlot: 96,
isErr: false,
},
{
name: "finalized slot < column slot (same epoch)",
finalizedSlot: 32,
columnSlot: 33,
isErr: false,
},
{
name: "finalized slot == column slot",
finalizedSlot: 64,
columnSlot: 64,
isErr: true,
},
{
name: "finalized epoch > column epoch",
finalizedSlot: 32,
columnSlot: 31,
isErr: true,
},
}
for _, tc := range testCases {
const blobCount = 1
t.Run(tc.name, func(t *testing.T) {
finalizedCheckpoint := func() *forkchoicetypes.Checkpoint {
return &forkchoicetypes.Checkpoint{
Epoch: slots.ToEpoch(tc.finalizedSlot),
Root: [fieldparams.RootLength]byte{},
}
}
parentRoot := [fieldparams.RootLength]byte{}
initializer := &Initializer{shared: &sharedResources{
fc: &mockForkchoicer{FinalizedCheckpointCB: finalizedCheckpoint},
}}
columns := GenerateTestDataColumns(t, parentRoot, tc.columnSlot, blobCount)
v := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := v.SlotAboveFinalized()
require.Equal(t, true, v.results.executed(RequireSlotAboveFinalized))
if tc.isErr {
require.ErrorIs(t, err, errSlotNotAfterFinalized)
require.NotNil(t, v.results.result(RequireSlotAboveFinalized))
return
}
require.NoError(t, err)
require.NoError(t, v.results.result(RequireSlotAboveFinalized))
err = v.SlotAboveFinalized()
require.NoError(t, err)
})
}
}
func TestValidProposerSignature(t *testing.T) {
const (
columnSlot = 0
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
validator := &ethpb.Validator{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
firstColumn := columns[0]
// The signature data does not depend on the data column itself, so we can use the first one.
expectedSignatureData := columnToSignatureData(firstColumn)
testCases := []struct {
isError bool
vscbShouldError bool
svcbReturn bool
stateByRooter StateByRooter
vscbError error
svcbError error
name string
}{
{
name: "cache hit - success",
svcbReturn: true,
svcbError: nil,
vscbShouldError: true,
vscbError: nil,
stateByRooter: &mockStateByRooter{sbr: sbrErrorIfCalled(t)},
isError: false,
},
{
name: "cache hit - error",
svcbReturn: true,
svcbError: errors.New("derp"),
vscbShouldError: true,
vscbError: nil,
stateByRooter: &mockStateByRooter{sbr: sbrErrorIfCalled(t)},
isError: true,
},
{
name: "cache miss - success",
svcbReturn: false,
svcbError: nil,
vscbShouldError: false,
vscbError: nil,
stateByRooter: sbrForValOverrideWithT(t, firstColumn.ProposerIndex(), validator),
isError: false,
},
{
name: "cache miss - state not found",
svcbReturn: false,
svcbError: nil,
vscbShouldError: false,
vscbError: nil,
stateByRooter: sbrNotFound(t, expectedSignatureData.Parent),
isError: true,
},
{
name: "cache miss - signature failure",
svcbReturn: false,
svcbError: nil,
vscbShouldError: false,
vscbError: errors.New("signature, not so good!"),
stateByRooter: sbrForValOverrideWithT(t, firstColumn.ProposerIndex(), validator),
isError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
signatureCache := &mockSignatureCache{
svcb: func(signatureData signatureData) (bool, error) {
if signatureData != expectedSignatureData {
t.Error("Did not see expected SignatureData")
}
return tc.svcbReturn, tc.svcbError
},
vscb: func(signatureData signatureData, _ validatorAtIndexer) (err error) {
if tc.vscbShouldError {
t.Error("VerifySignature should not be called if the result is cached")
return nil
}
if expectedSignatureData != signatureData {
t.Error("unexpected signature data")
}
return tc.vscbError
},
}
initializer := Initializer{
shared: &sharedResources{
sc: signatureCache,
sr: tc.stateByRooter,
hsp: &mockHeadStateProvider{},
fc: &mockForkchoicer{
TargetRootForEpochCB: fcReturnsTargetRoot([fieldparams.RootLength]byte{}),
},
},
}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.ValidProposerSignature(t.Context())
require.Equal(t, true, verifier.results.executed(RequireValidProposerSignature))
if tc.isError {
require.NotNil(t, err)
require.NotNil(t, verifier.results.result(RequireValidProposerSignature))
return
}
require.NoError(t, err)
require.NoError(t, verifier.results.result(RequireValidProposerSignature))
err = verifier.ValidProposerSignature(t.Context())
require.NoError(t, err)
})
}
}
func TestDataColumnsSidecarParentSeen(t *testing.T) {
const (
columnSlot = 0
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
firstColumn := columns[0]
fcHas := &mockForkchoicer{
HasNodeCB: func(parent [fieldparams.RootLength]byte) bool {
if parent != firstColumn.ParentRoot() {
t.Error("forkchoice.HasNode called with unexpected parent root")
}
return true
},
}
fcLacks := &mockForkchoicer{
HasNodeCB: func(parent [fieldparams.RootLength]byte) bool {
if parent != firstColumn.ParentRoot() {
t.Error("forkchoice.HasNode called with unexpected parent root")
}
return false
},
}
testCases := []struct {
name string
forkChoicer Forkchoicer
parentSeen func([fieldparams.RootLength]byte) bool
isError bool
}{
{
name: "happy path",
forkChoicer: fcHas,
parentSeen: nil,
isError: false,
},
{
name: "HasNode false, no badParent cb, expected error",
forkChoicer: fcLacks,
parentSeen: nil,
isError: true,
},
{
name: "HasNode false, badParent true",
forkChoicer: fcLacks,
parentSeen: badParentCb(t, firstColumn.ParentRoot(), true),
isError: false,
},
{
name: "HasNode false, badParent false",
forkChoicer: fcLacks,
parentSeen: badParentCb(t, firstColumn.ParentRoot(), false),
isError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
initializer := Initializer{shared: &sharedResources{fc: tc.forkChoicer}}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.SidecarParentSeen(tc.parentSeen)
require.Equal(t, true, verifier.results.executed(RequireSidecarParentSeen))
if tc.isError {
require.ErrorIs(t, err, errSidecarParentNotSeen)
require.NotNil(t, verifier.results.result(RequireSidecarParentSeen))
return
}
require.NoError(t, err)
require.NoError(t, verifier.results.result(RequireSidecarParentSeen))
err = verifier.SidecarParentSeen(tc.parentSeen)
require.NoError(t, err)
})
}
}
func TestDataColumnsSidecarParentValid(t *testing.T) {
testCases := []struct {
name string
badParentCbReturn bool
isError bool
}{
{
name: "parent valid",
badParentCbReturn: false,
isError: false,
},
{
name: "parent not valid",
badParentCbReturn: true,
isError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const (
columnSlot = 0
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
firstColumn := columns[0]
initializer := Initializer{shared: &sharedResources{}}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.SidecarParentValid(badParentCb(t, firstColumn.ParentRoot(), tc.badParentCbReturn))
require.Equal(t, true, verifier.results.executed(RequireSidecarParentValid))
if tc.isError {
require.ErrorIs(t, err, errSidecarParentInvalid)
require.NotNil(t, verifier.results.result(RequireSidecarParentValid))
return
}
require.NoError(t, err)
require.NoError(t, verifier.results.result(RequireSidecarParentValid))
err = verifier.SidecarParentValid(badParentCb(t, firstColumn.ParentRoot(), tc.badParentCbReturn))
require.NoError(t, err)
})
}
}
func TestColumnSidecarParentSlotLower(t *testing.T) {
columns := GenerateTestDataColumns(t, [32]byte{}, 1, 1)
firstColumn := columns[0]
cases := []struct {
name string
forkChoiceSlot primitives.Slot
forkChoiceError, err error
errCheckValue bool
}{
{
name: "Not in forkchoice",
forkChoiceError: errors.New("not in forkchoice"),
err: errSlotNotAfterParent,
},
{
name: "In forkchoice, slot lower",
forkChoiceSlot: firstColumn.Slot() - 1,
},
{
name: "In forkchoice, slot equal",
forkChoiceSlot: firstColumn.Slot(),
err: errSlotNotAfterParent,
errCheckValue: true,
},
{
name: "In forkchoice, slot higher",
forkChoiceSlot: firstColumn.Slot() + 1,
err: errSlotNotAfterParent,
errCheckValue: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
initializer := Initializer{
shared: &sharedResources{fc: &mockForkchoicer{
SlotCB: func(r [32]byte) (primitives.Slot, error) {
if firstColumn.ParentRoot() != r {
t.Error("forkchoice.Slot called with unexpected parent root")
}
return c.forkChoiceSlot, c.forkChoiceError
},
}},
}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.SidecarParentSlotLower()
require.Equal(t, true, verifier.results.executed(RequireSidecarParentSlotLower))
if c.err == nil {
require.NoError(t, err)
require.NoError(t, verifier.results.result(RequireSidecarParentSlotLower))
err = verifier.SidecarParentSlotLower()
require.NoError(t, err)
return
}
require.NotNil(t, err)
require.NotNil(t, verifier.results.result(RequireSidecarParentSlotLower))
if c.errCheckValue {
require.ErrorIs(t, err, c.err)
}
})
}
}
func TestDataColumnsSidecarDescendsFromFinalized(t *testing.T) {
testCases := []struct {
name string
hasNodeCBReturn bool
isError bool
}{
{
name: "Not canonical",
hasNodeCBReturn: false,
isError: true,
},
{
name: "Canonical",
hasNodeCBReturn: true,
isError: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const (
columnSlot = 0
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
firstColumn := columns[0]
initializer := Initializer{
shared: &sharedResources{
fc: &mockForkchoicer{
HasNodeCB: func(r [fieldparams.RootLength]byte) bool {
if firstColumn.ParentRoot() != r {
t.Error("forkchoice.Slot called with unexpected parent root")
}
return tc.hasNodeCBReturn
},
},
},
}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.SidecarDescendsFromFinalized()
require.Equal(t, true, verifier.results.executed(RequireSidecarDescendsFromFinalized))
if tc.isError {
require.ErrorIs(t, err, errSidecarNotFinalizedDescendent)
require.NotNil(t, verifier.results.result(RequireSidecarDescendsFromFinalized))
return
}
require.NoError(t, err)
require.NoError(t, verifier.results.result(RequireSidecarDescendsFromFinalized))
err = verifier.SidecarDescendsFromFinalized()
require.NoError(t, err)
})
}
}
func TestDataColumnsSidecarInclusionProven(t *testing.T) {
testCases := []struct {
name string
alterate bool
isError bool
}{
{
name: "Inclusion proven",
alterate: false,
isError: false,
},
{
name: "Inclusion not proven",
alterate: true,
isError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const (
columnSlot = 0
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
if tc.alterate {
firstColumn := columns[0]
byte0 := firstColumn.SignedBlockHeader.Header.BodyRoot[0]
firstColumn.SignedBlockHeader.Header.BodyRoot[0] = byte0 ^ 255
}
initializer := Initializer{
shared: &sharedResources{ic: newInclusionProofCache(1)},
}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.SidecarInclusionProven()
require.Equal(t, true, verifier.results.executed(RequireSidecarInclusionProven))
if tc.isError {
require.ErrorIs(t, err, ErrSidecarInclusionProofInvalid)
require.NotNil(t, verifier.results.result(RequireSidecarInclusionProven))
return
}
require.NoError(t, err)
require.NoError(t, verifier.results.result(RequireSidecarInclusionProven))
err = verifier.SidecarInclusionProven()
require.NoError(t, err)
})
}
}
func TestDataColumnsSidecarKzgProofVerified(t *testing.T) {
testCases := []struct {
isError bool
verifyDataColumnsCommitmentError error
name string
}{
{
name: "KZG proof verified",
verifyDataColumnsCommitmentError: nil,
isError: false,
},
{
name: "KZG proof not verified",
verifyDataColumnsCommitmentError: errors.New("KZG proof error"),
isError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const (
columnSlot = 0
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
firstColumn := columns[0]
verifyDataColumnsCommitment := func(roDataColumns []blocks.RODataColumn) error {
for _, roDataColumn := range roDataColumns {
require.Equal(t, true, reflect.DeepEqual(firstColumn.KzgCommitments, roDataColumn.KzgCommitments))
}
return tc.verifyDataColumnsCommitmentError
}
verifier := &RODataColumnsVerifier{
results: newResults(),
dataColumns: columns,
verifyDataColumnsCommitment: verifyDataColumnsCommitment,
}
err := verifier.SidecarKzgProofVerified()
require.Equal(t, true, verifier.results.executed(RequireSidecarKzgProofVerified))
if tc.isError {
require.NotNil(t, err)
require.NotNil(t, verifier.results.result(RequireSidecarKzgProofVerified))
return
}
require.NoError(t, err)
require.NoError(t, verifier.results.result(RequireSidecarKzgProofVerified))
err = verifier.SidecarKzgProofVerified()
require.NoError(t, err)
})
}
}
func TestDataColumnsSidecarProposerExpected(t *testing.T) {
const (
columnSlot = 1
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
firstColumn := columns[0]
ctx := t.Context()
testCases := []struct {
name string
stateByRooter StateByRooter
proposerCache proposerCache
columns []blocks.RODataColumn
error string
}{
{
name: "Cached, matches",
stateByRooter: nil,
proposerCache: &mockProposerCache{
ProposerCB: pcReturnsIdx(firstColumn.ProposerIndex()),
},
columns: columns,
},
{
name: "Cached, does not match",
stateByRooter: nil,
proposerCache: &mockProposerCache{
ProposerCB: pcReturnsIdx(firstColumn.ProposerIndex() + 1),
},
columns: columns,
error: errSidecarUnexpectedProposer.Error(),
},
{
name: "Not cached, state lookup failure",
stateByRooter: sbrNotFound(t, firstColumn.ParentRoot()),
proposerCache: &mockProposerCache{
ProposerCB: pcReturnsNotFound(),
},
columns: columns,
error: "verifying state",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
initializer := Initializer{
shared: &sharedResources{
sr: tc.stateByRooter,
pc: tc.proposerCache,
hsp: &mockHeadStateProvider{},
fc: &mockForkchoicer{
TargetRootForEpochCB: fcReturnsTargetRoot([fieldparams.RootLength]byte{}),
},
},
}
verifier := initializer.NewDataColumnsVerifier(tc.columns, GossipDataColumnSidecarRequirements)
var wg sync.WaitGroup
var err1, err2 error
wg.Go(func() {
err1 = verifier.SidecarProposerExpected(ctx)
})
wg.Go(func() {
err2 = verifier.SidecarProposerExpected(ctx)
})
wg.Wait()
require.Equal(t, true, verifier.results.executed(RequireSidecarProposerExpected))
if len(tc.error) > 0 {
require.ErrorContains(t, tc.error, err1)
require.ErrorContains(t, tc.error, err2)
require.NotNil(t, verifier.results.result(RequireSidecarProposerExpected))
return
}
require.NoError(t, err1)
require.NoError(t, err2)
require.NoError(t, verifier.results.result(RequireSidecarProposerExpected))
err := verifier.SidecarProposerExpected(ctx)
require.NoError(t, err)
})
}
}
func TestColumnRequirementSatisfaction(t *testing.T) {
const (
columnSlot = 1
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
initializer := Initializer{}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
// We haven't performed any verification, VerifiedRODataColumns should error.
_, err := verifier.VerifiedRODataColumns()
require.ErrorIs(t, err, errColumnsInvalid)
var me VerificationMultiError
ok := errors.As(err, &me)
require.Equal(t, true, ok)
fails := me.Failures()
// We haven't performed any verification, so all the results should be this type.
for _, v := range fails {
require.ErrorIs(t, v, ErrMissingVerification)
}
// Satisfy everything but the first requirement through the backdoor.
for _, r := range GossipDataColumnSidecarRequirements[1:] {
verifier.results.record(r, nil)
}
// One requirement is missing, VerifiedRODataColumns should still error.
_, err = verifier.VerifiedRODataColumns()
require.ErrorIs(t, err, errColumnsInvalid)
// Now, satisfy the first requirement.
verifier.results.record(GossipDataColumnSidecarRequirements[0], nil)
// VerifiedRODataColumns should now succeed.
require.Equal(t, true, verifier.results.allSatisfied())
_, err = verifier.VerifiedRODataColumns()
require.NoError(t, err)
}
func TestConcatRootSlot(t *testing.T) {
root := [fieldparams.RootLength]byte{1, 2, 3}
const slot = primitives.Slot(3210)
const expected = "\x01\x02\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003210"
actual := concatRootSlot(root, slot)
require.Equal(t, expected, actual)
}