Skip to content
This repository has been archived by the owner on Oct 25, 2024. It is now read-only.

Add ssz block validation endpoint #152

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions cmd/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/validation"
"github.com/naoina/toml"
"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -93,11 +94,12 @@ type ethstatsConfig struct {
}

type gethConfig struct {
Eth ethconfig.Config
Node node.Config
Ethstats ethstatsConfig
Metrics metrics.Config
Builder builder.Config
Eth ethconfig.Config
Node node.Config
Ethstats ethstatsConfig
Metrics metrics.Config
Builder builder.Config
Validation validation.Config
}

func loadConfig(file string, cfg *gethConfig) error {
Expand Down Expand Up @@ -131,10 +133,11 @@ func defaultNodeConfig() node.Config {
func loadBaseConfig(ctx *cli.Context) gethConfig {
// Load defaults.
cfg := gethConfig{
Eth: ethconfig.Defaults,
Node: defaultNodeConfig(),
Metrics: metrics.DefaultConfig,
Builder: builder.DefaultConfig,
Eth: ethconfig.Defaults,
Node: defaultNodeConfig(),
Metrics: metrics.DefaultConfig,
Builder: builder.DefaultConfig,
Validation: validation.DefaultConfig,
}

// Load config file.
Expand Down Expand Up @@ -170,6 +173,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
// Apply builder flags
utils.SetBuilderConfig(ctx, &cfg.Builder)

// Apply validation flags
utils.SetValidationConfig(ctx, &cfg.Validation)

return stack, cfg
}

Expand Down Expand Up @@ -218,6 +224,10 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
utils.Fatalf("Failed to register the Block Validation API: %v", err)
}

if err := validation.Register(stack, eth, &cfg.Validation); err != nil {
utils.Fatalf("Failed to register the Validation API: %v", err)
}

// Configure GraphQL if requested
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
Expand Down
69 changes: 69 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import (
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/hashdb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/ethereum/go-ethereum/validation"
pcsclite "github.com/gballet/go-libpcsclite"
gopsutil "github.com/shirou/gopsutil/mem"
"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -810,6 +811,43 @@ var (
Category: flags.BuilderCategory,
}

// Block Validation Settings
BlockValidationEnabled = &cli.BoolFlag{
Name: "validation",
Usage: "Enable the block validation service",
Value: validation.DefaultConfig.Enabled,
Category: flags.ValidationCategory,
}

BlockValidationListenAddr = &cli.StringFlag{
Name: "validation.listen_addr",
Usage: "Listening address for block validation endpoint",
Value: validation.DefaultConfig.ListenAddr,
Category: flags.ValidationCategory,
}

BlockValidationBlacklistSourceFilePath = &cli.StringFlag{
Name: "validation.blacklist",
Usage: "Path to file containing blacklisted addresses, json-encoded list of strings. " +
"Builder will ignore transactions that touch mentioned addresses. This flag is also used for block validation API.\n" +
"NOTE: builder.blacklist is deprecated and will be removed in the future in favor of validation.blacklist",
Category: flags.ValidationCategory,
}

BlockValidationUseBalanceDiff = &cli.BoolFlag{
Name: "validation.use_balance_diff",
Usage: "Block validation API will use fee recipient balance difference for profit calculation.",
Value: validation.DefaultConfig.ExcludeWithdrawals,
Category: flags.ValidationCategory,
}

BlockValidationExcludeWithdrawals = &cli.BoolFlag{
Name: "validation.exclude_withdrawals",
Usage: "Block validation API will exclude CL withdrawals to the fee recipient from the balance delta.",
Value: validation.DefaultConfig.ExcludeWithdrawals,
Category: flags.ValidationCategory,
}

// RPC settings
IPCDisabledFlag = &cli.BoolFlag{
Name: "ipcdisable",
Expand Down Expand Up @@ -1641,6 +1679,37 @@ func SetBuilderConfig(ctx *cli.Context, cfg *builder.Config) {
cfg.BlockProcessorURL = ctx.String(BuilderBlockProcessorURL.Name)
}

// SetValidationConfig applies node-related command line flags to the block validation config.
func SetValidationConfig(ctx *cli.Context, cfg *validation.Config) {
if ctx.IsSet(BlockValidationEnabled.Name) {
cfg.Enabled = ctx.Bool(BlockValidationEnabled.Name)
}

// this flag should be deprecated in favor of the validation api
if ctx.IsSet(BuilderBlockValidationBlacklistSourceFilePath.Name) {
cfg.Blocklist = ctx.String(BuilderBlockValidationBlacklistSourceFilePath.Name)
}
if ctx.IsSet(BlockValidationBlacklistSourceFilePath.Name) {
cfg.Blocklist = ctx.String(BlockValidationBlacklistSourceFilePath.Name)
}

// this flag should be deprecated in favor of the validation api
if ctx.IsSet(BuilderBlockValidationUseBalanceDiff.Name) {
cfg.UseCoinbaseDiff = ctx.Bool(BuilderBlockValidationUseBalanceDiff.Name)
}
if ctx.IsSet(BlockValidationUseBalanceDiff.Name) {
cfg.UseCoinbaseDiff = ctx.Bool(BlockValidationUseBalanceDiff.Name)
}

// this flag should be deprecated in favor of the validation api
if ctx.IsSet(BuilderBlockValidationExcludeWithdrawals.Name) {
cfg.ExcludeWithdrawals = ctx.Bool(BuilderBlockValidationExcludeWithdrawals.Name)
}
if ctx.IsSet(BlockValidationExcludeWithdrawals.Name) {
cfg.ExcludeWithdrawals = ctx.Bool(BlockValidationExcludeWithdrawals.Name)
}
}

// SetNodeConfig applies node-related command line flags to the config.
func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
SetP2PConfig(ctx, &cfg.P2P)
Expand Down
1 change: 1 addition & 0 deletions internal/flags/categories.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
MetricsCategory = "METRICS AND STATS"
MiscCategory = "MISC"
BuilderCategory = "BUILDER"
ValidationCategory = "VALIDATION"
TestingCategory = "TESTING"
DeprecatedCategory = "ALIASED (deprecated)"
)
Expand Down
113 changes: 113 additions & 0 deletions validation/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package validation

import (
"compress/gzip"
"encoding/json"
"io"
"net/http"
"time"

validation "github.com/ethereum/go-ethereum/eth/block-validation"
"github.com/ethereum/go-ethereum/log"
"github.com/gorilla/mux"
)

type BlockValidationApi struct {
validationApi *validation.BlockValidationAPI
}

func (api *BlockValidationApi) getRouter() http.Handler {
r := mux.NewRouter()

r.HandleFunc("/", api.handleRoot).Methods(http.MethodGet)
r.HandleFunc("/validate/block_submission", api.handleBuilderSubmission).Methods(http.MethodPost)
return r
}

func (api *BlockValidationApi) handleRoot(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}

func (api *BlockValidationApi) handleBuilderSubmission(w http.ResponseWriter, req *http.Request) {
var prevTime, nextTime time.Time
receivedAt := time.Now().UTC()
prevTime = receivedAt

var err error
var r io.Reader = req.Body

isGzip := req.Header.Get("Content-Encoding") == "gzip"
if isGzip {
r, err = gzip.NewReader(req.Body)
if err != nil {
log.Error("could not create gzip reader", "err", err)
api.RespondError(w, http.StatusBadRequest, err.Error())
return
}
}

limitReader := io.LimitReader(r, 10*1024*1024) // 10 MB
requestPayloadBytes, err := io.ReadAll(limitReader)
if err != nil {
log.Error("could not read payload", "err", err)
api.RespondError(w, http.StatusBadRequest, err.Error())
return
}

nextTime = time.Now().UTC()
readTime := uint64(nextTime.Sub(prevTime).Microseconds())
prevTime = nextTime

payload := new(BuilderBlockValidationRequestV3)

// Check for SSZ encoding
contentType := req.Header.Get("Content-Type")
if contentType == "application/octet-stream" {
if err = payload.UnmarshalSSZ(requestPayloadBytes); err != nil {
log.Error("could not decode payload - SSZ", "err", err)
}
} else {
if err := json.Unmarshal(requestPayloadBytes, payload); err != nil {
log.Error("could not decode payload - JSON", "err", err)
api.RespondError(w, http.StatusBadRequest, err.Error())
return
}
}

nextTime = time.Now().UTC()
decodeTime := uint64(nextTime.Sub(prevTime).Microseconds())
prevTime = nextTime

// Validate the payload
err = api.validationApi.ValidateBuilderSubmissionV3(&validation.BuilderBlockValidationRequestV3{
SubmitBlockRequest: payload.SubmitBlockRequest,
ParentBeaconBlockRoot: payload.ParentBeaconBlockRoot,
RegisteredGasLimit: payload.RegisteredGasLimit,
})
validationTime := uint64(time.Now().UTC().Sub(prevTime).Microseconds())

l := log.New("isGzip", isGzip, "payloadBytes", len(requestPayloadBytes), "contentType", contentType,
"numBlobs", len(payload.BlobsBundle.Blobs), "numTx", len(payload.ExecutionPayload.Transactions),
"slot", payload.Message.Slot, "readTime", readTime, "decodeTime", decodeTime, "validationTime", validationTime)

if err != nil {
l.Info("Validation failed", "err", err)
api.RespondError(w, http.StatusBadRequest, err.Error())
return
}
l.Info("Validation successful")
w.WriteHeader(http.StatusOK)
}

func (api *BlockValidationApi) RespondError(w http.ResponseWriter, code int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)

// write the json response
response := HTTPErrorResp{code, message}
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Error("Couldn't write response", "error", err, "response", response)
http.Error(w, "", http.StatusInternalServerError)
}
}
Loading
Loading