Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return simulated block value in validation api #161

Open
wants to merge 4 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
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
75 changes: 49 additions & 26 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 @@ -2488,13 +2488,15 @@ func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Ad
defer statedb.StopPrefetcher()

feeRecipientBalanceBefore := new(uint256.Int).Set(statedb.GetBalance(feeRecipient))
builderBalanceBefore := new(big.Int).Set(statedb.GetBalance(header.Coinbase).ToBig())

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

feeRecipientBalanceAfter := new(uint256.Int).Set(statedb.GetBalance(feeRecipient))
builderBalanceAfter := new(big.Int).Set(statedb.GetBalance(header.Coinbase).ToBig())

amtBeforeOrWithdrawn := new(uint256.Int).Set(feeRecipientBalanceBefore)
if excludeWithdrawals {
Expand All @@ -2508,88 +2510,109 @@ 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
}

// Coinbase balance diff
builderBalanceDelta := new(big.Int).Sub(builderBalanceAfter, builderBalanceBefore)

// Validate proposer payment

if useBalanceDiffProfit && feeRecipientBalanceAfter.Cmp(amtBeforeOrWithdrawn) >= 0 {
feeRecipientBalanceDelta := new(uint256.Int).Set(feeRecipientBalanceAfter)
feeRecipientBalanceDelta = feeRecipientBalanceDelta.Sub(feeRecipientBalanceDelta, amtBeforeOrWithdrawn)

uint256ExpectedProfit, ok := uint256.FromBig(expectedProfit)
if !ok {
uint256ExpectedProfit, overflow := uint256.FromBig(expectedProfit)
if !overflow {
if feeRecipientBalanceDelta.Cmp(uint256ExpectedProfit) >= 0 {
if feeRecipientBalanceDelta.Cmp(uint256ExpectedProfit) > 0 {
log.Warn("builder claimed profit is lower than calculated profit", "expected", expectedProfit, "actual", feeRecipientBalanceDelta)
}
return nil
return bc.calculateTrueBlockValue(builderBalanceDelta, feeRecipientBalanceDelta.ToBig(), feeRecipient == header.Coinbase)
}
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
return bc.calculateTrueBlockValue(builderBalanceDelta, paymentTx.Value(), feeRecipient == header.Coinbase)
}

func (bc *BlockChain) calculateTrueBlockValue(builderBalanceDelta, proposerPaymentValue *big.Int, feeRecipientIsCoinbase bool) (*uint256.Int, error) {
if feeRecipientIsCoinbase {
uint256ProposerPaymentValue, overflow := uint256.FromBig(proposerPaymentValue)
if overflow {
log.Warn("proposer payment value overflow when converting to uint256", "value", proposerPaymentValue)
return nil, nil
}
return uint256ProposerPaymentValue, nil
}
trueBlockValue := new(big.Int).Add(builderBalanceDelta, proposerPaymentValue)
uint256TrueBlockValue, overflow := uint256.FromBig(trueBlockValue)
if overflow {
log.Warn("true block value overflow when converting to uint256", "value", trueBlockValue)
return nil, nil
}
return uint256TrueBlockValue, nil
}

// SetTrieFlushInterval configures how often in-memory tries are persisted to disk.
Expand Down
58 changes: 37 additions & 21 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 Down Expand Up @@ -222,44 +225,57 @@ func (r *BuilderBlockValidationRequestV3) UnmarshalJSON(data []byte) error {
return nil
}

func (api *BlockValidationAPI) ValidateBuilderSubmissionV3(params *BuilderBlockValidationRequestV3) error {
type BuilderBlockValidationResponse struct {
BlockValue *uint256.Int
}

func (r *BuilderBlockValidationResponse) MarshalJSON() ([]byte, error) {
type validationResponseJSON struct {
BlockValue string `json:"block_value"`
}
return json.Marshal(&validationResponseJSON{
BlockValue: fmt.Sprintf("%d", r.BlockValue),
})
}

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))
log.Info("received block and blobs bundle", "hash", payload.BlockHash.String(), "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 +285,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
Loading