mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 02:40:45 +00:00
core: implement check_transaction reservation check in parallel processor
The parallel processor executes transactions independently with per-tx gas pools. Replicate the EELS check_transaction per-dimension reservation check in prepareExecResult to correctly reject blocks that exceed gas limits. Also add ValidateGasLimit to ValidateBody to catch BAL items exceeding block_gas_limit / GAS_BLOCK_ACCESS_LIST_ITEM before block processing. Prefix all BAL mismatch errors with "invalid block access list:" for consistent mapper matching.
This commit is contained in:
parent
076a330416
commit
ddb521e0d2
2 changed files with 44 additions and 23 deletions
|
|
@ -122,6 +122,8 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||||
return fmt.Errorf("access list hash mismatch. local: %x. remote: %x\n", block.AccessList().Hash(), *block.Header().BlockAccessListHash)
|
return fmt.Errorf("access list hash mismatch. local: %x. remote: %x\n", block.AccessList().Hash(), *block.Header().BlockAccessListHash)
|
||||||
} else if err := block.AccessList().Validate(len(block.Transactions())); err != nil {
|
} else if err := block.AccessList().Validate(len(block.Transactions())); err != nil {
|
||||||
return fmt.Errorf("invalid block access list: %v", err)
|
return fmt.Errorf("invalid block access list: %v", err)
|
||||||
|
} else if err := block.AccessList().ValidateGasLimit(block.GasLimit()); err != nil {
|
||||||
|
return fmt.Errorf("invalid block access list: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//panic("TODO: implement local access list construction path if importing a block without an access list")
|
//panic("TODO: implement local access list construction path if importing a block without an access list")
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ package core
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"slices"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessResultWithMetrics wraps ProcessResult with some metrics that are
|
// ProcessResultWithMetrics wraps ProcessResult with some metrics that are
|
||||||
|
|
@ -104,20 +105,36 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStar
|
||||||
var allLogs []*types.Log
|
var allLogs []*types.Log
|
||||||
var allReceipts []*types.Receipt
|
var allReceipts []*types.Receipt
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
|
// EIP-8037 check_transaction: verify this tx fits in remaining
|
||||||
|
// block capacity per-dimension BEFORE adding its actual usage.
|
||||||
|
// Regular gas is capped at MaxTxGas; state gas uses full tx.gas.
|
||||||
|
regularAvailable := header.GasLimit - sumRegular
|
||||||
|
stateAvailable := header.GasLimit - sumState
|
||||||
|
if min(params.MaxTxGas, result.txGasLimit) > regularAvailable ||
|
||||||
|
result.txGasLimit > stateAvailable {
|
||||||
|
return &ProcessResultWithMetrics{
|
||||||
|
ProcessResult: &ProcessResult{Error: ErrGasLimitReached},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tx fits; add actual gas usage.
|
||||||
sumRegular += result.txRegular
|
sumRegular += result.txRegular
|
||||||
sumState += result.txState
|
sumState += result.txState
|
||||||
|
|
||||||
|
// Verify BAL mutations match for included txs.
|
||||||
|
if result.balErr != nil {
|
||||||
|
return &ProcessResultWithMetrics{
|
||||||
|
ProcessResult: &ProcessResult{Error: result.balErr},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cumulativeReceipt += result.execGas
|
cumulativeReceipt += result.execGas
|
||||||
result.receipt.CumulativeGasUsed = cumulativeReceipt
|
result.receipt.CumulativeGasUsed = cumulativeReceipt
|
||||||
allLogs = append(allLogs, result.receipt.Logs...)
|
allLogs = append(allLogs, result.receipt.Logs...)
|
||||||
allReceipts = append(allReceipts, result.receipt)
|
allReceipts = append(allReceipts, result.receipt)
|
||||||
}
|
}
|
||||||
// Block gas = max(sum_regular, sum_state) per EIP-8037.
|
|
||||||
blockGasUsed := max(sumRegular, sumState)
|
blockGasUsed := max(sumRegular, sumState)
|
||||||
if blockGasUsed > header.GasLimit {
|
|
||||||
return &ProcessResultWithMetrics{
|
|
||||||
ProcessResult: &ProcessResult{Error: fmt.Errorf("gas limit exceeded")},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var postMut bal.StateMutations
|
var postMut bal.StateMutations
|
||||||
// Read requests if Prague is enabled.
|
// Read requests if Prague is enabled.
|
||||||
|
|
@ -156,14 +173,14 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStar
|
||||||
accessList := bal.NewAccessListReader(*block.AccessList())
|
accessList := bal.NewAccessListReader(*block.AccessList())
|
||||||
if !postMut.Eq(*accessList.MutationsAt(lastBALIdx)) {
|
if !postMut.Eq(*accessList.MutationsAt(lastBALIdx)) {
|
||||||
return &ProcessResultWithMetrics{
|
return &ProcessResultWithMetrics{
|
||||||
ProcessResult: &ProcessResult{Error: fmt.Errorf("mismatch between local/remote access list mutations for final idx")},
|
ProcessResult: &ProcessResult{Error: fmt.Errorf("invalid block access list: mismatch between local/remote access list mutations for final idx")},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accesses.Merge(postTxAccesses)
|
accesses.Merge(postTxAccesses)
|
||||||
if !validateStateAccesses(lastBALIdx, accessList, accesses) {
|
if !validateStateAccesses(lastBALIdx, accessList, accesses) {
|
||||||
return &ProcessResultWithMetrics{
|
return &ProcessResultWithMetrics{
|
||||||
ProcessResult: &ProcessResult{Error: fmt.Errorf("mismatch between local/remote access list for state accesses")},
|
ProcessResult: &ProcessResult{Error: fmt.Errorf("invalid block access list: mismatch between local/remote access list for state accesses")},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,11 +199,13 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStar
|
||||||
}
|
}
|
||||||
|
|
||||||
type txExecResult struct {
|
type txExecResult struct {
|
||||||
idx int // transaction index
|
idx int // transaction index
|
||||||
receipt *types.Receipt
|
receipt *types.Receipt
|
||||||
err error // non-EVM error which would render the block invalid
|
err error // non-EVM error which would render the block invalid (e.g. tx apply failure)
|
||||||
blockGas uint64
|
balErr error // BAL mutation mismatch (deferred until after gas limit check)
|
||||||
execGas uint64
|
blockGas uint64
|
||||||
|
execGas uint64
|
||||||
|
txGasLimit uint64 // original tx gas limit (for pre-execution gas capacity check)
|
||||||
|
|
||||||
// Per-tx dimensional gas for Amsterdam 2D gas accounting (EIP-8037).
|
// Per-tx dimensional gas for Amsterdam 2D gas accounting (EIP-8037).
|
||||||
txRegular uint64
|
txRegular uint64
|
||||||
|
|
@ -201,7 +220,6 @@ func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxReads ba
|
||||||
// 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd
|
// 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd
|
||||||
// 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL)
|
// 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL)
|
||||||
var results []txExecResult
|
var results []txExecResult
|
||||||
gp := NewGasPool(block.GasLimit())
|
|
||||||
var execErr error
|
var execErr error
|
||||||
var numTxComplete int
|
var numTxComplete int
|
||||||
|
|
||||||
|
|
@ -217,8 +235,6 @@ func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxReads ba
|
||||||
// short-circuit if invalid block was detected
|
// short-circuit if invalid block was detected
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
execErr = res.err
|
execErr = res.err
|
||||||
} else if err := gp.SubGas(res.receipt.CumulativeGasUsed); err != nil {
|
|
||||||
execErr = err
|
|
||||||
} else {
|
} else {
|
||||||
results = append(results, res)
|
results = append(results, res)
|
||||||
accesses.Merge(res.stateReads)
|
accesses.Merge(res.stateReads)
|
||||||
|
|
@ -254,6 +270,7 @@ func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxReads ba
|
||||||
type stateRootCalculationResult struct {
|
type stateRootCalculationResult struct {
|
||||||
err error
|
err error
|
||||||
metrics *state.BALStateTransitionMetrics
|
metrics *state.BALStateTransitionMetrics
|
||||||
|
root common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcAndVerifyRoot performs the post-state root hash calculation, verifying
|
// calcAndVerifyRoot performs the post-state root hash calculation, verifying
|
||||||
|
|
@ -298,17 +315,19 @@ func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transactio
|
||||||
}
|
}
|
||||||
|
|
||||||
accessList := bal.NewAccessListReader(*block.AccessList())
|
accessList := bal.NewAccessListReader(*block.AccessList())
|
||||||
|
var balMismatch error
|
||||||
if !accessList.MutationsAt(balIdx).Eq(mut) {
|
if !accessList.MutationsAt(balIdx).Eq(mut) {
|
||||||
err := fmt.Errorf("mismatch between local/remote computed state mutations at bal idx %d. got:\n%s\nexpected:\n%s\n", balIdx, mut.String(), accessList.MutationsAt(balIdx).String())
|
balMismatch = fmt.Errorf("invalid block access list: mismatch between local/remote computed state mutations at bal idx %d. got:\n%s\nexpected:\n%s\n", balIdx, mut.String(), accessList.MutationsAt(balIdx).String())
|
||||||
return &txExecResult{err: err}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
txRegular, txState := gp.AmsterdamDimensions()
|
txRegular, txState := gp.AmsterdamDimensions()
|
||||||
return &txExecResult{
|
return &txExecResult{
|
||||||
idx: balIdx,
|
idx: balIdx,
|
||||||
|
balErr: balMismatch,
|
||||||
receipt: receipt,
|
receipt: receipt,
|
||||||
execGas: receipt.GasUsed,
|
execGas: receipt.GasUsed,
|
||||||
blockGas: gp.Used(),
|
blockGas: gp.Used(),
|
||||||
|
txGasLimit: tx.Gas(),
|
||||||
txRegular: txRegular,
|
txRegular: txRegular,
|
||||||
txState: txState,
|
txState: txState,
|
||||||
stateReads: db.Reader().(state.StateReaderTracker).GetStateAccessList(),
|
stateReads: db.Reader().(state.StateReaderTracker).GetStateAccessList(),
|
||||||
|
|
@ -337,7 +356,7 @@ func (p *ParallelStateProcessor) processBlockPreTx(block *types.Block, statedb *
|
||||||
mutations.Merge(pbhMutations)
|
mutations.Merge(pbhMutations)
|
||||||
reads := readerWithTracker.(state.StateReaderTracker).GetStateAccessList()
|
reads := readerWithTracker.(state.StateReaderTracker).GetStateAccessList()
|
||||||
if !accessList.MutationsAt(0).Eq(mutations) {
|
if !accessList.MutationsAt(0).Eq(mutations) {
|
||||||
return nil, fmt.Errorf("mismatch between local/remote access list mutations at idx 0")
|
return nil, fmt.Errorf("invalid block access list: mismatch between local/remote access list mutations at idx 0")
|
||||||
}
|
}
|
||||||
return reads, nil
|
return reads, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue