Skip to content

Commit

Permalink
Allow alternative methods of proposer payments in validation api.
Browse files Browse the repository at this point in the history
  • Loading branch information
dvush committed Jul 24, 2023
1 parent 1e2ca34 commit 4b30a57
Show file tree
Hide file tree
Showing 11 changed files with 501 additions and 20 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ $ geth --help
--builder.validation_blacklist value
Path to file containing blacklisted addresses, json-encoded list of strings
--builder.validation_force_last_tx_payment (default: false)
Block validation API will enforce that the last tx in the block is payment to
the proposer.
--builder.validator_checks (default: false)
Enable the validator checks
Expand Down
2 changes: 2 additions & 0 deletions builder/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Config struct {
RemoteRelayEndpoint string `toml:",omitempty"`
SecondaryRemoteRelayEndpoints []string `toml:",omitempty"`
ValidationBlocklist string `toml:",omitempty"`
ValidationForceLastTxPayment bool `toml:",omitempty"`
BuilderRateLimitDuration string `toml:",omitempty"`
BuilderRateLimitMaxBurst int `toml:",omitempty"`
BuilderRateLimitResubmitInterval string `toml:",omitempty"`
Expand Down Expand Up @@ -48,6 +49,7 @@ var DefaultConfig = Config{
RemoteRelayEndpoint: "",
SecondaryRemoteRelayEndpoints: nil,
ValidationBlocklist: "",
ValidationForceLastTxPayment: false,
BuilderRateLimitDuration: RateLimitIntervalDefault.String(),
BuilderRateLimitMaxBurst: RateLimitBurstDefault,
EnableCancellations: false,
Expand Down
2 changes: 1 addition & 1 deletion builder/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *Config) error {
return fmt.Errorf("failed to load validation blocklist %w", err)
}
}
validator = blockvalidation.NewBlockValidationAPI(backend, accessVerifier)
validator = blockvalidation.NewBlockValidationAPI(backend, accessVerifier, cfg.ValidationForceLastTxPayment)
}

// Set up builder rate limiter based on environment variables or CLI flags.
Expand Down
3 changes: 3 additions & 0 deletions cmd/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
if ctx.IsSet(utils.BuilderBlockValidationBlacklistSourceFilePath.Name) {
bvConfig.BlacklistSourceFilePath = ctx.String(utils.BuilderBlockValidationBlacklistSourceFilePath.Name)
}
if ctx.IsSet(utils.BuilderBlockValidationForceLastTxPayment.Name) {
bvConfig.ForceLastTxPayment = ctx.Bool(utils.BuilderBlockValidationForceLastTxPayment.Name)
}

if err := blockvalidationapi.Register(stack, eth, bvConfig); err != nil {
utils.Fatalf("Failed to register the Block Validation API: %v", err)
Expand Down
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ var (
utils.BuilderEnabled,
utils.BuilderEnableValidatorChecks,
utils.BuilderBlockValidationBlacklistSourceFilePath,
utils.BuilderBlockValidationForceLastTxPayment,
utils.BuilderEnableLocalRelay,
utils.BuilderSecondsInSlot,
utils.BuilderSlotsInEpoch,
Expand Down
7 changes: 7 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,12 @@ var (
Value: "",
Category: flags.BuilderCategory,
}
BuilderBlockValidationForceLastTxPayment = &cli.BoolFlag{
Name: "builder.validation_force_last_tx_payment",
Usage: "Block validation API will enforce that the last tx in the block is payment to the proposer.",
Value: false,
Category: flags.BuilderCategory,
}
BuilderEnableLocalRelay = &cli.BoolFlag{
Name: "builder.local_relay",
Usage: "Enable the local relay",
Expand Down Expand Up @@ -1676,6 +1682,7 @@ func SetBuilderConfig(ctx *cli.Context, cfg *builder.Config) {
cfg.RemoteRelayEndpoint = ctx.String(BuilderRemoteRelayEndpoint.Name)
cfg.SecondaryRemoteRelayEndpoints = strings.Split(ctx.String(BuilderSecondaryRemoteRelayEndpoints.Name), ",")
cfg.ValidationBlocklist = ctx.String(BuilderBlockValidationBlacklistSourceFilePath.Name)
cfg.ValidationForceLastTxPayment = ctx.Bool(BuilderBlockValidationForceLastTxPayment.Name)
cfg.BuilderRateLimitDuration = ctx.String(BuilderRateLimitDuration.Name)
cfg.BuilderRateLimitMaxBurst = ctx.Int(BuilderRateLimitMaxBurst.Name)
cfg.BuilderSubmissionOffset = ctx.Duration(BuilderSubmissionOffset.Name)
Expand Down
54 changes: 53 additions & 1 deletion core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/utils"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/syncx"
Expand Down Expand Up @@ -2494,7 +2495,11 @@ func (bc *BlockChain) SetBlockValidatorAndProcessorForTesting(v Validator, p Pro
bc.processor = p
}

func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Address, expectedProfit *big.Int, registeredGasLimit uint64, vmConfig vm.Config) error {
// ValidatePayload validates the payload of the block.
// It returns nil if the payload is valid, otherwise it returns an error.
// - `forceLastTxPayment` if set to true, proposer payment is assumed to be in the last transaction of the block
// otherwise we use proposer balance changes after each transaction to calculate proposer payment (see details in the code)
func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Address, expectedProfit *big.Int, registeredGasLimit uint64, vmConfig vm.Config, forceLastTxPayment bool) error {
header := block.Header()
if err := bc.engine.VerifyHeader(bc, header, true); err != nil {
return err
Expand Down Expand Up @@ -2527,11 +2532,20 @@ func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Ad
// and dangling prefetcher, without defering each and holding on live refs.
defer statedb.StopPrefetcher()

// Inject balance change tracer
// This will allow us to check balance changes of the fee recipient without modifying `Process` method
balanceTracer := logger.NewBalanceChangeTracer(feeRecipient, vmConfig.Tracer, statedb)
vmConfig.Tracer = balanceTracer
vmConfig.Debug = true

receipts, _, usedGas, err := bc.processor.Process(block, statedb, vmConfig)
if err != nil {
return err
}

// Get fee recipient balance changes during each transaction execution
balanceChanges := balanceTracer.GetBalanceChanges()

if bc.Config().IsShanghai(header.Time) {
if header.WithdrawalsHash == nil {
return fmt.Errorf("withdrawals hash is missing")
Expand All @@ -2554,6 +2568,44 @@ func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Ad
return err
}

// Validate proposer payment

if !forceLastTxPayment {
// We calculate the proposer payment by counting balance increases of the fee recipient account after each transaction.
// If the balance decreases for the fee recipient for some transaction, we ignore it,
// but we still count profit from the tip of this transaction if the fee recipient is also a coinbase.
// If this method of profit calculation fails for some reason, we fall back to the old method of calculating proposer payment
// where we look at the last transaction in the block.
feeRecipientProfit := big.NewInt(0)
for i, balanceChange := range balanceChanges {
if balanceChange.Sign() > 0 {
feeRecipientProfit.Add(feeRecipientProfit, balanceChange)
} else {
// If the fee recipient balance decreases, it means that the fee recipient sent eth out of the account
// or paid for the gas of the transaction.
// In this case, we ignore the balance change, but we still count fee profit as a positive balance change if we can.
if block.Coinbase() == feeRecipient {
if i >= len(block.Transactions()) {
log.Error("transactions length is less than balance changes length")
break
}
tip, err := block.Transactions()[i].EffectiveGasTip(block.BaseFee())
if err != nil {
log.Error("failed to calculate tip", "err", err)
break
}
profit := tip.Mul(tip, new(big.Int).SetUint64(receipts[i].GasUsed))
feeRecipientProfit.Add(feeRecipientProfit, profit)
}
}
}
if feeRecipientProfit.Cmp(expectedProfit) >= 0 {
return nil
}

log.Warn("proposer payment not enough, trying last tx payment validation", "expected", expectedProfit, "actual", feeRecipientProfit)
}

if len(receipts) == 0 {
return errors.New("no proposer payment receipt")
}
Expand Down
17 changes: 11 additions & 6 deletions eth/block-validation/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ func NewAccessVerifierFromFile(path string) (*AccessVerifier, error) {

type BlockValidationConfig struct {
BlacklistSourceFilePath string
// If set to true, proposer payment is assumed to be in the last transaction of the block.
ForceLastTxPayment bool
}

// Register adds catalyst APIs to the full node.
Expand All @@ -104,7 +106,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg BlockValidationConfig
stack.RegisterAPIs([]rpc.API{
{
Namespace: "flashbots",
Service: NewBlockValidationAPI(backend, accessVerifier),
Service: NewBlockValidationAPI(backend, accessVerifier, cfg.ForceLastTxPayment),
},
})
return nil
Expand All @@ -113,14 +115,17 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg BlockValidationConfig
type BlockValidationAPI struct {
eth *eth.Ethereum
accessVerifier *AccessVerifier
// If set to true, proposer payment is assumed to be in the last transaction of the block.
forceLastTxPayment bool
}

// 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 {
func NewBlockValidationAPI(eth *eth.Ethereum, accessVerifier *AccessVerifier, forceLastTxPayment bool) *BlockValidationAPI {
return &BlockValidationAPI{
eth: eth,
accessVerifier: accessVerifier,
eth: eth,
accessVerifier: accessVerifier,
forceLastTxPayment: forceLastTxPayment,
}
}

Expand Down Expand Up @@ -180,7 +185,7 @@ func (api *BlockValidationAPI) ValidateBuilderSubmissionV1(params *BuilderBlockV
vmconfig = vm.Config{Tracer: tracer, Debug: true}
}

err = api.eth.BlockChain().ValidatePayload(block, feeRecipient, expectedProfit, params.RegisteredGasLimit, vmconfig)
err = api.eth.BlockChain().ValidatePayload(block, feeRecipient, expectedProfit, params.RegisteredGasLimit, vmconfig, api.forceLastTxPayment)
if err != nil {
log.Error("invalid payload", "hash", payload.BlockHash.String(), "number", payload.BlockNumber, "parentHash", payload.ParentHash.String(), "err", err)
return err
Expand Down Expand Up @@ -272,7 +277,7 @@ func (api *BlockValidationAPI) ValidateBuilderSubmissionV2(params *BuilderBlockV
vmconfig = vm.Config{Tracer: tracer, Debug: true}
}

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

0 comments on commit 4b30a57

Please sign in to comment.