2023-03-28 16:44:41 +00:00
package lookup
import (
"context"
2023-12-07 17:37:11 +00:00
"fmt"
2023-03-28 16:44:41 +00:00
"strconv"
2023-10-03 05:52:29 +00:00
"strings"
2023-03-28 16:44:41 +00:00
2023-10-03 05:52:29 +00:00
"github.com/ethereum/go-ethereum/common/hexutil"
2023-03-28 16:44:41 +00:00
"github.com/pkg/errors"
2024-02-15 05:46:47 +00:00
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filesystem"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/time/slots"
2024-01-23 20:07:46 +00:00
log "github.com/sirupsen/logrus"
2023-03-28 16:44:41 +00:00
)
// BlockIdParseError represents an error scenario where a block ID could not be parsed.
type BlockIdParseError struct {
message string
}
// NewBlockIdParseError creates a new error instance.
func NewBlockIdParseError ( reason error ) BlockIdParseError {
return BlockIdParseError {
message : errors . Wrapf ( reason , "could not parse block ID" ) . Error ( ) ,
}
}
// Error returns the underlying error message.
func ( e BlockIdParseError ) Error ( ) string {
return e . message
}
// Blocker is responsible for retrieving blocks.
type Blocker interface {
Block ( ctx context . Context , id [ ] byte ) ( interfaces . ReadOnlySignedBeaconBlock , error )
2023-12-07 17:37:11 +00:00
Blobs ( ctx context . Context , id string , indices [ ] uint64 ) ( [ ] * blocks . VerifiedROBlob , * core . RpcError )
2023-03-28 16:44:41 +00:00
}
// BeaconDbBlocker is an implementation of Blocker. It retrieves blocks from the beacon chain database.
type BeaconDbBlocker struct {
2024-02-06 22:31:17 +00:00
BeaconDB db . ReadOnlyDatabase
ChainInfoFetcher blockchain . ChainInfoFetcher
GenesisTimeFetcher blockchain . TimeFetcher
BlobStorage * filesystem . BlobStorage
2023-03-28 16:44:41 +00:00
}
// Block returns the beacon block for a given identifier. The identifier can be one of:
// - "head" (canonical head in node's view)
// - "genesis"
// - "finalized"
// - "justified"
// - <slot>
// - <hex encoded block root with '0x' prefix>
2023-10-03 05:52:29 +00:00
// - <block root>
2023-03-28 16:44:41 +00:00
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 :
2023-10-03 05:52:29 +00:00
stringId := strings . ToLower ( string ( id ) )
if len ( stringId ) >= 2 && stringId [ : 2 ] == "0x" {
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 {
2023-03-28 16:44:41 +00:00
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
}
}
}
}
return blk , nil
}
2023-12-07 17:37:11 +00:00
// Blobs returns the blobs for a given block id identifier and blob indices. The identifier can be one of:
// - "head" (canonical head in node's view)
// - "genesis"
// - "finalized"
// - "justified"
// - <slot>
// - <hex encoded block root with '0x' prefix>
// - <block root>
2024-02-06 22:31:17 +00:00
//
// cases:
// - no block, 404
// - block exists, no commitment, 200 w/ empty list
// - block exists, has commitments, inside retention period (greater of protocol- or user-specified) serve then w/ 200 unless we hit an error reading them.
// we are technically not supposed to import a block to forkchoice unless we have the blobs, so the nuance here is if we can't find the file and we are inside the protocol-defined retention period, then it's actually a 500.
// - block exists, has commitments, outside retention period (greater of protocol- or user-specified) - ie just like block exists, no commitment
2023-12-07 17:37:11 +00:00
func ( p * BeaconDbBlocker ) Blobs ( ctx context . Context , id string , indices [ ] uint64 ) ( [ ] * blocks . VerifiedROBlob , * core . RpcError ) {
var root [ ] 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
root , 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 }
}
root = 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 }
}
root = jcp . Root
default :
if bytesutil . IsHex ( [ ] byte ( id ) ) {
var err error
root , err = hexutil . Decode ( id )
if len ( root ) != fieldparams . RootLength {
return nil , & core . RpcError { Err : fmt . Errorf ( "invalid block root of length %d" , len ( root ) ) , Reason : core . BadRequest }
}
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 ( "block not found: no block roots at slot %d" , slot ) , Reason : core . NotFound }
}
if err != nil {
return nil , & core . RpcError { Err : errors . Wrap ( err , "failed to get block roots by slot" ) , Reason : core . Internal }
}
root = 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 . Wrap ( err , "could not determine if block root is canonical" ) , Reason : core . Internal }
}
if canonical {
root = blockRoot [ : ]
break
}
}
}
}
2024-02-06 22:31:17 +00:00
if ! p . BeaconDB . HasBlock ( ctx , bytesutil . ToBytes32 ( root ) ) {
return nil , & core . RpcError { Err : errors . New ( "block not found" ) , Reason : core . NotFound }
}
b , err := p . BeaconDB . Block ( ctx , bytesutil . ToBytes32 ( root ) )
if err != nil {
return nil , & core . RpcError { Err : errors . Wrap ( err , "failed to retrieve block from db" ) , Reason : core . Internal }
}
// if block is not in the retention window return 200 w/ empty list
if ! params . WithinDAPeriod ( slots . ToEpoch ( b . Block ( ) . Slot ( ) ) , slots . ToEpoch ( p . GenesisTimeFetcher . CurrentSlot ( ) ) ) {
return make ( [ ] * blocks . VerifiedROBlob , 0 ) , nil
}
commitments , err := b . Block ( ) . Body ( ) . BlobKzgCommitments ( )
if err != nil {
return nil , & core . RpcError { Err : errors . Wrap ( err , "failed to retrieve kzg commitments from block" ) , Reason : core . Internal }
}
// if there are no commitments return 200 w/ empty list
if len ( commitments ) == 0 {
return make ( [ ] * blocks . VerifiedROBlob , 0 ) , nil
}
2023-12-07 17:37:11 +00:00
if len ( indices ) == 0 {
m , err := p . BlobStorage . Indices ( bytesutil . ToBytes32 ( root ) )
if err != nil {
2024-01-23 20:07:46 +00:00
log . WithFields ( log . Fields {
2024-02-22 22:40:36 +00:00
"blockRoot" : hexutil . Encode ( root ) ,
2024-01-23 20:07:46 +00:00
} ) . Error ( errors . Wrapf ( err , "could not retrieve blob indices for root %#x" , root ) )
return nil , & core . RpcError { Err : fmt . Errorf ( "could not retrieve blob indices for root %#x" , root ) , Reason : core . Internal }
2023-12-07 17:37:11 +00:00
}
for k , v := range m {
if v {
indices = append ( indices , uint64 ( k ) )
}
}
}
2023-12-07 22:33:26 +00:00
// returns empty slice if there are no indices
blobs := make ( [ ] * blocks . VerifiedROBlob , len ( indices ) )
for i , index := range indices {
2023-12-07 17:37:11 +00:00
vblob , err := p . BlobStorage . Get ( bytesutil . ToBytes32 ( root ) , index )
if err != nil {
2024-01-23 20:07:46 +00:00
log . WithFields ( log . Fields {
2024-02-22 22:40:36 +00:00
"blockRoot" : hexutil . Encode ( root ) ,
"blobIndex" : index ,
2024-01-23 20:07:46 +00:00
} ) . Error ( errors . Wrapf ( err , "could not retrieve blob for block root %#x at index %d" , root , index ) )
return nil , & core . RpcError { Err : fmt . Errorf ( "could not retrieve blob for block root %#x at index %d" , root , index ) , Reason : core . Internal }
2023-12-07 17:37:11 +00:00
}
2023-12-07 22:33:26 +00:00
blobs [ i ] = & vblob
2023-12-07 17:37:11 +00:00
}
return blobs , nil
}