mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,11 +61,12 @@ func TestGetBlock(t *testing.T) {
|
||||
fetcher := &BeaconDbBlocker{
|
||||
BeaconDB: beaconDB,
|
||||
ChainInfoFetcher: &mockChain.ChainService{
|
||||
DB: beaconDB,
|
||||
Block: wsb,
|
||||
Root: headBlock.BlockRoot,
|
||||
FinalizedCheckPoint: ðpb.Checkpoint{Root: blkContainers[64].BlockRoot},
|
||||
CanonicalRoots: canonicalRoots,
|
||||
DB: beaconDB,
|
||||
Block: wsb,
|
||||
Root: headBlock.BlockRoot,
|
||||
FinalizedCheckPoint: ðpb.Checkpoint{Root: blkContainers[64].BlockRoot},
|
||||
CurrentJustifiedCheckPoint: ðpb.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: ðpb.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: ðpb.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) {
|
||||
|
||||
Reference in New Issue
Block a user