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

Commit

Permalink
Add block value to validation api
Browse files Browse the repository at this point in the history
  • Loading branch information
avalonche committed Jun 25, 2024
1 parent e5e16e1 commit 03a9c50
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 52 deletions.
2 changes: 1 addition & 1 deletion builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ func (b *Builder) onSealedBlock(opts SubmitBlockOpts) error {
case spec.DataVersionCapella:
err = b.validator.ValidateBuilderSubmissionV2(&blockvalidation.BuilderBlockValidationRequestV2{SubmitBlockRequest: *versionedBlockRequest.Capella, RegisteredGasLimit: opts.ValidatorData.GasLimit})
case spec.DataVersionDeneb:
err = b.validator.ValidateBuilderSubmissionV3(&blockvalidation.BuilderBlockValidationRequestV3{SubmitBlockRequest: *versionedBlockRequest.Deneb, RegisteredGasLimit: opts.ValidatorData.GasLimit, ParentBeaconBlockRoot: *opts.Block.BeaconRoot()})
_, err = b.validator.ValidateBuilderSubmissionV3(&blockvalidation.BuilderBlockValidationRequestV3{SubmitBlockRequest: *versionedBlockRequest.Deneb, RegisteredGasLimit: opts.ValidatorData.GasLimit, ParentBeaconBlockRoot: *opts.Block.BeaconRoot()})
}
if err != nil {
log.Error("could not validate block", "version", dataVersion.String(), "err", err)
Expand Down
53 changes: 29 additions & 24 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2454,31 +2454,31 @@ func (bc *BlockChain) SetBlockValidatorAndProcessorForTesting(v Validator, p Pro
// - `useBalanceDiffProfit` if set to false, proposer payment is assumed to be in the last transaction of the block
// otherwise we use proposer balance changes after the block to calculate proposer payment (see details in the code)
// - `excludeWithdrawals` if set to true, withdrawals to the fee recipient are excluded from the balance change
func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Address, expectedProfit *big.Int, registeredGasLimit uint64, vmConfig vm.Config, useBalanceDiffProfit, excludeWithdrawals bool) error {
func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Address, expectedProfit *big.Int, registeredGasLimit uint64, vmConfig vm.Config, useBalanceDiffProfit, excludeWithdrawals bool) (*uint256.Int, error) {
header := block.Header()
if err := bc.engine.VerifyHeader(bc, header); err != nil {
return err
return nil, err
}

current := bc.CurrentBlock()
reorg, err := bc.forker.ReorgNeeded(current, header)
if err == nil && reorg {
return errors.New("block requires a reorg")
return nil, errors.New("block requires a reorg")
}

parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return errors.New("parent not found")
return nil, errors.New("parent not found")
}

calculatedGasLimit := CalcGasLimit(parent.GasLimit, registeredGasLimit)
if calculatedGasLimit != header.GasLimit {
return errors.New("incorrect gas limit set")
return nil, errors.New("incorrect gas limit set")
}

statedb, err := bc.StateAt(parent.Root)
if err != nil {
return err
return nil, err
}

// The chain importer is starting and stopping trie prefetchers. If a bad
Expand All @@ -2491,7 +2491,7 @@ func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Ad

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

feeRecipientBalanceDelta := new(uint256.Int).Set(statedb.GetBalance(feeRecipient))
Expand All @@ -2507,24 +2507,24 @@ func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Ad

if bc.Config().IsShanghai(header.Number, header.Time) {
if header.WithdrawalsHash == nil {
return fmt.Errorf("withdrawals hash is missing")
return nil, fmt.Errorf("withdrawals hash is missing")
}
// withdrawals hash and withdrawals validated later in ValidateBody
} else {
if header.WithdrawalsHash != nil {
return fmt.Errorf("withdrawals hash present before shanghai")
return nil, fmt.Errorf("withdrawals hash present before shanghai")
}
if block.Withdrawals() != nil {
return fmt.Errorf("withdrawals list present in block body before shanghai")
return nil, fmt.Errorf("withdrawals list present in block body before shanghai")
}
}

if err := bc.validator.ValidateBody(block); err != nil {
return err
return nil, err
}

if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
return err
return nil, err
}

// Validate proposer payment
Expand All @@ -2536,56 +2536,61 @@ func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Ad
if feeRecipientBalanceDelta.Cmp(uint256ExpectedProfit) > 0 {
log.Warn("builder claimed profit is lower than calculated profit", "expected", expectedProfit, "actual", feeRecipientBalanceDelta)
}
return nil
return feeRecipientBalanceDelta, nil
}
log.Warn("proposer payment not enough, trying last tx payment validation", "expected", expectedProfit, "actual", feeRecipientBalanceDelta)
}
}

if len(receipts) == 0 {
return errors.New("no proposer payment receipt")
return nil, errors.New("no proposer payment receipt")
}

lastReceipt := receipts[len(receipts)-1]
if lastReceipt.Status != types.ReceiptStatusSuccessful {
return errors.New("proposer payment not successful")
return nil, errors.New("proposer payment not successful")
}
txIndex := lastReceipt.TransactionIndex
if txIndex+1 != uint(len(block.Transactions())) {
return fmt.Errorf("proposer payment index not last transaction in the block (%d of %d)", txIndex, len(block.Transactions())-1)
return nil, fmt.Errorf("proposer payment index not last transaction in the block (%d of %d)", txIndex, len(block.Transactions())-1)
}

paymentTx := block.Transaction(lastReceipt.TxHash)
if paymentTx == nil {
return errors.New("payment tx not in the block")
return nil, errors.New("payment tx not in the block")
}

paymentTo := paymentTx.To()
if paymentTo == nil || *paymentTo != feeRecipient {
return fmt.Errorf("payment tx not to the proposers fee recipient (%v)", paymentTo)
return nil, fmt.Errorf("payment tx not to the proposers fee recipient (%v)", paymentTo)
}

if paymentTx.Value().Cmp(expectedProfit) != 0 {
return fmt.Errorf("inaccurate payment %s, expected %s", paymentTx.Value().String(), expectedProfit.String())
return nil, fmt.Errorf("inaccurate payment %s, expected %s", paymentTx.Value().String(), expectedProfit.String())
}

if len(paymentTx.Data()) != 0 {
return fmt.Errorf("malformed proposer payment, contains calldata")
return nil, fmt.Errorf("malformed proposer payment, contains calldata")
}

if paymentTx.GasPrice().Cmp(block.BaseFee()) != 0 {
return fmt.Errorf("malformed proposer payment, gas price not equal to base fee")
return nil, fmt.Errorf("malformed proposer payment, gas price not equal to base fee")
}

if paymentTx.GasTipCap().Cmp(block.BaseFee()) != 0 && paymentTx.GasTipCap().Sign() != 0 {
return fmt.Errorf("malformed proposer payment, unexpected gas tip cap")
return nil, fmt.Errorf("malformed proposer payment, unexpected gas tip cap")
}

if paymentTx.GasFeeCap().Cmp(block.BaseFee()) != 0 {
return fmt.Errorf("malformed proposer payment, unexpected gas fee cap")
return nil, fmt.Errorf("malformed proposer payment, unexpected gas fee cap")
}

return nil
blockValue, ok := uint256.FromBig(paymentTx.Value())
if !ok {
return nil, fmt.Errorf("malformed proposer payment, value too large")
}

return blockValue, nil
}

// SetTrieFlushInterval configures how often in-memory tries are persisted to disk.
Expand Down
47 changes: 27 additions & 20 deletions eth/block-validation/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"
)

type BlacklistedAddresses []common.Address
Expand Down Expand Up @@ -153,7 +154,8 @@ func (api *BlockValidationAPI) ValidateBuilderSubmissionV1(params *BuilderBlockV
return err
}

return api.validateBlock(block, params.Message, params.RegisteredGasLimit)
_, err = api.validateBlock(block, params.Message, params.RegisteredGasLimit)
return err
}

type BuilderBlockValidationRequestV2 struct {
Expand Down Expand Up @@ -192,7 +194,8 @@ func (api *BlockValidationAPI) ValidateBuilderSubmissionV2(params *BuilderBlockV
return err
}

return api.validateBlock(block, params.Message, params.RegisteredGasLimit)
_, err = api.validateBlock(block, params.Message, params.RegisteredGasLimit)
return err
}

type BuilderBlockValidationRequestV3 struct {
Expand All @@ -201,6 +204,10 @@ type BuilderBlockValidationRequestV3 struct {
RegisteredGasLimit uint64 `json:"registered_gas_limit,string"`
}

type BuilderBlockValidationResponse struct {
BlockValue *uint256.Int `json:"block_value"`
}

func (r *BuilderBlockValidationRequestV3) UnmarshalJSON(data []byte) error {
params := &struct {
ParentBeaconBlockRoot common.Hash `json:"parent_beacon_block_root"`
Expand All @@ -222,44 +229,44 @@ func (r *BuilderBlockValidationRequestV3) UnmarshalJSON(data []byte) error {
return nil
}

func (api *BlockValidationAPI) ValidateBuilderSubmissionV3(params *BuilderBlockValidationRequestV3) error {
func (api *BlockValidationAPI) ValidateBuilderSubmissionV3(params *BuilderBlockValidationRequestV3) (*BuilderBlockValidationResponse, error) {
// TODO: fuzztest, make sure the validation is sound
payload := params.ExecutionPayload
blobsBundle := params.BlobsBundle
log.Info("blobs bundle", "blobs", len(blobsBundle.Blobs), "commits", len(blobsBundle.Commitments), "proofs", len(blobsBundle.Proofs))
block, err := engine.ExecutionPayloadV3ToBlock(payload, blobsBundle, params.ParentBeaconBlockRoot)
if err != nil {
return err
return nil, err
}

err = api.validateBlock(block, params.Message, params.RegisteredGasLimit)
blockValue, err := api.validateBlock(block, params.Message, params.RegisteredGasLimit)
if err != nil {
log.Error("invalid payload", "hash", block.Hash, "number", block.NumberU64(), "parentHash", block.ParentHash, "err", err)
return err
return nil, err
}
err = validateBlobsBundle(block.Transactions(), blobsBundle)
if err != nil {
log.Error("invalid blobs bundle", "err", err)
return err
return nil, err
}
return nil
return &BuilderBlockValidationResponse{blockValue}, nil
}

func (api *BlockValidationAPI) validateBlock(block *types.Block, msg *builderApiV1.BidTrace, registeredGasLimit uint64) error {
func (api *BlockValidationAPI) validateBlock(block *types.Block, msg *builderApiV1.BidTrace, registeredGasLimit uint64) (*uint256.Int, error) {
if msg.ParentHash != phase0.Hash32(block.ParentHash()) {
return fmt.Errorf("incorrect ParentHash %s, expected %s", msg.ParentHash.String(), block.ParentHash().String())
return nil, fmt.Errorf("incorrect ParentHash %s, expected %s", msg.ParentHash.String(), block.ParentHash().String())
}

if msg.BlockHash != phase0.Hash32(block.Hash()) {
return fmt.Errorf("incorrect BlockHash %s, expected %s", msg.BlockHash.String(), block.Hash().String())
return nil, fmt.Errorf("incorrect BlockHash %s, expected %s", msg.BlockHash.String(), block.Hash().String())
}

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

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

feeRecipient := common.BytesToAddress(msg.ProposerFeeRecipient[:])
Expand All @@ -269,33 +276,33 @@ func (api *BlockValidationAPI) validateBlock(block *types.Block, msg *builderApi
var tracer *logger.AccessListTracer = nil
if api.accessVerifier != nil {
if err := api.accessVerifier.isBlacklisted(block.Coinbase()); err != nil {
return err
return nil, err
}
if err := api.accessVerifier.isBlacklisted(feeRecipient); err != nil {
return err
return nil, err
}
if err := api.accessVerifier.verifyTransactions(types.LatestSigner(api.eth.BlockChain().Config()), block.Transactions()); err != nil {
return err
return nil, err
}
isPostMerge := true // the call is PoS-native
precompiles := vm.ActivePrecompiles(api.eth.APIBackend.ChainConfig().Rules(new(big.Int).SetUint64(block.NumberU64()), isPostMerge, block.Time()))
tracer = logger.NewAccessListTracer(nil, common.Address{}, common.Address{}, precompiles)
vmconfig = vm.Config{Tracer: tracer}
}

err := api.eth.BlockChain().ValidatePayload(block, feeRecipient, expectedProfit, registeredGasLimit, vmconfig, api.useBalanceDiffProfit, api.excludeWithdrawals)
blockValue, err := api.eth.BlockChain().ValidatePayload(block, feeRecipient, expectedProfit, registeredGasLimit, vmconfig, api.useBalanceDiffProfit, api.excludeWithdrawals)
if err != nil {
return err
return nil, err
}

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

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

func validateBlobsBundle(txs types.Transactions, blobsBundle *builderApiDeneb.BlobsBundle) error {
Expand Down
23 changes: 16 additions & 7 deletions eth/block-validation/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,15 +390,20 @@ func TestValidateBuilderSubmissionV3(t *testing.T) {
ParentBeaconBlockRoot: common.Hash{42},
}

require.ErrorContains(t, api.ValidateBuilderSubmissionV3(blockRequest), "inaccurate payment")
response, err := api.ValidateBuilderSubmissionV3(blockRequest)
require.ErrorContains(t, err, "inaccurate payment")
require.Nil(t, response)
blockRequest.Message.Value = uint256.NewInt(132912184722468)
require.NoError(t, api.ValidateBuilderSubmissionV3(blockRequest))
response, err = api.ValidateBuilderSubmissionV3(blockRequest)
require.NoError(t, err)
require.Equal(t, response.BlockValue, uint256.NewInt(132912184722468))

blockRequest.Message.GasLimit += 1
blockRequest.ExecutionPayload.GasLimit += 1
updatePayloadHashV3(t, blockRequest)

require.ErrorContains(t, api.ValidateBuilderSubmissionV3(blockRequest), "incorrect gas limit set")
_, err = api.ValidateBuilderSubmissionV3(blockRequest)
require.ErrorContains(t, err, "incorrect gas limit set")

blockRequest.Message.GasLimit -= 1
blockRequest.ExecutionPayload.GasLimit -= 1
Expand All @@ -411,20 +416,23 @@ func TestValidateBuilderSubmissionV3(t *testing.T) {
testAddr: {},
},
}
require.ErrorContains(t, api.ValidateBuilderSubmissionV3(blockRequest), "transaction from blacklisted address 0x71562b71999873DB5b286dF957af199Ec94617F7")
_, err = api.ValidateBuilderSubmissionV3(blockRequest)
require.ErrorContains(t, err, "transaction from blacklisted address 0x71562b71999873DB5b286dF957af199Ec94617F7")

// Test tx to blacklisted address
api.accessVerifier = &AccessVerifier{
blacklistedAddresses: map[common.Address]struct{}{
{0x16}: {},
},
}
require.ErrorContains(t, api.ValidateBuilderSubmissionV3(blockRequest), "transaction to blacklisted address 0x1600000000000000000000000000000000000000")
_, err = api.ValidateBuilderSubmissionV3(blockRequest)
require.ErrorContains(t, err, "transaction to blacklisted address 0x1600000000000000000000000000000000000000")

api.accessVerifier = nil

blockRequest.Message.GasUsed = 10
require.ErrorContains(t, api.ValidateBuilderSubmissionV3(blockRequest), "incorrect GasUsed 10, expected 119996")
_, err = api.ValidateBuilderSubmissionV3(blockRequest)
require.ErrorContains(t, err, "incorrect GasUsed 10, expected 119996")
blockRequest.Message.GasUsed = execData.GasUsed

newTestKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f290")
Expand All @@ -441,7 +449,8 @@ func TestValidateBuilderSubmissionV3(t *testing.T) {
copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
blockRequest.ExecutionPayload = invalidPayload
updatePayloadHashV3(t, blockRequest)
require.ErrorContains(t, api.ValidateBuilderSubmissionV3(blockRequest), "could not apply tx 4", "insufficient funds for gas * price + value")
_, err = api.ValidateBuilderSubmissionV3(blockRequest)
require.ErrorContains(t, err, "could not apply tx 4", "insufficient funds for gas * price + value")
}

func updatePayloadHash(t *testing.T, blockRequest *BuilderBlockValidationRequest) {
Expand Down

0 comments on commit 03a9c50

Please sign in to comment.