adding justified support to blocker and optimizing logic (#15715)

* adding justified support to blocker and optimizing logic

* gaz

* fixing tests

* Update beacon-chain/rpc/lookup/blocker.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update beacon-chain/rpc/lookup/blocker.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* better error handling from radek feedback

* unneeded change

* more radek review

* addressing more feedback

* fixing small comment

* fixing errors

* removing fallback

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
james-prysm
2025-09-18 12:01:27 -05:00
committed by GitHub
parent 900f162467
commit d826a3c7fe
8 changed files with 359 additions and 163 deletions

View File

@@ -71,7 +71,7 @@ func TestBlobs(t *testing.T) {
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.StringContains(t, "blobs are not supported for Phase 0 fork", e.Message)
assert.StringContains(t, "not supported for Phase 0 fork", e.Message)
})
t.Run("head", func(t *testing.T) {
u := "http://foo.example/head"
@@ -336,11 +336,23 @@ func TestBlobs(t *testing.T) {
require.Equal(t, false, resp.Finalized)
})
t.Run("slot before Deneb fork", func(t *testing.T) {
// Create and save a pre-Deneb block at slot 31
predenebBlock := util.NewBeaconBlock()
predenebBlock.Block.Slot = 31
util.SaveBlock(t, t.Context(), db, predenebBlock)
u := "http://foo.example/31"
request := httptest.NewRequest("GET", u, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{}
s.Blocker = &lookup.BeaconDbBlocker{
ChainInfoFetcher: &mockChain.ChainService{},
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
Genesis: time.Now(),
},
BeaconDB: db,
BlobStorage: bs,
}
s.Blobs(writer, request)
@@ -348,7 +360,7 @@ func TestBlobs(t *testing.T) {
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.StringContains(t, "blobs are not supported before Deneb fork", e.Message)
assert.StringContains(t, "not supported before", e.Message)
})
t.Run("malformed block ID", func(t *testing.T) {
u := "http://foo.example/foo"
@@ -609,7 +621,7 @@ func TestGetBlobs(t *testing.T) {
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.StringContains(t, "blobs are not supported for Phase 0 fork", e.Message)
assert.StringContains(t, "not supported for Phase 0 fork", e.Message)
})
t.Run("head", func(t *testing.T) {
u := "http://foo.example/head"
@@ -808,11 +820,22 @@ func TestGetBlobs(t *testing.T) {
require.Equal(t, false, resp.Finalized)
})
t.Run("slot before Deneb fork", func(t *testing.T) {
// Create and save a pre-Deneb block at slot 31
predenebBlock := util.NewBeaconBlock()
predenebBlock.Block.Slot = 31
util.SaveBlock(t, t.Context(), db, predenebBlock)
u := "http://foo.example/31"
request := httptest.NewRequest("GET", u, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{}
s.Blocker = &lookup.BeaconDbBlocker{
BeaconDB: db,
ChainInfoFetcher: &mockChain.ChainService{},
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
Genesis: time.Now(),
},
}
s.GetBlobs(writer, request)
@@ -820,7 +843,7 @@ func TestGetBlobs(t *testing.T) {
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.StringContains(t, "blobs are not supported before Deneb fork", e.Message)
assert.StringContains(t, "not supported before", e.Message)
})
t.Run("malformed block ID", func(t *testing.T) {
u := "http://foo.example/foo"

View File

@@ -39,8 +39,8 @@ func HandleIsOptimisticError(w http.ResponseWriter, err error) {
return
}
var blockRootsNotFoundErr *lookup.BlockRootsNotFoundError
if errors.As(err, &blockRootsNotFoundErr) {
var blockNotFoundErr *lookup.BlockNotFoundError
if errors.As(err, &blockNotFoundErr) {
httputil.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusNotFound)
return
}

View File

@@ -138,7 +138,7 @@ func isStateRootOptimistic(
return true, errors.Wrapf(err, "could not get block roots for slot %d", st.Slot())
}
if !has {
return true, lookup.NewBlockRootsNotFoundError()
return true, lookup.NewBlockNotFoundError("no block roots returned from the database")
}
for _, r := range roots {
b, err := database.Block(ctx, r)

View File

@@ -286,8 +286,8 @@ func TestIsOptimistic(t *testing.T) {
require.NoError(t, err)
mf := &testutil.MockStater{BeaconState: st}
_, err = IsOptimistic(ctx, []byte(hexutil.Encode(bytesutil.PadTo([]byte("root"), 32))), cs, mf, cs, db)
var blockRootsNotFoundErr *lookup.BlockRootsNotFoundError
require.Equal(t, true, errors.As(err, &blockRootsNotFoundErr))
var blockNotFoundErr *lookup.BlockNotFoundError
require.Equal(t, true, errors.As(err, &blockNotFoundErr))
})
})
@@ -395,11 +395,11 @@ func TestHandleIsOptimisticError(t *testing.T) {
})
t.Run("no block roots error handled as 404", func(t *testing.T) {
rr := httptest.NewRecorder()
blockRootsErr := lookup.NewBlockRootsNotFoundError()
HandleIsOptimisticError(rr, blockRootsErr)
blockNotFoundErr := lookup.NewBlockNotFoundError("no block roots returned from the database")
HandleIsOptimisticError(rr, blockNotFoundErr)
require.Equal(t, http.StatusNotFound, rr.Code)
require.StringContains(t, blockRootsErr.Error(), rr.Body.String())
require.StringContains(t, blockNotFoundErr.Error(), rr.Body.String())
})
t.Run("generic error handled as 500", func(t *testing.T) {

View File

@@ -25,6 +25,7 @@ go_library(
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",

View File

@@ -19,22 +19,25 @@ import (
"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v6/runtime/version"
"github.com/OffchainLabs/prysm/v6/time/slots"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
)
type BlockRootsNotFoundError struct {
// BlockNotFoundError represents an error when a block cannot be found.
type BlockNotFoundError struct {
message string
}
func NewBlockRootsNotFoundError() *BlockRootsNotFoundError {
return &BlockRootsNotFoundError{
message: "no block roots returned from the database",
// NewBlockNotFoundError creates a new BlockNotFoundError with a custom message.
func NewBlockNotFoundError(msg string) *BlockNotFoundError {
return &BlockNotFoundError{
message: msg,
}
}
func (e BlockRootsNotFoundError) Error() string {
func (e *BlockNotFoundError) Error() string {
return e.message
}
@@ -70,6 +73,141 @@ type BeaconDbBlocker struct {
DataColumnStorage *filesystem.DataColumnStorage
}
// resolveBlockIDByRootOrSlot resolves a block ID that is either a root (hex string or raw bytes) or a slot number.
func (p *BeaconDbBlocker) resolveBlockIDByRootOrSlot(ctx context.Context, id string) ([fieldparams.RootLength]byte, interfaces.ReadOnlySignedBeaconBlock, error) {
var rootSlice []byte
// Check if it's a hex-encoded root
if bytesutil.IsHex([]byte(id)) {
var err error
rootSlice, err = bytesutil.DecodeHexWithLength(id, fieldparams.RootLength)
if err != nil {
e := NewBlockIdParseError(err)
return [32]byte{}, nil, &e
}
} else if len(id) == 32 {
// Handle raw 32-byte root
rootSlice = []byte(id)
} else {
// Try to parse as slot number
slot, err := strconv.ParseUint(id, 10, 64)
if err != nil {
e := NewBlockIdParseError(err)
return [32]byte{}, nil, &e
}
// Get block roots for the slot
ok, roots, err := p.BeaconDB.BlockRootsBySlot(ctx, primitives.Slot(slot))
if err != nil {
return [32]byte{}, nil, errors.Wrapf(err, "could not retrieve block roots for slot %d", slot)
}
if !ok || len(roots) == 0 {
return [32]byte{}, nil, NewBlockNotFoundError(fmt.Sprintf("no blocks found at slot %d", slot))
}
// Find the canonical block root
if p.ChainInfoFetcher == nil {
return [32]byte{}, nil, errors.New("chain info fetcher is not configured")
}
for _, root := range roots {
canonical, err := p.ChainInfoFetcher.IsCanonical(ctx, root)
if err != nil {
return [32]byte{}, nil, errors.Wrapf(err, "could not determine if block root is canonical")
}
if canonical {
rootSlice = root[:]
break
}
}
// If no canonical block found, rootSlice remains nil
if rootSlice == nil {
// No canonical block at this slot
return [32]byte{}, nil, NewBlockNotFoundError(fmt.Sprintf("no canonical block found at slot %d", slot))
}
}
// Fetch the block using the root
root := bytesutil.ToBytes32(rootSlice)
blk, err := p.BeaconDB.Block(ctx, root)
if err != nil {
return [32]byte{}, nil, errors.Wrapf(err, "failed to retrieve block %#x from db", rootSlice)
}
if blk == nil {
return [32]byte{}, nil, NewBlockNotFoundError(fmt.Sprintf("block %#x not found in db", rootSlice))
}
return root, blk, nil
}
// resolveBlockID resolves a block ID to root and signed block.
// Fork validation is handled outside this function by the calling methods.
func (p *BeaconDbBlocker) resolveBlockID(ctx context.Context, id string) ([fieldparams.RootLength]byte, interfaces.ReadOnlySignedBeaconBlock, error) {
switch id {
case "genesis":
blk, err := p.BeaconDB.GenesisBlock(ctx)
if err != nil {
return [32]byte{}, nil, errors.Wrap(err, "could not retrieve genesis block")
}
if blk == nil {
return [32]byte{}, nil, NewBlockNotFoundError("genesis block not found")
}
root, err := blk.Block().HashTreeRoot()
if err != nil {
return [32]byte{}, nil, errors.Wrap(err, "could not get genesis block root")
}
return root, blk, nil
case "head":
blk, err := p.ChainInfoFetcher.HeadBlock(ctx)
if err != nil {
return [32]byte{}, nil, errors.Wrap(err, "could not retrieve head block")
}
if blk == nil {
return [32]byte{}, nil, NewBlockNotFoundError("head block not found")
}
root, err := blk.Block().HashTreeRoot()
if err != nil {
return [32]byte{}, nil, errors.Wrap(err, "could not get head block root")
}
return root, blk, nil
case "finalized":
finalized := p.ChainInfoFetcher.FinalizedCheckpt()
if finalized == nil {
return [32]byte{}, nil, errors.New("received nil finalized checkpoint")
}
finalizedRoot := bytesutil.ToBytes32(finalized.Root)
blk, err := p.BeaconDB.Block(ctx, finalizedRoot)
if err != nil {
return [32]byte{}, nil, errors.Wrap(err, "could not retrieve finalized block")
}
if blk == nil {
return [32]byte{}, nil, NewBlockNotFoundError(fmt.Sprintf("finalized block %#x not found", finalizedRoot))
}
return finalizedRoot, blk, nil
case "justified":
jcp := p.ChainInfoFetcher.CurrentJustifiedCheckpt()
if jcp == nil {
return [32]byte{}, nil, errors.New("received nil justified checkpoint")
}
justifiedRoot := bytesutil.ToBytes32(jcp.Root)
blk, err := p.BeaconDB.Block(ctx, justifiedRoot)
if err != nil {
return [32]byte{}, nil, errors.Wrap(err, "could not retrieve justified block")
}
if blk == nil {
return [32]byte{}, nil, NewBlockNotFoundError(fmt.Sprintf("justified block %#x not found", justifiedRoot))
}
return justifiedRoot, blk, nil
default:
return p.resolveBlockIDByRootOrSlot(ctx, id)
}
}
// Block returns the beacon block for a given identifier. The identifier can be one of:
// - "head" (canonical head in node's view)
// - "genesis"
@@ -79,71 +217,9 @@ type BeaconDbBlocker struct {
// - <hex encoded block root with '0x' prefix>
// - <block root>
func (p *BeaconDbBlocker) Block(ctx context.Context, id []byte) (interfaces.ReadOnlySignedBeaconBlock, error) {
var err error
var blk interfaces.ReadOnlySignedBeaconBlock
switch string(id) {
case "head":
blk, err = p.ChainInfoFetcher.HeadBlock(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not retrieve head block")
}
case "finalized":
finalized := p.ChainInfoFetcher.FinalizedCheckpt()
finalizedRoot := bytesutil.ToBytes32(finalized.Root)
blk, err = p.BeaconDB.Block(ctx, finalizedRoot)
if err != nil {
return nil, errors.New("could not get finalized block from db")
}
case "genesis":
blk, err = p.BeaconDB.GenesisBlock(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not retrieve genesis block")
}
default:
if bytesutil.IsHex(id) {
decoded, err := hexutil.Decode(string(id))
if err != nil {
e := NewBlockIdParseError(err)
return nil, &e
}
blk, err = p.BeaconDB.Block(ctx, bytesutil.ToBytes32(decoded))
if err != nil {
return nil, errors.Wrap(err, "could not retrieve block")
}
} else if len(id) == 32 {
blk, err = p.BeaconDB.Block(ctx, bytesutil.ToBytes32(id))
if err != nil {
return nil, errors.Wrap(err, "could not retrieve block")
}
} else {
slot, err := strconv.ParseUint(string(id), 10, 64)
if err != nil {
e := NewBlockIdParseError(err)
return nil, &e
}
blks, err := p.BeaconDB.BlocksBySlot(ctx, primitives.Slot(slot))
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve blocks for slot %d", slot)
}
_, roots, err := p.BeaconDB.BlockRootsBySlot(ctx, primitives.Slot(slot))
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve block roots for slot %d", slot)
}
numBlks := len(blks)
if numBlks == 0 {
return nil, nil
}
for i, b := range blks {
canonical, err := p.ChainInfoFetcher.IsCanonical(ctx, roots[i])
if err != nil {
return nil, errors.Wrapf(err, "could not determine if block root is canonical")
}
if canonical {
blk = b
break
}
}
}
_, blk, err := p.resolveBlockID(ctx, string(id))
if err != nil {
return nil, err
}
return blk, nil
}
@@ -171,88 +247,40 @@ func (p *BeaconDbBlocker) Blobs(ctx context.Context, id string, opts ...options.
opt(cfg)
}
// Resolve block ID to root
var rootSlice []byte
switch id {
case "genesis":
return nil, &core.RpcError{Err: errors.New("blobs are not supported for Phase 0 fork"), Reason: core.BadRequest}
case "head":
var err error
rootSlice, err = p.ChainInfoFetcher.HeadRoot(ctx)
if err != nil {
return nil, &core.RpcError{Err: errors.Wrapf(err, "could not retrieve head root"), Reason: core.Internal}
}
case "finalized":
fcp := p.ChainInfoFetcher.FinalizedCheckpt()
if fcp == nil {
return nil, &core.RpcError{Err: errors.New("received nil finalized checkpoint"), Reason: core.Internal}
}
rootSlice = fcp.Root
case "justified":
jcp := p.ChainInfoFetcher.CurrentJustifiedCheckpt()
if jcp == nil {
return nil, &core.RpcError{Err: errors.New("received nil justified checkpoint"), Reason: core.Internal}
}
rootSlice = jcp.Root
default:
if bytesutil.IsHex([]byte(id)) {
var err error
rootSlice, err = bytesutil.DecodeHexWithLength(id, fieldparams.RootLength)
if err != nil {
return nil, &core.RpcError{Err: NewBlockIdParseError(err), Reason: core.BadRequest}
}
} else {
slot, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return nil, &core.RpcError{Err: NewBlockIdParseError(err), Reason: core.BadRequest}
}
denebStart, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch)
if err != nil {
return nil, &core.RpcError{Err: errors.Wrap(err, "could not calculate Deneb start slot"), Reason: core.Internal}
}
if primitives.Slot(slot) < denebStart {
return nil, &core.RpcError{Err: errors.New("blobs are not supported before Deneb fork"), Reason: core.BadRequest}
}
ok, roots, err := p.BeaconDB.BlockRootsBySlot(ctx, primitives.Slot(slot))
if !ok {
return nil, &core.RpcError{Err: fmt.Errorf("no block roots at slot %d", slot), Reason: core.NotFound}
}
if err != nil {
return nil, &core.RpcError{Err: errors.Wrapf(err, "failed to get block roots for slot %d", slot), Reason: core.Internal}
}
rootSlice = roots[0][:]
if len(roots) == 1 {
break
}
for _, blockRoot := range roots {
canonical, err := p.ChainInfoFetcher.IsCanonical(ctx, blockRoot)
if err != nil {
return nil, &core.RpcError{Err: errors.Wrapf(err, "could not determine if block %#x is canonical", blockRoot), Reason: core.Internal}
}
if canonical {
rootSlice = blockRoot[:]
break
}
}
}
// Check for genesis block first (not supported for blobs)
if id == "genesis" {
return nil, &core.RpcError{Err: errors.New("not supported for Phase 0 fork"), Reason: core.BadRequest}
}
root := bytesutil.ToBytes32(rootSlice)
roSignedBlock, err := p.BeaconDB.Block(ctx, root)
// Resolve block ID to root and block
root, roSignedBlock, err := p.resolveBlockID(ctx, id)
if err != nil {
return nil, &core.RpcError{Err: errors.Wrapf(err, "failed to retrieve block %#x from db", rootSlice), Reason: core.Internal}
var blockNotFound *BlockNotFoundError
var blockIdParseErr *BlockIdParseError
reason := core.Internal // Default to Internal for unexpected errors
if errors.As(err, &blockNotFound) {
reason = core.NotFound
} else if errors.As(err, &blockIdParseErr) {
reason = core.BadRequest
}
return nil, &core.RpcError{Err: err, Reason: core.ErrorReason(reason)}
}
if roSignedBlock == nil {
return nil, &core.RpcError{Err: fmt.Errorf("block %#x not found in db", rootSlice), Reason: core.NotFound}
// Validate fork epoch for Deneb (blobs)
if roSignedBlock != nil {
slot := roSignedBlock.Block().Slot()
if slots.ToEpoch(slot) < params.BeaconConfig().DenebForkEpoch {
forkName := version.String(slots.ToForkVersion(slot))
return nil, &core.RpcError{Err: fmt.Errorf("not supported before %s fork", forkName), Reason: core.BadRequest}
}
}
roBlock := roSignedBlock.Block()
commitments, err := roBlock.Body().BlobKzgCommitments()
if err != nil {
return nil, &core.RpcError{Err: errors.Wrapf(err, "failed to retrieve kzg commitments from block %#x", rootSlice), Reason: core.Internal}
return nil, &core.RpcError{Err: errors.Wrapf(err, "failed to retrieve kzg commitments from block %#x", root), Reason: core.Internal}
}
// If there are no commitments return 200 w/ empty list
@@ -266,7 +294,7 @@ func (p *BeaconDbBlocker) Blobs(ctx context.Context, id string, opts ...options.
if fuluForkEpoch != primitives.Epoch(math.MaxUint64) {
fuluForkSlot, err = slots.EpochStart(fuluForkEpoch)
if err != nil {
return nil, &core.RpcError{Err: errors.Wrap(err, "could not calculate peerDAS start slot"), Reason: core.Internal}
return nil, &core.RpcError{Err: errors.Wrap(err, "could not calculate Fulu start slot"), Reason: core.Internal}
}
}

View File

@@ -61,11 +61,12 @@ func TestGetBlock(t *testing.T) {
fetcher := &BeaconDbBlocker{
BeaconDB: beaconDB,
ChainInfoFetcher: &mockChain.ChainService{
DB: beaconDB,
Block: wsb,
Root: headBlock.BlockRoot,
FinalizedCheckPoint: &ethpb.Checkpoint{Root: blkContainers[64].BlockRoot},
CanonicalRoots: canonicalRoots,
DB: beaconDB,
Block: wsb,
Root: headBlock.BlockRoot,
FinalizedCheckPoint: &ethpb.Checkpoint{Root: blkContainers[64].BlockRoot},
CurrentJustifiedCheckPoint: &ethpb.Checkpoint{Root: blkContainers[32].BlockRoot},
CanonicalRoots: canonicalRoots,
},
}
@@ -108,6 +109,11 @@ func TestGetBlock(t *testing.T) {
blockID: []byte("finalized"),
want: blkContainers[64].Block.(*ethpb.BeaconBlockContainer_Phase0Block).Phase0Block,
},
{
name: "justified",
blockID: []byte("justified"),
want: blkContainers[32].Block.(*ethpb.BeaconBlockContainer_Phase0Block).Phase0Block,
},
{
name: "genesis",
blockID: []byte("genesis"),
@@ -162,6 +168,112 @@ func TestGetBlock(t *testing.T) {
}
}
func TestBlobsErrorHandling(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.DenebForkEpoch = 1
params.OverrideBeaconConfig(cfg)
ctx := t.Context()
db := testDB.SetupDB(t)
t.Run("non-existent block by root returns 404", func(t *testing.T) {
blocker := &BeaconDbBlocker{
BeaconDB: db,
}
_, rpcErr := blocker.Blobs(ctx, "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
require.NotNil(t, rpcErr)
require.Equal(t, core.ErrorReason(core.NotFound), rpcErr.Reason)
require.StringContains(t, "not found", rpcErr.Err.Error())
})
t.Run("non-existent block by slot returns 404", func(t *testing.T) {
blocker := &BeaconDbBlocker{
BeaconDB: db,
ChainInfoFetcher: &mockChain.ChainService{},
}
_, rpcErr := blocker.Blobs(ctx, "999999")
require.NotNil(t, rpcErr)
require.Equal(t, core.ErrorReason(core.NotFound), rpcErr.Reason)
require.StringContains(t, "no blocks found at slot", rpcErr.Err.Error())
})
t.Run("genesis block not found returns 404", func(t *testing.T) {
blocker := &BeaconDbBlocker{
BeaconDB: db,
}
// Note: genesis blocks don't support blobs, so this returns BadRequest
_, rpcErr := blocker.Blobs(ctx, "genesis")
require.NotNil(t, rpcErr)
require.Equal(t, core.ErrorReason(core.BadRequest), rpcErr.Reason)
require.StringContains(t, "not supported for Phase 0", rpcErr.Err.Error())
})
t.Run("finalized block not found returns 404", func(t *testing.T) {
// Set up a finalized checkpoint pointing to a non-existent block
nonExistentRoot := bytesutil.PadTo([]byte("nonexistent"), 32)
blocker := &BeaconDbBlocker{
BeaconDB: db,
ChainInfoFetcher: &mockChain.ChainService{
FinalizedCheckPoint: &ethpb.Checkpoint{Root: nonExistentRoot},
},
}
_, rpcErr := blocker.Blobs(ctx, "finalized")
require.NotNil(t, rpcErr)
require.Equal(t, core.ErrorReason(core.NotFound), rpcErr.Reason)
require.StringContains(t, "finalized block", rpcErr.Err.Error())
require.StringContains(t, "not found", rpcErr.Err.Error())
})
t.Run("justified block not found returns 404", func(t *testing.T) {
// Set up a justified checkpoint pointing to a non-existent block
nonExistentRoot := bytesutil.PadTo([]byte("nonexistent2"), 32)
blocker := &BeaconDbBlocker{
BeaconDB: db,
ChainInfoFetcher: &mockChain.ChainService{
CurrentJustifiedCheckPoint: &ethpb.Checkpoint{Root: nonExistentRoot},
},
}
_, rpcErr := blocker.Blobs(ctx, "justified")
require.NotNil(t, rpcErr)
require.Equal(t, core.ErrorReason(core.NotFound), rpcErr.Reason)
require.StringContains(t, "justified block", rpcErr.Err.Error())
require.StringContains(t, "not found", rpcErr.Err.Error())
})
t.Run("invalid block ID returns 400", func(t *testing.T) {
blocker := &BeaconDbBlocker{
BeaconDB: db,
}
_, rpcErr := blocker.Blobs(ctx, "invalid-hex")
require.NotNil(t, rpcErr)
require.Equal(t, core.ErrorReason(core.BadRequest), rpcErr.Reason)
require.StringContains(t, "could not parse block ID", rpcErr.Err.Error())
})
t.Run("database error returns 500", func(t *testing.T) {
// Create a pre-Deneb block with valid slot
predenebBlock := util.NewBeaconBlock()
predenebBlock.Block.Slot = 100
util.SaveBlock(t, ctx, db, predenebBlock)
// Create blocker without ChainInfoFetcher to trigger internal error when checking canonical status
blocker := &BeaconDbBlocker{
BeaconDB: db,
}
_, rpcErr := blocker.Blobs(ctx, "100")
require.NotNil(t, rpcErr)
require.Equal(t, core.ErrorReason(core.Internal), rpcErr.Reason)
})
}
func TestGetBlob(t *testing.T) {
const (
slot = 123
@@ -240,14 +352,17 @@ func TestGetBlob(t *testing.T) {
blocker := &BeaconDbBlocker{}
_, rpcErr := blocker.Blobs(ctx, "genesis")
require.Equal(t, http.StatusBadRequest, core.ErrorReasonToHTTP(rpcErr.Reason))
require.StringContains(t, "blobs are not supported for Phase 0 fork", rpcErr.Err.Error())
require.StringContains(t, "not supported for Phase 0 fork", rpcErr.Err.Error())
})
t.Run("head", func(t *testing.T) {
setupDeneb(t)
blocker := &BeaconDbBlocker{
ChainInfoFetcher: &mockChain.ChainService{Root: denebBlockRoot[:]},
ChainInfoFetcher: &mockChain.ChainService{
Root: denebBlockRoot[:],
Block: denebBlock,
},
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
Genesis: time.Now(),
},
@@ -326,6 +441,7 @@ func TestGetBlob(t *testing.T) {
setupDeneb(t)
blocker := &BeaconDbBlocker{
ChainInfoFetcher: &mockChain.ChainService{},
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
Genesis: time.Now(),
},
@@ -491,6 +607,31 @@ func TestGetBlob(t *testing.T) {
require.DeepSSZEqual(t, initialBlobSidecarPb, retrievedBlobSidecarPb)
}
})
t.Run("pre-deneb block should return 400", func(t *testing.T) {
// Setup with Deneb fork at epoch 1, so slot 0 is before Deneb
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.DenebForkEpoch = 1
params.OverrideBeaconConfig(cfg)
// Create a pre-Deneb block (slot 0, which is epoch 0)
predenebBlock := util.NewBeaconBlock()
predenebBlock.Block.Slot = 0
util.SaveBlock(t, ctx, db, predenebBlock)
predenebBlockRoot, err := predenebBlock.Block.HashTreeRoot()
require.NoError(t, err)
blocker := &BeaconDbBlocker{
BeaconDB: db,
}
_, rpcErr := blocker.Blobs(ctx, hexutil.Encode(predenebBlockRoot[:]))
require.NotNil(t, rpcErr)
require.Equal(t, core.ErrorReason(core.BadRequest), rpcErr.Reason)
require.Equal(t, http.StatusBadRequest, core.ErrorReasonToHTTP(rpcErr.Reason))
require.StringContains(t, "not supported before", rpcErr.Err.Error())
})
}
func TestBlobs_CommitmentOrdering(t *testing.T) {