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

Commit

Permalink
Implement block validation API
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruteri authored and avalonche committed Mar 22, 2023
1 parent dcb2961 commit 12a7df8
Show file tree
Hide file tree
Showing 6 changed files with 552 additions and 1 deletion.
2 changes: 2 additions & 0 deletions beacon/engine/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie"

boostTypes "github.com/flashbots/go-boost-utils/types"
)

//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
Expand Down
6 changes: 5 additions & 1 deletion cmd/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
// Configure log filter RPC API.
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)

// Configure GraphQL if requested.
if err := blockvalidationapi.Register(stack, eth, ctx); err != nil {
utils.Fatalf("Failed to register the Block Validation API: %v", err)
}

// Configure GraphQL if requested
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
}
Expand Down
8 changes: 8 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,14 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Value: metrics.DefaultConfig.InfluxDBOrganization,
Category: flags.MetricsCategory,
}

// Builder API flags
BuilderBlockValidationBlacklistSourceFilePath = &cli.StringFlag{
Name: "builder.validation_blacklist",
Usage: "Path to file containing blacklisted addresses, json-encoded list of strings. Default assumes CWD is repo's root",
Value: "ofac_blacklist.json",
Category: flags.EthCategory,
}
)

var (
Expand Down
175 changes: 175 additions & 0 deletions eth/block-validation/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package blockvalidation

import (
"encoding/json"
"errors"
"fmt"
"math/big"
"os"

"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"

boostTypes "github.com/flashbots/go-boost-utils/types"
)

type BlacklistedAddresses []common.Address

type AccessVerifier struct {
blacklistedAddresses map[common.Address]struct{}
}

func (a *AccessVerifier) verifyTraces(tracer *logger.AccessListTracer) error {
log.Info("x", "tracer.AccessList()", tracer.AccessList())
for _, accessTuple := range tracer.AccessList() {
// TODO: should we ignore common.Address{}?
if _, found := a.blacklistedAddresses[accessTuple.Address]; found {
log.Info("bundle accesses blacklisted address", "address", accessTuple.Address)
return fmt.Errorf("blacklisted address %s in execution trace", accessTuple.Address.String())
}
}

return nil
}

func (a *AccessVerifier) verifyTransactions(signer types.Signer, txs types.Transactions) error {
for _, tx := range txs {
from, err := signer.Sender(tx)
if err == nil {
if _, present := a.blacklistedAddresses[from]; present {
return fmt.Errorf("transaction from blacklisted address %s", from.String())
}
}
to := tx.To()
if to != nil {
if _, present := a.blacklistedAddresses[*to]; present {
return fmt.Errorf("transaction to blacklisted address %s", to.String())
}
}
}
return nil
}

func NewAccessVerifierFromFile(path string) (*AccessVerifier, error) {
bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}

var ba BlacklistedAddresses
if err := json.Unmarshal(bytes, &ba); err != nil {
return nil, err
}

blacklistedAddresses := make(map[common.Address]struct{}, len(ba))
for _, address := range ba {
blacklistedAddresses[address] = struct{}{}
}

return &AccessVerifier{
blacklistedAddresses: blacklistedAddresses,
}, nil
}

// Register adds catalyst APIs to the full node.
func Register(stack *node.Node, backend *eth.Ethereum, ctx *cli.Context) error {
var accessVerifier *AccessVerifier
if ctx.IsSet(utils.BuilderBlockValidationBlacklistSourceFilePath.Name) {
var err error
accessVerifier, err = NewAccessVerifierFromFile(ctx.String(utils.BuilderBlockValidationBlacklistSourceFilePath.Name))
if err != nil {
return err
}
}

stack.RegisterAPIs([]rpc.API{
{
Namespace: "flashbots",
Service: NewBlockValidationAPI(backend, accessVerifier),
},
})
return nil
}

type BlockValidationAPI struct {
eth *eth.Ethereum
accessVerifier *AccessVerifier
}

// NewConsensusAPI creates a new consensus api for the given backend.
// The underlying blockchain needs to have a valid terminal total difficulty set.
func NewBlockValidationAPI(eth *eth.Ethereum, accessVerifier *AccessVerifier) *BlockValidationAPI {
return &BlockValidationAPI{
eth: eth,
accessVerifier: accessVerifier,
}
}

func (api *BlockValidationAPI) ValidateBuilderSubmissionV1(params *boostTypes.BuilderSubmitBlockRequest) error {
// TODO: fuzztest, make sure the validation is sound
// TODO: handle context!

if params.ExecutionPayload == nil {
return errors.New("nil execution payload")
}
payload := params.ExecutionPayload
block, err := beacon.ExecutionPayloadToBlock(payload)
if err != nil {
return err
}

if params.Message.ParentHash != boostTypes.Hash(block.ParentHash()) {
return fmt.Errorf("incorrect ParentHash %s, expected %s", params.Message.ParentHash.String(), block.ParentHash().String())
}

if params.Message.BlockHash != boostTypes.Hash(block.Hash()) {
return fmt.Errorf("incorrect BlockHash %s, expected %s", params.Message.BlockHash.String(), block.Hash().String())
}

if params.Message.GasLimit != block.GasLimit() {
return fmt.Errorf("incorrect GasLimit %d, expected %d", params.Message.GasLimit, block.GasLimit())
}

if params.Message.GasUsed != block.GasUsed() {
return fmt.Errorf("incorrect GasUsed %d, expected %d", params.Message.GasUsed, block.GasUsed())
}

feeRecipient := common.BytesToAddress(params.Message.ProposerFeeRecipient[:])
expectedProfit := params.Message.Value.BigInt()

var vmconfig vm.Config
var tracer *logger.AccessListTracer = nil
if api.accessVerifier != nil {
if err := api.accessVerifier.verifyTransactions(types.LatestSigner(api.eth.BlockChain().Config()), block.Transactions()); err != nil {
return err
}
isPostMerge := true // the call is PoS-native
precompiles := vm.ActivePrecompiles(api.eth.APIBackend.ChainConfig().Rules(new(big.Int).SetUint64(params.ExecutionPayload.BlockNumber), isPostMerge))
tracer = logger.NewAccessListTracer(nil, common.Address{}, common.Address{}, precompiles)
vmconfig = vm.Config{Tracer: tracer, Debug: true}
}

err = api.eth.BlockChain().ValidatePayload(block, feeRecipient, expectedProfit, vmconfig)
if err != nil {
log.Error("invalid payload", "hash", payload.BlockHash.String(), "number", payload.BlockNumber, "parentHash", payload.ParentHash.String(), "err", err)
return err
}

if api.accessVerifier != nil && tracer != nil {
if err := api.accessVerifier.verifyTraces(tracer); err != nil {
return err
}
}

log.Info("validated block", "hash", block.Hash(), "number", block.NumberU64(), "parentHash", block.ParentHash())
return nil
}
Loading

0 comments on commit 12a7df8

Please sign in to comment.