2022-05-06 15:03:15 +00:00
// This file will keep track of all the code needed to process a slot.
// To process a slot, it should have all the necessary data needed to write it to the DB.
// But not actually write it.
package beaconclient
import (
2022-05-16 15:49:23 +00:00
"context"
2022-05-06 15:03:15 +00:00
"encoding/hex"
"fmt"
"strconv"
"strings"
2022-05-17 14:32:31 +00:00
si "github.com/prysmaticlabs/prysm/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/consensus-types/wrapper"
dt "github.com/prysmaticlabs/prysm/encoding/ssz/detect"
2022-05-06 15:03:15 +00:00
// The below is temporary, once https://github.com/prysmaticlabs/prysm/issues/10006 has been resolved we wont need it.
// pb "github.com/prysmaticlabs/prysm/proto/prysm/v2"
2022-05-17 14:32:31 +00:00
state "github.com/prysmaticlabs/prysm/beacon-chain/state"
2022-05-06 15:03:15 +00:00
log "github.com/sirupsen/logrus"
"github.com/vulcanize/ipld-ethcl-indexer/pkg/database/sql"
"github.com/vulcanize/ipld-ethcl-indexer/pkg/loghelper"
2022-05-16 15:49:23 +00:00
"golang.org/x/sync/errgroup"
2022-05-06 15:03:15 +00:00
)
var (
SlotUnmarshalError = func ( obj string ) string {
return fmt . Sprintf ( "Unable to properly unmarshal the Slot field in the %s." , obj )
}
2022-05-17 14:32:31 +00:00
ParentRootUnmarshalError = "Unable to properly unmarshal the ParentRoot field in the SignedBeaconBlock."
MissingIdentifiedError = "Can't query state without a set slot or block_root"
MissingEth1Data = "Can't get the Eth1 block_hash"
VersionedUnmarshalerError = "Unable to create a versioned unmarshaler"
2022-05-06 15:03:15 +00:00
)
type ProcessSlot struct {
// Generic
2022-05-12 13:52:13 +00:00
Slot int // The slot number.
Epoch int // The epoch number.
BlockRoot string // The hex encoded string of the BlockRoot.
StateRoot string // The hex encoded string of the StateRoot.
ParentBlockRoot string // The hex encoded string of the parent block.
Status string // The status of the block
2022-05-13 12:48:31 +00:00
HeadOrHistoric string // Is this the head or a historic slot. This is critical when trying to analyze errors and skipped slots.
2022-05-12 13:52:13 +00:00
Db sql . Database // The DB object used to write to the DB.
Metrics * BeaconClientMetrics // An object to keep track of the beaconclient metrics
2022-05-06 15:03:15 +00:00
// BeaconBlock
2022-05-17 14:32:31 +00:00
SszSignedBeaconBlock [ ] byte // The entire SSZ encoded SignedBeaconBlock
FullSignedBeaconBlock si . SignedBeaconBlock // The unmarshaled BeaconState object, the unmarshalling could have errors.
2022-05-06 15:03:15 +00:00
// BeaconState
2022-05-17 14:32:31 +00:00
FullBeaconState state . BeaconState // The unmarshaled BeaconState object, the unmarshalling could have errors.
SszBeaconState [ ] byte // The entire SSZ encoded BeaconState
2022-05-06 15:03:15 +00:00
// DB Write objects
DbSlotsModel * DbSlots // The model being written to the slots table.
DbSignedBeaconBlockModel * DbSignedBeaconBlock // The model being written to the signed_beacon_block table.
DbBeaconState * DbBeaconState // The model being written to the beacon_state table.
}
// This function will do all the work to process the slot and write it to the DB.
2022-05-12 19:44:05 +00:00
func processFullSlot ( db sql . Database , serverAddress string , slot int , blockRoot string , stateRoot string , previousSlot int , previousBlockRoot string , headOrHistoric string , metrics * BeaconClientMetrics , knownGapsTableIncrement int ) error {
2022-05-06 15:03:15 +00:00
ps := & ProcessSlot {
Slot : slot ,
BlockRoot : blockRoot ,
StateRoot : stateRoot ,
HeadOrHistoric : headOrHistoric ,
2022-05-12 13:52:13 +00:00
Db : db ,
Metrics : metrics ,
2022-05-06 15:03:15 +00:00
}
2022-05-16 15:49:23 +00:00
g , _ := errgroup . WithContext ( context . Background ( ) )
2022-05-17 14:32:31 +00:00
vUnmarshalerCh := make ( chan * dt . VersionedUnmarshaler , 1 )
2022-05-16 15:49:23 +00:00
2022-05-13 12:48:31 +00:00
// Get the BeaconState.
2022-05-16 15:49:23 +00:00
g . Go ( func ( ) error {
2022-05-17 14:32:31 +00:00
err := ps . getBeaconState ( serverAddress , vUnmarshalerCh )
2022-05-16 15:49:23 +00:00
if err != nil {
return err
}
return nil
} )
2022-05-06 15:03:15 +00:00
2022-05-13 12:48:31 +00:00
// Get the SignedBeaconBlock.
2022-05-16 15:49:23 +00:00
g . Go ( func ( ) error {
2022-05-17 14:32:31 +00:00
err := ps . getSignedBeaconBlock ( serverAddress , vUnmarshalerCh )
2022-05-16 15:49:23 +00:00
if err != nil {
return err
}
return nil
} )
if err := g . Wait ( ) ; err != nil {
2022-05-16 18:10:43 +00:00
writeKnownGaps ( ps . Db , 1 , ps . Slot , ps . Slot , err , "processSlot" , ps . Metrics )
2022-05-17 19:10:47 +00:00
return err
2022-05-06 15:03:15 +00:00
}
2022-05-13 12:48:31 +00:00
2022-05-12 19:44:05 +00:00
if ps . HeadOrHistoric == "head" && previousSlot == 0 && previousBlockRoot == "" {
2022-05-16 18:10:43 +00:00
writeStartUpGaps ( db , knownGapsTableIncrement , ps . Slot , ps . Metrics )
2022-05-12 19:44:05 +00:00
}
2022-05-06 15:03:15 +00:00
2022-05-12 13:52:13 +00:00
// Get this object ready to write
2022-05-13 12:48:31 +00:00
blockRootEndpoint := serverAddress + BcBlockRootEndpoint ( strconv . Itoa ( ps . Slot ) )
dw , err := ps . createWriteObjects ( blockRootEndpoint )
if err != nil {
2022-05-16 18:10:43 +00:00
writeKnownGaps ( ps . Db , 1 , ps . Slot , ps . Slot , err , "blockRoot" , ps . Metrics )
2022-05-13 12:48:31 +00:00
return err
}
2022-05-12 13:52:13 +00:00
// Write the object to the DB.
2022-05-12 19:44:05 +00:00
err = dw . writeFullSlot ( )
if err != nil {
2022-05-16 18:10:43 +00:00
writeKnownGaps ( ps . Db , 1 , ps . Slot , ps . Slot , err , "processSlot" , ps . Metrics )
return err
2022-05-12 19:44:05 +00:00
}
2022-05-12 13:52:13 +00:00
2022-05-06 15:03:15 +00:00
// Handle any reorgs or skipped slots.
2022-05-12 19:44:05 +00:00
headOrHistoric = strings . ToLower ( headOrHistoric )
if headOrHistoric != "head" && headOrHistoric != "historic" {
return fmt . Errorf ( "headOrHistoric must be either historic or head!" )
}
2022-05-13 12:48:31 +00:00
if ps . HeadOrHistoric == "head" && previousSlot != 0 && previousBlockRoot != "" && ps . Status != "skipped" {
2022-05-12 19:44:05 +00:00
ps . checkPreviousSlot ( previousSlot , previousBlockRoot , knownGapsTableIncrement )
2022-05-06 15:03:15 +00:00
}
return nil
}
// Handle a slot that is at head. A wrapper function for calling `handleFullSlot`.
2022-05-12 19:44:05 +00:00
func processHeadSlot ( db sql . Database , serverAddress string , slot int , blockRoot string , stateRoot string , previousSlot int , previousBlockRoot string , metrics * BeaconClientMetrics , knownGapsTableIncrement int ) error {
return processFullSlot ( db , serverAddress , slot , blockRoot , stateRoot , previousSlot , previousBlockRoot , "head" , metrics , knownGapsTableIncrement )
2022-05-06 15:03:15 +00:00
}
// Handle a historic slot. A wrapper function for calling `handleFullSlot`.
// Commented because of the linter...... LOL
//func handleHistoricSlot(db sql.Database, serverAddress string, slot int) error {
// return handleFullSlot(db, serverAddress, slot, "", "", 0, "", "historic")
//}
// Update the SszSignedBeaconBlock and FullSignedBeaconBlock object with their respective values.
2022-05-17 14:32:31 +00:00
func ( ps * ProcessSlot ) getSignedBeaconBlock ( serverAddress string , vmCh <- chan * dt . VersionedUnmarshaler ) error {
2022-05-06 15:03:15 +00:00
var blockIdentifier string // Used to query the block
if ps . BlockRoot != "" {
blockIdentifier = ps . BlockRoot
} else if ps . Slot != 0 {
blockIdentifier = strconv . Itoa ( ps . Slot )
} else {
log . Error ( MissingIdentifiedError )
return fmt . Errorf ( MissingIdentifiedError )
}
2022-05-09 18:44:27 +00:00
blockEndpoint := serverAddress + BcBlockQueryEndpoint + blockIdentifier
2022-05-06 15:03:15 +00:00
var err error
var rc int
ps . SszSignedBeaconBlock , rc , err = querySsz ( blockEndpoint , strconv . Itoa ( ps . Slot ) )
if err != nil {
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Error ( "Unable to properly query the slot." )
return err
}
2022-05-17 14:32:31 +00:00
vm := <- vmCh
2022-05-06 15:03:15 +00:00
if rc != 200 {
2022-05-17 14:32:31 +00:00
ps . FullSignedBeaconBlock = & wrapper . Phase0SignedBeaconBlock { }
2022-05-13 12:48:31 +00:00
ps . SszSignedBeaconBlock = [ ] byte { }
ps . ParentBlockRoot = ""
ps . Status = "skipped"
return nil
2022-05-06 15:03:15 +00:00
}
2022-05-17 14:32:31 +00:00
if vm == nil {
return fmt . Errorf ( VersionedUnmarshalerError )
}
2022-05-06 15:03:15 +00:00
2022-05-17 14:32:31 +00:00
ps . FullSignedBeaconBlock , err = vm . UnmarshalBeaconBlock ( ps . SszSignedBeaconBlock )
2022-05-06 15:03:15 +00:00
if err != nil {
2022-05-17 14:32:31 +00:00
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Error ( "We are getting an error message when unmarshalling the SignedBeaconBlock." )
if ps . FullSignedBeaconBlock . Block ( ) . Slot ( ) == 0 {
2022-05-06 15:03:15 +00:00
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Error ( SlotUnmarshalError ( "SignedBeaconBlock" ) )
return fmt . Errorf ( SlotUnmarshalError ( "SignedBeaconBlock" ) )
2022-05-17 14:32:31 +00:00
} else if ps . FullSignedBeaconBlock . Block ( ) . ParentRoot ( ) == nil {
2022-05-06 15:03:15 +00:00
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Error ( ParentRootUnmarshalError )
return fmt . Errorf ( ParentRootUnmarshalError )
}
2022-05-12 19:44:05 +00:00
log . Warn ( "We received a processing error: " , err )
2022-05-06 15:03:15 +00:00
}
2022-05-17 14:32:31 +00:00
ps . ParentBlockRoot = "0x" + hex . EncodeToString ( ps . FullSignedBeaconBlock . Block ( ) . ParentRoot ( ) )
2022-05-06 15:03:15 +00:00
return nil
}
// Update the SszBeaconState and FullBeaconState object with their respective values.
2022-05-17 14:32:31 +00:00
func ( ps * ProcessSlot ) getBeaconState ( serverEndpoint string , vmCh chan <- * dt . VersionedUnmarshaler ) error {
2022-05-06 15:03:15 +00:00
var stateIdentifier string // Used to query the state
if ps . StateRoot != "" {
stateIdentifier = ps . StateRoot
} else if ps . Slot != 0 {
stateIdentifier = strconv . Itoa ( ps . Slot )
} else {
log . Error ( MissingIdentifiedError )
return fmt . Errorf ( MissingIdentifiedError )
}
2022-05-09 18:44:27 +00:00
stateEndpoint := serverEndpoint + BcStateQueryEndpoint + stateIdentifier
2022-05-06 15:03:15 +00:00
ps . SszBeaconState , _ , _ = querySsz ( stateEndpoint , strconv . Itoa ( ps . Slot ) )
2022-05-17 14:32:31 +00:00
versionedUnmarshaler , err := dt . FromState ( ps . SszBeaconState )
2022-05-06 15:03:15 +00:00
if err != nil {
2022-05-17 14:32:31 +00:00
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Error ( VersionedUnmarshalerError )
vmCh <- nil
return fmt . Errorf ( VersionedUnmarshalerError )
}
vmCh <- versionedUnmarshaler
ps . FullBeaconState , err = versionedUnmarshaler . UnmarshalBeaconState ( ps . SszBeaconState )
if err != nil {
loghelper . LogError ( err ) . Error ( "We are getting an error message when unmarshalling the BeaconState" )
if ps . FullBeaconState . Slot ( ) == 0 {
2022-05-06 15:03:15 +00:00
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Error ( SlotUnmarshalError ( "BeaconState" ) )
return fmt . Errorf ( SlotUnmarshalError ( "BeaconState" ) )
2022-05-17 14:32:31 +00:00
} else if hex . EncodeToString ( ps . FullBeaconState . Eth1Data ( ) . BlockHash ) == "" {
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Error ( MissingEth1Data )
return fmt . Errorf ( MissingEth1Data )
2022-05-06 15:03:15 +00:00
}
}
return nil
}
// Check to make sure that the previous block we processed is the parent of the current block.
2022-05-12 19:44:05 +00:00
func ( ps * ProcessSlot ) checkPreviousSlot ( previousSlot int , previousBlockRoot string , knownGapsTableIncrement int ) {
2022-05-17 14:32:31 +00:00
parentRoot := "0x" + hex . EncodeToString ( ps . FullSignedBeaconBlock . Block ( ) . ParentRoot ( ) )
if previousSlot == int ( ps . FullBeaconState . Slot ( ) ) {
2022-05-06 15:03:15 +00:00
log . WithFields ( log . Fields {
"slot" : ps . FullBeaconState . Slot ,
"fork" : true ,
} ) . Warn ( "A fork occurred! The previous slot and current slot match." )
2022-05-12 13:52:13 +00:00
writeReorgs ( ps . Db , strconv . Itoa ( ps . Slot ) , ps . BlockRoot , ps . Metrics )
2022-05-17 14:32:31 +00:00
} else if previousSlot + 1 != int ( ps . FullBeaconState . Slot ( ) ) {
2022-05-06 15:03:15 +00:00
log . WithFields ( log . Fields {
"previousSlot" : previousSlot ,
2022-05-17 19:10:47 +00:00
"currentSlot" : ps . FullBeaconState . Slot ( ) ,
2022-05-06 15:03:15 +00:00
} ) . Error ( "We skipped a few slots." )
2022-05-17 14:32:31 +00:00
writeKnownGaps ( ps . Db , knownGapsTableIncrement , previousSlot + 1 , int ( ps . FullBeaconState . Slot ( ) ) - 1 , fmt . Errorf ( "Gaps during head processing" ) , "headGaps" , ps . Metrics )
2022-05-06 15:03:15 +00:00
} else if previousBlockRoot != parentRoot {
log . WithFields ( log . Fields {
"previousBlockRoot" : previousBlockRoot ,
"currentBlockParent" : parentRoot ,
} ) . Error ( "The previousBlockRoot does not match the current blocks parent, an unprocessed fork might have occurred." )
2022-05-12 13:52:13 +00:00
writeReorgs ( ps . Db , strconv . Itoa ( previousSlot ) , parentRoot , ps . Metrics )
2022-05-06 15:03:15 +00:00
} else {
log . Debug ( "Previous Slot and Current Slot are one distance from each other." )
}
}
// Transforms all the raw data into DB models that can be written to the DB.
2022-05-13 12:48:31 +00:00
func ( ps * ProcessSlot ) createWriteObjects ( blockRootEndpoint string ) ( * DatabaseWriter , error ) {
2022-05-06 15:03:15 +00:00
var (
2022-05-13 12:48:31 +00:00
stateRoot string
blockRoot string
status string
eth1BlockHash string
2022-05-06 15:03:15 +00:00
)
2022-05-13 12:48:31 +00:00
if ps . Status == "skipped" {
stateRoot = ""
blockRoot = ""
eth1BlockHash = ""
2022-05-06 15:03:15 +00:00
} else {
2022-05-13 12:48:31 +00:00
if ps . StateRoot != "" {
stateRoot = ps . StateRoot
} else {
2022-05-17 14:32:31 +00:00
stateRoot = "0x" + hex . EncodeToString ( ps . FullSignedBeaconBlock . Block ( ) . StateRoot ( ) )
2022-05-13 12:48:31 +00:00
log . Debug ( "StateRoot: " , stateRoot )
}
2022-05-06 15:03:15 +00:00
2022-05-13 12:48:31 +00:00
if ps . BlockRoot != "" {
blockRoot = ps . BlockRoot
} else {
var err error
blockRoot , err = queryBlockRoot ( blockRootEndpoint , strconv . Itoa ( ps . Slot ) )
if err != nil {
return nil , err
}
}
2022-05-17 14:32:31 +00:00
eth1BlockHash = "0x" + hex . EncodeToString ( ps . FullSignedBeaconBlock . Block ( ) . Body ( ) . Eth1Data ( ) . BlockHash )
2022-05-06 15:03:15 +00:00
}
if ps . Status != "" {
status = ps . Status
} else {
status = "proposed"
}
2022-05-13 14:46:13 +00:00
dw , err := CreateDatabaseWrite ( ps . Db , ps . Slot , stateRoot , blockRoot , ps . ParentBlockRoot , eth1BlockHash , status , ps . SszSignedBeaconBlock , ps . SszBeaconState , ps . Metrics )
if err != nil {
return dw , err
}
2022-05-06 15:03:15 +00:00
2022-05-13 12:48:31 +00:00
return dw , nil
2022-05-06 15:03:15 +00:00
}