2022-05-18 16:12:54 +00:00
// VulcanizeDB
// Copyright © 2022 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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-17 20:05:15 +00:00
"context"
2022-05-06 15:03:15 +00:00
"encoding/hex"
"fmt"
"strconv"
"strings"
2022-06-06 13:02:43 +00:00
"github.com/jackc/pgx/v4"
2022-05-17 20:05:15 +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 20:05:15 +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-17 20:05:15 +00:00
"golang.org/x/sync/errgroup"
2022-05-06 15:03:15 +00:00
)
var (
2022-05-17 20:05:15 +00:00
ParentRootUnmarshalError = "Unable to properly unmarshal the ParentRoot field in the SignedBeaconBlock."
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 20:05:15 +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 20:05:15 +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-24 20:18:55 +00:00
// It will return the error and error process. The error process is used for providing reach detail to the
// known_gaps table.
2022-06-09 18:53:35 +00:00
func processFullSlot ( ctx context . Context , db sql . Database , serverAddress string , slot int , blockRoot string , stateRoot string , previousSlot int , previousBlockRoot string , headOrHistoric string , metrics * BeaconClientMetrics , knownGapsTableIncrement int , checkDb bool ) ( error , string ) {
select {
case <- ctx . Done ( ) :
return nil , ""
default :
ps := & ProcessSlot {
Slot : slot ,
BlockRoot : blockRoot ,
StateRoot : stateRoot ,
HeadOrHistoric : headOrHistoric ,
Db : db ,
Metrics : metrics ,
}
2022-05-17 20:05:15 +00:00
2022-06-09 18:53:35 +00:00
g , _ := errgroup . WithContext ( context . Background ( ) )
vUnmarshalerCh := make ( chan * dt . VersionedUnmarshaler , 1 )
// Get the BeaconState.
g . Go ( func ( ) error {
select {
case <- ctx . Done ( ) :
return nil
default :
err := ps . getBeaconState ( serverAddress , vUnmarshalerCh )
if err != nil {
return err
}
return nil
}
} )
// Get the SignedBeaconBlock.
g . Go ( func ( ) error {
select {
case <- ctx . Done ( ) :
return nil
default :
err := ps . getSignedBeaconBlock ( serverAddress , vUnmarshalerCh )
if err != nil {
return err
}
return nil
}
} )
if err := g . Wait ( ) ; err != nil {
return err , "processSlot"
2022-05-17 20:05:15 +00:00
}
2022-05-06 15:03:15 +00:00
2022-06-09 18:53:35 +00:00
finalBlockRoot , finalStateRoot , finalEth1BlockHash , err := ps . provideFinalHash ( )
2022-05-17 20:05:15 +00:00
if err != nil {
2022-06-09 18:53:35 +00:00
return err , "CalculateBlockRoot"
}
if checkDb {
inDb , err := IsSlotInDb ( ctx , ps . Db , strconv . Itoa ( ps . Slot ) , finalBlockRoot , finalStateRoot )
if err != nil {
return err , "checkDb"
}
if inDb {
log . WithField ( "slot" , slot ) . Info ( "Slot already in the DB." )
return nil , ""
}
2022-05-17 20:05:15 +00:00
}
2022-05-06 15:03:15 +00:00
2022-06-09 18:53:35 +00:00
// Get this object ready to write
dw , err := ps . createWriteObjects ( finalBlockRoot , finalStateRoot , finalEth1BlockHash )
2022-06-06 13:02:43 +00:00
if err != nil {
2022-06-09 18:53:35 +00:00
return err , "blockRoot"
2022-06-06 13:02:43 +00:00
}
2022-06-09 18:53:35 +00:00
// Write the object to the DB.
defer func ( ) {
err := dw . Tx . Rollback ( dw . Ctx )
if err != nil && err != pgx . ErrTxClosed {
loghelper . LogError ( err ) . Error ( "We were unable to Rollback a transaction" )
}
} ( )
err = dw . transactFullSlot ( )
if err != nil {
return err , "processSlot"
2022-06-06 13:02:43 +00:00
}
2022-06-09 18:53:35 +00:00
// Handle any reorgs or skipped slots.
headOrHistoric = strings . ToLower ( headOrHistoric )
if headOrHistoric != "head" && headOrHistoric != "historic" {
return fmt . Errorf ( "headOrHistoric must be either historic or head!" ) , ""
}
if ps . HeadOrHistoric == "head" && previousSlot != 0 && previousBlockRoot != "" && ps . Status != "skipped" {
ps . checkPreviousSlot ( dw . Tx , dw . Ctx , previousSlot , previousBlockRoot , knownGapsTableIncrement )
2022-06-06 13:02:43 +00:00
}
2022-05-12 13:52:13 +00:00
2022-06-09 18:53:35 +00:00
// Commit the transaction
if err = dw . Tx . Commit ( dw . Ctx ) ; err != nil {
return err , "transactionCommit"
}
2022-06-06 13:02:43 +00:00
2022-06-09 18:53:35 +00:00
return nil , ""
2022-05-06 15:03:15 +00:00
}
}
// Handle a slot that is at head. A wrapper function for calling `handleFullSlot`.
2022-06-06 13:02:43 +00:00
func processHeadSlot ( db sql . Database , serverAddress string , slot int , blockRoot string , stateRoot string , previousSlot int , previousBlockRoot string , metrics * BeaconClientMetrics , knownGapsTableIncrement int , checkDb bool ) {
2022-05-24 20:18:55 +00:00
// Get the knownGaps at startUp.
if previousSlot == 0 && previousBlockRoot == "" {
writeStartUpGaps ( db , knownGapsTableIncrement , slot , metrics )
}
2022-06-09 18:53:35 +00:00
err , errReason := processFullSlot ( context . Background ( ) , db , serverAddress , slot , blockRoot , stateRoot , previousSlot , previousBlockRoot , "head" , metrics , knownGapsTableIncrement , checkDb )
2022-05-24 20:18:55 +00:00
if err != nil {
writeKnownGaps ( db , knownGapsTableIncrement , slot , slot , err , errReason , metrics )
}
2022-05-06 15:03:15 +00:00
}
// Handle a historic slot. A wrapper function for calling `handleFullSlot`.
2022-06-09 18:53:35 +00:00
func handleHistoricSlot ( ctx context . Context , db sql . Database , serverAddress string , slot int , metrics * BeaconClientMetrics , checkDb bool ) ( error , string ) {
return processFullSlot ( ctx , db , serverAddress , slot , "" , "" , 0 , "" , "historic" , metrics , 1 , checkDb )
2022-05-24 20:18:55 +00:00
}
2022-05-06 15:03:15 +00:00
// Update the SszSignedBeaconBlock and FullSignedBeaconBlock object with their respective values.
2022-05-17 20:05:15 +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 {
2022-05-24 20:18:55 +00:00
blockIdentifier = strconv . Itoa ( ps . Slot )
2022-05-06 15:03:15 +00:00
}
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 20:05:15 +00:00
vm := <- vmCh
2022-05-06 15:03:15 +00:00
if rc != 200 {
2022-05-17 20:05:15 +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 20:05:15 +00:00
if vm == nil {
return fmt . Errorf ( VersionedUnmarshalerError )
}
2022-05-06 15:03:15 +00:00
2022-05-17 20:05:15 +00:00
ps . FullSignedBeaconBlock , err = vm . UnmarshalBeaconBlock ( ps . SszSignedBeaconBlock )
2022-05-06 15:03:15 +00:00
if err != nil {
2022-06-08 14:26:27 +00:00
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Warn ( "Unable to process the slots SignedBeaconBlock" )
return nil
2022-05-06 15:03:15 +00:00
}
2022-05-17 20:05:15 +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 20:05:15 +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 {
2022-05-24 20:18:55 +00:00
stateIdentifier = strconv . Itoa ( ps . Slot )
2022-05-06 15:03:15 +00:00
}
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 20:05:15 +00:00
versionedUnmarshaler , err := dt . FromState ( ps . SszBeaconState )
if err != nil {
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Error ( VersionedUnmarshalerError )
vmCh <- nil
return fmt . Errorf ( VersionedUnmarshalerError )
}
vmCh <- versionedUnmarshaler
ps . FullBeaconState , err = versionedUnmarshaler . UnmarshalBeaconState ( ps . SszBeaconState )
2022-05-06 15:03:15 +00:00
if err != nil {
2022-06-08 14:26:27 +00:00
loghelper . LogSlotError ( strconv . Itoa ( ps . Slot ) , err ) . Error ( "Unable to process the slots BeaconState" )
return err
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-06-06 13:02:43 +00:00
func ( ps * ProcessSlot ) checkPreviousSlot ( tx sql . Tx , ctx context . Context , previousSlot int , previousBlockRoot string , knownGapsTableIncrement int ) {
2022-05-17 20:05:15 +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 {
2022-05-24 20:18:55 +00:00
"slot" : ps . FullBeaconState . Slot ( ) ,
2022-05-06 15:03:15 +00:00
"fork" : true ,
} ) . Warn ( "A fork occurred! The previous slot and current slot match." )
2022-06-06 13:02:43 +00:00
transactReorgs ( tx , ctx , strconv . Itoa ( ps . Slot ) , ps . BlockRoot , ps . Metrics )
2022-05-24 20:18:55 +00:00
} else if previousSlot > int ( ps . FullBeaconState . Slot ( ) ) {
log . WithFields ( log . Fields {
"previousSlot" : previousSlot ,
"curSlot" : int ( ps . FullBeaconState . Slot ( ) ) ,
} ) . Warn ( "We noticed the previous slot is greater than the current slot." )
2022-05-17 20:05:15 +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 20:05:15 +00:00
"currentSlot" : ps . FullBeaconState . Slot ( ) ,
2022-05-06 15:03:15 +00:00
} ) . Error ( "We skipped a few slots." )
2022-06-06 13:02:43 +00:00
transactKnownGaps ( tx , ctx , 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-06-06 13:02:43 +00:00
transactReorgs ( tx , ctx , 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-06-06 13:02:43 +00:00
func ( ps * ProcessSlot ) createWriteObjects ( blockRoot , stateRoot , eth1BlockHash string ) ( * DatabaseWriter , error ) {
var status string
if ps . Status != "" {
status = ps . Status
} else {
status = "proposed"
}
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
}
return dw , nil
}
// This function will return the final blockRoot, stateRoot, and eth1BlockHash that will be
// used to write to a DB
func ( ps * ProcessSlot ) provideFinalHash ( ) ( string , string , string , error ) {
2022-05-06 15:03:15 +00:00
var (
2022-05-13 12:48:31 +00:00
stateRoot string
blockRoot 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 20:05:15 +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
2022-05-24 20:18:55 +00:00
rawBlockRoot , err := ps . FullSignedBeaconBlock . Block ( ) . HashTreeRoot ( )
//blockRoot, err = queryBlockRoot(blockRootEndpoint, strconv.Itoa(ps.Slot))
2022-05-13 12:48:31 +00:00
if err != nil {
2022-06-06 13:02:43 +00:00
return "" , "" , "" , err
2022-05-13 12:48:31 +00:00
}
2022-05-24 20:18:55 +00:00
blockRoot = "0x" + hex . EncodeToString ( rawBlockRoot [ : ] )
2022-06-06 13:02:43 +00:00
log . WithFields ( log . Fields { "blockRoot" : blockRoot } ) . Debug ( "Block Root from ssz" )
2022-05-13 12:48:31 +00:00
}
2022-05-17 20:05:15 +00:00
eth1BlockHash = "0x" + hex . EncodeToString ( ps . FullSignedBeaconBlock . Block ( ) . Body ( ) . Eth1Data ( ) . BlockHash )
2022-05-06 15:03:15 +00:00
}
2022-06-06 13:02:43 +00:00
return blockRoot , stateRoot , eth1BlockHash , nil
2022-05-06 15:03:15 +00:00
}