mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-07 23:48:36 +00:00
core/vm, core, miner: abort timed-out block transactions
This commit is contained in:
parent
1abbae239d
commit
98e364dfbd
4 changed files with 137 additions and 1 deletions
|
|
@ -176,6 +176,9 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if evm.Cancelled() {
|
||||
return nil, vm.ErrExecutionInterrupted
|
||||
}
|
||||
// Update the state with pending changes.
|
||||
var root []byte
|
||||
if evm.ChainConfig().IsByzantium(blockNumber) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ var (
|
|||
ErrGasUintOverflow = errors.New("gas uint64 overflow")
|
||||
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
|
||||
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
|
||||
ErrExecutionInterrupted = errors.New("execution interrupted")
|
||||
|
||||
// errStopToken is an internal token indicating interpreter loop termination,
|
||||
// never returned to outside callers.
|
||||
|
|
|
|||
|
|
@ -175,14 +175,23 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
|
|||
}
|
||||
} else {
|
||||
interrupt := new(atomic.Int32)
|
||||
timeout := make(chan struct{})
|
||||
timer := time.AfterFunc(miner.config.Recommit, func() {
|
||||
interrupt.Store(commitInterruptTimeout)
|
||||
work.evm.Cancel()
|
||||
close(timeout)
|
||||
})
|
||||
defer timer.Stop()
|
||||
|
||||
err := miner.fillTransactions(ctx, interrupt, work)
|
||||
if !timer.Stop() {
|
||||
<-timeout
|
||||
if err == nil {
|
||||
err = errBlockInterruptedByTimeout
|
||||
}
|
||||
}
|
||||
if errors.Is(err, errBlockInterruptedByTimeout) {
|
||||
log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(miner.config.Recommit))
|
||||
miner.resetWorkEVM(work)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -366,6 +375,13 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (miner *Miner) resetWorkEVM(env *environment) {
|
||||
if env.evm != nil {
|
||||
env.evm.Release()
|
||||
}
|
||||
env.evm = vm.NewEVM(core.NewEVMBlockContext(env.header, miner.chain, &env.coinbase), env.state, miner.chainConfig, vm.Config{})
|
||||
}
|
||||
|
||||
func (miner *Miner) commitTransaction(ctx context.Context, env *environment, tx *types.Transaction) (err error) {
|
||||
_, _, spanEnd := telemetry.StartSpan(ctx, "miner.commitTransaction")
|
||||
defer spanEnd(&err)
|
||||
|
|
@ -418,6 +434,11 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*
|
|||
gp = env.gasPool.Snapshot()
|
||||
)
|
||||
receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx)
|
||||
if env.evm.Cancelled() {
|
||||
env.state.RevertToSnapshot(snap)
|
||||
env.gasPool.Set(gp)
|
||||
return nil, errBlockInterruptedByTimeout
|
||||
}
|
||||
if err != nil {
|
||||
env.state.RevertToSnapshot(snap)
|
||||
env.gasPool.Set(gp)
|
||||
|
|
@ -522,6 +543,9 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl
|
|||
|
||||
err := miner.commitTransaction(ctx, env, tx)
|
||||
switch {
|
||||
case errors.Is(err, errBlockInterruptedByTimeout):
|
||||
return err
|
||||
|
||||
case errors.Is(err, core.ErrNonceTooLow):
|
||||
// New head notification data race between the transaction pool and miner, shift
|
||||
log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce())
|
||||
|
|
|
|||
108
miner/worker_test.go
Normal file
108
miner/worker_test.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package miner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
func TestCommitTransactionsReturnsTimeoutOnCancelledEVM(t *testing.T) {
|
||||
var (
|
||||
key, _ = crypto.GenerateKey()
|
||||
from = crypto.PubkeyToAddress(key.PublicKey)
|
||||
contract = common.HexToAddress("0x1000000000000000000000000000000000000000")
|
||||
header = &types.Header{
|
||||
Number: big.NewInt(1),
|
||||
GasLimit: 1_000_000,
|
||||
GasUsed: 0,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Difficulty: big.NewInt(0),
|
||||
Coinbase: common.HexToAddress("0x2000000000000000000000000000000000000000"),
|
||||
}
|
||||
)
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
statedb.CreateAccount(from)
|
||||
statedb.AddBalance(from, uint256.MustFromBig(new(big.Int).Lsh(big.NewInt(1), 100)), tracing.BalanceIncreaseGenesisBalance)
|
||||
statedb.CreateAccount(contract)
|
||||
// Store a value before entering a cancelled jump loop. Without the miner-side
|
||||
// Cancelled check, the interpreter clears its stop token and this write survives.
|
||||
statedb.SetCode(contract, common.Hex2Bytes("600160005560075b600756"), tracing.CodeChangeUnspecified)
|
||||
statedb.Finalise(true)
|
||||
|
||||
evm := vm.NewEVM(core.NewEVMBlockContext(header, nil, &header.Coinbase), statedb, params.TestChainConfig, vm.Config{})
|
||||
defer evm.Release()
|
||||
evm.Cancel()
|
||||
|
||||
signer := types.MakeSigner(params.TestChainConfig, header.Number, header.Time)
|
||||
tx := types.MustSignNewTx(key, signer, &types.LegacyTx{
|
||||
Nonce: 0,
|
||||
To: &contract,
|
||||
Gas: 100_000,
|
||||
GasPrice: big.NewInt(params.InitialBaseFee),
|
||||
})
|
||||
plainTxs := newTransactionsByPriceAndNonce(signer, map[common.Address][]*txpool.LazyTransaction{
|
||||
from: {{
|
||||
Tx: tx,
|
||||
Hash: tx.Hash(),
|
||||
Time: time.Now(),
|
||||
GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
|
||||
GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
|
||||
Gas: tx.Gas(),
|
||||
}},
|
||||
}, header.BaseFee)
|
||||
blobTxs := newTransactionsByPriceAndNonce(signer, map[common.Address][]*txpool.LazyTransaction{}, header.BaseFee)
|
||||
env := &environment{
|
||||
signer: signer,
|
||||
state: statedb,
|
||||
gasPool: core.NewGasPool(header.GasLimit),
|
||||
header: header,
|
||||
evm: evm,
|
||||
}
|
||||
miner := &Miner{chainConfig: params.TestChainConfig}
|
||||
|
||||
err := miner.commitTransactions(context.Background(), env, plainTxs, blobTxs, nil)
|
||||
if !errors.Is(err, errBlockInterruptedByTimeout) {
|
||||
t.Fatalf("unexpected error: got %v, want %v", err, errBlockInterruptedByTimeout)
|
||||
}
|
||||
if len(env.txs) != 0 {
|
||||
t.Fatalf("interrupted transaction included: %d txs", len(env.txs))
|
||||
}
|
||||
if got := statedb.GetState(contract, common.Hash{}); got != (common.Hash{}) {
|
||||
t.Fatalf("interrupted transaction state was not reverted: %x", got)
|
||||
}
|
||||
if got := env.gasPool.Used(); got != 0 {
|
||||
t.Fatalf("interrupted transaction gas was not restored: %d", got)
|
||||
}
|
||||
if header.GasUsed != 0 {
|
||||
t.Fatalf("interrupted transaction updated header gas: %d", header.GasUsed)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue