mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 07:06:42 +00:00
* add : make commitInterrupt OPCODE level * fix : build fix + lint * fix : small fix * chg : use context and add check on commitTransactions * add : testCommitInterruptExperimentBorContract * fix : minor fixes * add : TestCommitInterruptExperimentBorContract working * fix : lint and context.TODO() to context.Background() * fix : lint * fix : context cancel check * add : metrics for tx/opcode level interrupts + comments * add : interruptcommit flag * fix : minor flag fix * add : caching of interrupted tx * add : minor improvements on cache * fix : lint & added comments * add : GetCurrentTxFromContext and SetCurrentTxOnContext * fix : unstable testcases giving false negatives * rm : remove parallelTest from CommitInterrupt Tests * fix : small changes * rm : remove t.Parallel() from TestGenerateBlockAndImport tests * fix : commitInterrupt tests assertion changes
889 lines
29 KiB
Go
889 lines
29 KiB
Go
package miner
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
cmath "github.com/ethereum/go-ethereum/common/math"
|
|
"github.com/ethereum/go-ethereum/common/tracing"
|
|
"github.com/ethereum/go-ethereum/consensus"
|
|
"github.com/ethereum/go-ethereum/consensus/bor"
|
|
"github.com/ethereum/go-ethereum/consensus/clique"
|
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
"github.com/ethereum/go-ethereum/core"
|
|
"github.com/ethereum/go-ethereum/core/state"
|
|
"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/ethdb"
|
|
"github.com/ethereum/go-ethereum/event"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
lru "github.com/hashicorp/golang-lru"
|
|
)
|
|
|
|
const (
|
|
// testCode is the testing contract binary code which will initialises some
|
|
// variables in constructor
|
|
testCode = "0x60806040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0060005534801561003457600080fd5b5060fc806100436000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80630c4dae8814603757806398a213cf146053575b600080fd5b603d607e565b6040518082815260200191505060405180910390f35b607c60048036036020811015606757600080fd5b81019080803590602001909291905050506084565b005b60005481565b806000819055507fe9e44f9f7da8c559de847a3232b57364adc0354f15a2cd8dc636d54396f9587a6000546040518082815260200191505060405180910390a15056fea265627a7a723058208ae31d9424f2d0bc2a3da1a5dd659db2d71ec322a17db8f87e19e209e3a1ff4a64736f6c634300050a0032"
|
|
|
|
// testGas is the gas required for contract deployment.
|
|
testGas = 144109
|
|
|
|
storageContractByteCode = "608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100a1565b60405180910390f35b610073600480360381019061006e91906100ed565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009b81610088565b82525050565b60006020820190506100b66000830184610092565b92915050565b600080fd5b6100ca81610088565b81146100d557600080fd5b50565b6000813590506100e7816100c1565b92915050565b600060208284031215610103576101026100bc565b5b6000610111848285016100d8565b9150509291505056fea2646970667358221220322c78243e61b783558509c9cc22cb8493dde6925aa5e89a08cdf6e22f279ef164736f6c63430008120033"
|
|
storageContractTxCallData = "0x6057361d0000000000000000000000000000000000000000000000000000000000000001"
|
|
storageCallTxGas = 100000
|
|
)
|
|
|
|
func init() {
|
|
signer := types.LatestSigner(params.TestChainConfig)
|
|
|
|
tx1 := types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{
|
|
ChainID: params.TestChainConfig.ChainID,
|
|
Nonce: 0,
|
|
To: &testUserAddress,
|
|
Value: big.NewInt(1000),
|
|
Gas: params.TxGas,
|
|
GasPrice: big.NewInt(params.InitialBaseFee),
|
|
})
|
|
|
|
pendingTxs = append(pendingTxs, tx1)
|
|
|
|
tx2 := types.MustSignNewTx(testBankKey, signer, &types.LegacyTx{
|
|
Nonce: 1,
|
|
To: &testUserAddress,
|
|
Value: big.NewInt(1000),
|
|
Gas: params.TxGas,
|
|
GasPrice: big.NewInt(params.InitialBaseFee),
|
|
})
|
|
|
|
newTxs = append(newTxs, tx2)
|
|
}
|
|
|
|
// testWorkerBackend implements worker.Backend interfaces and wraps all information needed during the testing.
|
|
type testWorkerBackend struct {
|
|
DB ethdb.Database
|
|
txPool *core.TxPool
|
|
chain *core.BlockChain
|
|
Genesis *core.Genesis
|
|
uncleBlock *types.Block
|
|
}
|
|
|
|
func newTestWorkerBackend(t TensingObject, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend {
|
|
var gspec = core.Genesis{
|
|
Config: chainConfig,
|
|
Alloc: core.GenesisAlloc{TestBankAddress: {Balance: testBankFunds}},
|
|
GasLimit: 30_000_000,
|
|
}
|
|
|
|
switch e := engine.(type) {
|
|
case *bor.Bor:
|
|
gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength)
|
|
copy(gspec.ExtraData[32:32+common.AddressLength], TestBankAddress.Bytes())
|
|
e.Authorize(TestBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) {
|
|
return crypto.Sign(crypto.Keccak256(data), testBankKey)
|
|
})
|
|
case *clique.Clique:
|
|
gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength)
|
|
copy(gspec.ExtraData[32:32+common.AddressLength], TestBankAddress.Bytes())
|
|
e.Authorize(TestBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) {
|
|
return crypto.Sign(crypto.Keccak256(data), testBankKey)
|
|
})
|
|
case *ethash.Ethash:
|
|
default:
|
|
t.Fatalf("unexpected consensus engine type: %T", engine)
|
|
}
|
|
|
|
genesis := gspec.MustCommit(db)
|
|
|
|
chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{}, nil, nil, nil)
|
|
txpool := core.NewTxPool(testTxPoolConfig, chainConfig, chain)
|
|
|
|
// Generate a small n-block chain and an uncle block for it
|
|
if n > 0 {
|
|
blocks, _ := core.GenerateChain(chainConfig, genesis, engine, db, n, func(i int, gen *core.BlockGen) {
|
|
gen.SetCoinbase(TestBankAddress)
|
|
})
|
|
if _, err := chain.InsertChain(blocks); err != nil {
|
|
t.Fatalf("failed to insert origin chain: %v", err)
|
|
}
|
|
}
|
|
|
|
parent := genesis
|
|
if n > 0 {
|
|
parent = chain.GetBlockByHash(chain.CurrentBlock().ParentHash())
|
|
}
|
|
|
|
blocks, _ := core.GenerateChain(chainConfig, parent, engine, db, 1, func(i int, gen *core.BlockGen) {
|
|
gen.SetCoinbase(testUserAddress)
|
|
})
|
|
|
|
return &testWorkerBackend{
|
|
DB: db,
|
|
chain: chain,
|
|
txPool: txpool,
|
|
Genesis: &gspec,
|
|
uncleBlock: blocks[0],
|
|
}
|
|
}
|
|
|
|
func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain }
|
|
func (b *testWorkerBackend) TxPool() *core.TxPool { return b.txPool }
|
|
func (b *testWorkerBackend) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) {
|
|
return nil, errors.New("not supported")
|
|
}
|
|
|
|
func (b *testWorkerBackend) newRandomUncle() (*types.Block, error) {
|
|
var parent *types.Block
|
|
|
|
cur := b.chain.CurrentBlock()
|
|
|
|
if cur.NumberU64() == 0 {
|
|
parent = b.chain.Genesis()
|
|
} else {
|
|
parent = b.chain.GetBlockByHash(b.chain.CurrentBlock().ParentHash())
|
|
}
|
|
|
|
var err error
|
|
|
|
blocks, _ := core.GenerateChain(b.chain.Config(), parent, b.chain.Engine(), b.DB, 1, func(i int, gen *core.BlockGen) {
|
|
var addr = make([]byte, common.AddressLength)
|
|
|
|
_, err = rand.Read(addr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
gen.SetCoinbase(common.BytesToAddress(addr))
|
|
})
|
|
|
|
return blocks[0], err
|
|
}
|
|
|
|
// newRandomTx creates a new transaction.
|
|
func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
|
|
var tx *types.Transaction
|
|
|
|
gasPrice := big.NewInt(10 * params.InitialBaseFee)
|
|
|
|
if creation {
|
|
tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(TestBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey)
|
|
} else {
|
|
tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(TestBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey)
|
|
}
|
|
|
|
return tx
|
|
}
|
|
|
|
// newRandomTxWithNonce creates a new transaction with the given nonce.
|
|
func (b *testWorkerBackend) newRandomTxWithNonce(creation bool, nonce uint64) *types.Transaction {
|
|
var tx *types.Transaction
|
|
|
|
gasPrice := big.NewInt(100 * params.InitialBaseFee)
|
|
|
|
if creation {
|
|
tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(TestBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey)
|
|
} else {
|
|
tx, _ = types.SignTx(types.NewTransaction(nonce, testUserAddress, big.NewInt(1000), params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey)
|
|
}
|
|
|
|
return tx
|
|
}
|
|
|
|
// newRandomTxWithGas creates a new transactionto deploy a storage smart contract.
|
|
func (b *testWorkerBackend) newStorageCreateContractTx() (*types.Transaction, common.Address) {
|
|
var tx *types.Transaction
|
|
|
|
gasPrice := big.NewInt(10 * params.InitialBaseFee)
|
|
|
|
tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(TestBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(storageContractByteCode)), types.HomesteadSigner{}, testBankKey)
|
|
contractAddr := crypto.CreateAddress(TestBankAddress, b.txPool.Nonce(TestBankAddress))
|
|
|
|
return tx, contractAddr
|
|
}
|
|
|
|
// newStorageContractCallTx creates a new transaction to call a storage smart contract.
|
|
func (b *testWorkerBackend) newStorageContractCallTx(to common.Address, nonce uint64) *types.Transaction {
|
|
var tx *types.Transaction
|
|
|
|
gasPrice := big.NewInt(10 * params.InitialBaseFee)
|
|
|
|
tx, _ = types.SignTx(types.NewTransaction(nonce, to, nil, storageCallTxGas, gasPrice, common.FromHex(storageContractTxCallData)), types.HomesteadSigner{}, testBankKey)
|
|
|
|
return tx
|
|
}
|
|
|
|
// NewTestWorker creates a new test worker with the given parameters.
|
|
func NewTestWorker(t TensingObject, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int, noempty uint32, delay uint, opcodeDelay uint) (*worker, *testWorkerBackend, func()) {
|
|
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
|
|
backend.txPool.AddLocals(pendingTxs)
|
|
|
|
var w *worker
|
|
|
|
if delay != 0 || opcodeDelay != 0 {
|
|
//nolint:staticcheck
|
|
w = newWorkerWithDelay(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, delay, opcodeDelay)
|
|
} else {
|
|
//nolint:staticcheck
|
|
w = newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false)
|
|
}
|
|
|
|
w.setEtherbase(TestBankAddress)
|
|
|
|
// enable empty blocks
|
|
w.noempty = noempty
|
|
|
|
return w, backend, w.close
|
|
}
|
|
|
|
// newWorkerWithDelay is newWorker() with extra params to induce artficial delays for tests such as commit-interrupt.
|
|
// nolint:staticcheck
|
|
func newWorkerWithDelay(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool, delay uint, opcodeDelay uint) *worker {
|
|
worker := &worker{
|
|
config: config,
|
|
chainConfig: chainConfig,
|
|
engine: engine,
|
|
eth: eth,
|
|
mux: mux,
|
|
chain: eth.BlockChain(),
|
|
isLocalBlock: isLocalBlock,
|
|
localUncles: make(map[common.Hash]*types.Block),
|
|
remoteUncles: make(map[common.Hash]*types.Block),
|
|
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), sealingLogAtDepth),
|
|
pendingTasks: make(map[common.Hash]*task),
|
|
txsCh: make(chan core.NewTxsEvent, txChanSize),
|
|
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
|
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
|
|
newWorkCh: make(chan *newWorkReq),
|
|
getWorkCh: make(chan *getWorkReq),
|
|
taskCh: make(chan *task),
|
|
resultCh: make(chan *types.Block, resultQueueSize),
|
|
exitCh: make(chan struct{}),
|
|
startCh: make(chan struct{}, 1),
|
|
resubmitIntervalCh: make(chan time.Duration),
|
|
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
|
|
noempty: 1,
|
|
interruptCommitFlag: config.CommitInterruptFlag,
|
|
}
|
|
worker.profileCount = new(int32)
|
|
// Subscribe NewTxsEvent for tx pool
|
|
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
|
|
// Subscribe events for blockchain
|
|
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
|
|
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
|
|
|
|
interruptedTxCache, err := lru.New(vm.InterruptedTxCacheSize)
|
|
if err != nil {
|
|
log.Warn("Failed to create interrupted tx cache", "err", err)
|
|
}
|
|
|
|
worker.interruptedTxCache = &vm.TxCache{
|
|
Cache: interruptedTxCache,
|
|
}
|
|
|
|
if !worker.interruptCommitFlag {
|
|
worker.noempty = 0
|
|
}
|
|
|
|
// Sanitize recommit interval if the user-specified one is too short.
|
|
recommit := worker.config.Recommit
|
|
if recommit < minRecommitInterval {
|
|
log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval)
|
|
recommit = minRecommitInterval
|
|
}
|
|
|
|
ctx := tracing.WithTracer(context.Background(), otel.GetTracerProvider().Tracer("MinerWorker"))
|
|
|
|
worker.wg.Add(4)
|
|
|
|
go worker.mainLoopWithDelay(ctx, delay, opcodeDelay)
|
|
go worker.newWorkLoop(ctx, recommit)
|
|
go worker.resultLoop()
|
|
go worker.taskLoop()
|
|
|
|
// Submit first work to initialize pending state.
|
|
if init {
|
|
worker.startCh <- struct{}{}
|
|
}
|
|
|
|
return worker
|
|
}
|
|
|
|
// mainLoopWithDelay is mainLoop() with extra params to induce artficial delays for tests such as commit-interrupt.
|
|
// nolint:gocognit
|
|
func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint, opcodeDelay uint) {
|
|
defer w.wg.Done()
|
|
defer w.txsSub.Unsubscribe()
|
|
defer w.chainHeadSub.Unsubscribe()
|
|
defer w.chainSideSub.Unsubscribe()
|
|
defer func() {
|
|
if w.current != nil {
|
|
w.current.discard()
|
|
}
|
|
}()
|
|
|
|
cleanTicker := time.NewTicker(time.Second * 10)
|
|
defer cleanTicker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case req := <-w.newWorkCh:
|
|
//nolint:contextcheck
|
|
w.commitWorkWithDelay(req.ctx, req.interrupt, req.noempty, req.timestamp, delay, opcodeDelay)
|
|
|
|
case req := <-w.getWorkCh:
|
|
//nolint:contextcheck
|
|
block, err := w.generateWork(req.ctx, req.params)
|
|
if err != nil {
|
|
req.err = err
|
|
req.result <- nil
|
|
} else {
|
|
req.result <- block
|
|
}
|
|
|
|
case ev := <-w.chainSideCh:
|
|
// Short circuit for duplicate side blocks
|
|
if _, exist := w.localUncles[ev.Block.Hash()]; exist {
|
|
continue
|
|
}
|
|
|
|
if _, exist := w.remoteUncles[ev.Block.Hash()]; exist {
|
|
continue
|
|
}
|
|
|
|
// Add side block to possible uncle block set depending on the author.
|
|
if w.isLocalBlock != nil && w.isLocalBlock(ev.Block.Header()) {
|
|
w.localUncles[ev.Block.Hash()] = ev.Block
|
|
} else {
|
|
w.remoteUncles[ev.Block.Hash()] = ev.Block
|
|
}
|
|
|
|
// If our sealing block contains less than 2 uncle blocks,
|
|
// add the new uncle block if valid and regenerate a new
|
|
// sealing block for higher profit.
|
|
if w.isRunning() && w.current != nil && len(w.current.uncles) < 2 {
|
|
start := time.Now()
|
|
if err := w.commitUncle(w.current, ev.Block.Header()); err == nil {
|
|
commitErr := w.commit(ctx, w.current.copy(), nil, true, start)
|
|
if commitErr != nil {
|
|
log.Error("error while committing work for mining", "err", commitErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
case <-cleanTicker.C:
|
|
chainHead := w.chain.CurrentBlock()
|
|
for hash, uncle := range w.localUncles {
|
|
if uncle.NumberU64()+staleThreshold <= chainHead.NumberU64() {
|
|
delete(w.localUncles, hash)
|
|
}
|
|
}
|
|
|
|
for hash, uncle := range w.remoteUncles {
|
|
if uncle.NumberU64()+staleThreshold <= chainHead.NumberU64() {
|
|
delete(w.remoteUncles, hash)
|
|
}
|
|
}
|
|
|
|
case ev := <-w.txsCh:
|
|
// Apply transactions to the pending state if we're not sealing
|
|
//
|
|
// Note all transactions received may not be continuous with transactions
|
|
// already included in the current sealing block. These transactions will
|
|
// be automatically eliminated.
|
|
// nolint : nestif
|
|
if !w.isRunning() && w.current != nil {
|
|
// If block is already full, abort
|
|
if gp := w.current.gasPool; gp != nil && gp.Gas() < params.TxGas {
|
|
continue
|
|
}
|
|
|
|
txs := make(map[common.Address]types.Transactions)
|
|
|
|
for _, tx := range ev.Txs {
|
|
acc, _ := types.Sender(w.current.signer, tx)
|
|
txs[acc] = append(txs[acc], tx)
|
|
}
|
|
|
|
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee))
|
|
tcount := w.current.tcount
|
|
|
|
var interruptCtx = context.Background()
|
|
stopFn := func() {}
|
|
defer func() {
|
|
stopFn()
|
|
}()
|
|
|
|
if w.interruptCommitFlag {
|
|
interruptCtx, stopFn = getInterruptTimer(ctx, w.current, w.chain.CurrentBlock())
|
|
// nolint : staticcheck
|
|
interruptCtx = vm.PutCache(interruptCtx, w.interruptedTxCache)
|
|
}
|
|
|
|
w.commitTransactionsWithDelay(w.current, txset, nil, interruptCtx)
|
|
|
|
// Only update the snapshot if any new transactions were added
|
|
// to the pending block
|
|
if tcount != w.current.tcount {
|
|
w.updateSnapshot(w.current)
|
|
}
|
|
|
|
stopFn()
|
|
} else {
|
|
// Special case, if the consensus engine is 0 period clique(dev mode),
|
|
// submit sealing work here since all empty submission will be rejected
|
|
// by clique. Of course the advance sealing(empty submission) is disabled.
|
|
if w.chainConfig.Clique != nil && w.chainConfig.Clique.Period == 0 {
|
|
w.commitWork(ctx, nil, true, time.Now().Unix())
|
|
}
|
|
}
|
|
|
|
atomic.AddInt32(&w.newTxs, int32(len(ev.Txs)))
|
|
|
|
// System stopped
|
|
case <-w.exitCh:
|
|
return
|
|
case <-w.txsSub.Err():
|
|
return
|
|
case <-w.chainHeadSub.Err():
|
|
return
|
|
case <-w.chainSideSub.Err():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// commitWorkWithDelay is commitWork() with extra params to induce artficial delays for tests such as commit-interrupt.
|
|
func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noempty bool, timestamp int64, delay uint, opcodeDelay uint) {
|
|
start := time.Now()
|
|
|
|
var (
|
|
work *environment
|
|
err error
|
|
)
|
|
|
|
tracing.Exec(ctx, "", "worker.prepareWork", func(ctx context.Context, span trace.Span) {
|
|
// Set the coinbase if the worker is running or it's required
|
|
var coinbase common.Address
|
|
if w.isRunning() {
|
|
if w.coinbase == (common.Address{}) {
|
|
log.Error("Refusing to mine without etherbase")
|
|
return
|
|
}
|
|
|
|
coinbase = w.coinbase // Use the preset address as the fee recipient
|
|
}
|
|
|
|
work, err = w.prepareWork(&generateParams{
|
|
timestamp: uint64(timestamp),
|
|
coinbase: coinbase,
|
|
})
|
|
})
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// nolint : contextcheck
|
|
var interruptCtx = context.Background()
|
|
|
|
stopFn := func() {}
|
|
defer func() {
|
|
stopFn()
|
|
}()
|
|
|
|
if !noempty && w.interruptCommitFlag {
|
|
interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
|
// nolint : staticcheck
|
|
interruptCtx = vm.PutCache(interruptCtx, w.interruptedTxCache)
|
|
// nolint : staticcheck
|
|
interruptCtx = context.WithValue(interruptCtx, vm.InterruptCtxDelayKey, delay)
|
|
// nolint : staticcheck
|
|
interruptCtx = context.WithValue(interruptCtx, vm.InterruptCtxOpcodeDelayKey, opcodeDelay)
|
|
}
|
|
|
|
ctx, span := tracing.StartSpan(ctx, "commitWork")
|
|
defer tracing.EndSpan(span)
|
|
|
|
tracing.SetAttributes(
|
|
span,
|
|
attribute.Int("number", int(work.header.Number.Uint64())),
|
|
)
|
|
|
|
// Create an empty block based on temporary copied state for
|
|
// sealing in advance without waiting block execution finished.
|
|
if !noempty && atomic.LoadUint32(&w.noempty) == 0 {
|
|
err = w.commit(ctx, work.copy(), nil, false, start)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Fill pending transactions from the txpool
|
|
w.fillTransactionsWithDelay(ctx, interrupt, work, interruptCtx)
|
|
|
|
err = w.commit(ctx, work.copy(), w.fullTaskHook, true, start)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Swap out the old work with the new one, terminating any leftover
|
|
// prefetcher processes in the mean time and starting a new one.
|
|
if w.current != nil {
|
|
w.current.discard()
|
|
}
|
|
|
|
w.current = work
|
|
}
|
|
|
|
// fillTransactionsWithDelay is fillTransactions() with extra params to induce artficial delays for tests such as commit-interrupt.
|
|
// nolint:gocognit
|
|
func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32, env *environment, interruptCtx context.Context) {
|
|
ctx, span := tracing.StartSpan(ctx, "fillTransactions")
|
|
defer tracing.EndSpan(span)
|
|
|
|
// Split the pending transactions into locals and remotes
|
|
// Fill the block with all available pending transactions.
|
|
|
|
var (
|
|
localTxsCount int
|
|
remoteTxsCount int
|
|
localTxs = make(map[common.Address]types.Transactions)
|
|
remoteTxs map[common.Address]types.Transactions
|
|
)
|
|
|
|
// TODO: move to config or RPC
|
|
const profiling = false
|
|
|
|
if profiling {
|
|
doneCh := make(chan struct{})
|
|
|
|
defer func() {
|
|
close(doneCh)
|
|
}()
|
|
|
|
go func(number uint64) {
|
|
closeFn := func() error {
|
|
return nil
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-time.After(150 * time.Millisecond):
|
|
// Check if we've not crossed limit
|
|
if attempt := atomic.AddInt32(w.profileCount, 1); attempt >= 10 {
|
|
log.Info("Completed profiling", "attempt", attempt)
|
|
|
|
return
|
|
}
|
|
|
|
log.Info("Starting profiling in fill transactions", "number", number)
|
|
|
|
dir, err := os.MkdirTemp("", fmt.Sprintf("bor-traces-%s-", time.Now().UTC().Format("2006-01-02-150405Z")))
|
|
if err != nil {
|
|
log.Error("Error in profiling", "path", dir, "number", number, "err", err)
|
|
return
|
|
}
|
|
|
|
// grab the cpu profile
|
|
closeFnInternal, err := startProfiler("cpu", dir, number)
|
|
if err != nil {
|
|
log.Error("Error in profiling", "path", dir, "number", number, "err", err)
|
|
return
|
|
}
|
|
|
|
closeFn = func() error {
|
|
err := closeFnInternal()
|
|
|
|
log.Info("Completed profiling", "path", dir, "number", number, "error", err)
|
|
|
|
return nil
|
|
}
|
|
|
|
case <-doneCh:
|
|
err := closeFn()
|
|
|
|
if err != nil {
|
|
log.Info("closing fillTransactions", "number", number, "error", err)
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
}(env.header.Number.Uint64())
|
|
}
|
|
|
|
tracing.Exec(ctx, "", "worker.SplittingTransactions", func(ctx context.Context, span trace.Span) {
|
|
|
|
prePendingTime := time.Now()
|
|
|
|
pending := w.eth.TxPool().Pending(ctx, true)
|
|
remoteTxs = pending
|
|
|
|
postPendingTime := time.Now()
|
|
|
|
for _, account := range w.eth.TxPool().Locals() {
|
|
if txs := remoteTxs[account]; len(txs) > 0 {
|
|
delete(remoteTxs, account)
|
|
localTxs[account] = txs
|
|
}
|
|
}
|
|
|
|
postLocalsTime := time.Now()
|
|
|
|
localTxsCount = len(localTxs)
|
|
remoteTxsCount = len(remoteTxs)
|
|
|
|
tracing.SetAttributes(
|
|
span,
|
|
attribute.Int("len of local txs", localTxsCount),
|
|
attribute.Int("len of remote txs", remoteTxsCount),
|
|
attribute.String("time taken by Pending()", fmt.Sprintf("%v", postPendingTime.Sub(prePendingTime))),
|
|
attribute.String("time taken by Locals()", fmt.Sprintf("%v", postLocalsTime.Sub(postPendingTime))),
|
|
)
|
|
})
|
|
|
|
var (
|
|
localEnvTCount int
|
|
remoteEnvTCount int
|
|
committed bool
|
|
)
|
|
|
|
if localTxsCount > 0 {
|
|
var txs *types.TransactionsByPriceAndNonce
|
|
|
|
tracing.Exec(ctx, "", "worker.LocalTransactionsByPriceAndNonce", func(ctx context.Context, span trace.Span) {
|
|
txs = types.NewTransactionsByPriceAndNonce(env.signer, localTxs, cmath.FromBig(env.header.BaseFee))
|
|
|
|
tracing.SetAttributes(
|
|
span,
|
|
attribute.Int("len of tx local Heads", txs.GetTxs()),
|
|
)
|
|
})
|
|
|
|
tracing.Exec(ctx, "", "worker.LocalCommitTransactions", func(ctx context.Context, span trace.Span) {
|
|
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx)
|
|
})
|
|
|
|
if committed {
|
|
return
|
|
}
|
|
|
|
localEnvTCount = env.tcount
|
|
}
|
|
|
|
if remoteTxsCount > 0 {
|
|
var txs *types.TransactionsByPriceAndNonce
|
|
|
|
tracing.Exec(ctx, "", "worker.RemoteTransactionsByPriceAndNonce", func(ctx context.Context, span trace.Span) {
|
|
txs = types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, cmath.FromBig(env.header.BaseFee))
|
|
|
|
tracing.SetAttributes(
|
|
span,
|
|
attribute.Int("len of tx remote Heads", txs.GetTxs()),
|
|
)
|
|
})
|
|
|
|
tracing.Exec(ctx, "", "worker.RemoteCommitTransactions", func(ctx context.Context, span trace.Span) {
|
|
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx)
|
|
})
|
|
|
|
if committed {
|
|
return
|
|
}
|
|
|
|
remoteEnvTCount = env.tcount
|
|
}
|
|
|
|
tracing.SetAttributes(
|
|
span,
|
|
attribute.Int("len of final local txs ", localEnvTCount),
|
|
attribute.Int("len of final remote txs", remoteEnvTCount),
|
|
)
|
|
}
|
|
|
|
// commitTransactionsWithDelay is commitTransactions() with extra params to induce artficial delays for tests such as commit-interrupt.
|
|
// nolint:gocognit, unparam
|
|
func (w *worker) commitTransactionsWithDelay(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *int32, interruptCtx context.Context) bool {
|
|
gasLimit := env.header.GasLimit
|
|
if env.gasPool == nil {
|
|
env.gasPool = new(core.GasPool).AddGas(gasLimit)
|
|
}
|
|
|
|
var coalescedLogs []*types.Log
|
|
|
|
initialGasLimit := env.gasPool.Gas()
|
|
initialTxs := txs.GetTxs()
|
|
|
|
var breakCause string
|
|
|
|
defer func() {
|
|
log.OnDebug(func(lg log.Logging) {
|
|
lg("commitTransactions-stats",
|
|
"initialTxsCount", initialTxs,
|
|
"initialGasLimit", initialGasLimit,
|
|
"resultTxsCount", txs.GetTxs(),
|
|
"resultGapPool", env.gasPool.Gas(),
|
|
"exitCause", breakCause)
|
|
})
|
|
}()
|
|
|
|
mainloop:
|
|
for {
|
|
if interruptCtx != nil {
|
|
// case of interrupting by timeout
|
|
select {
|
|
case <-interruptCtx.Done():
|
|
log.Warn("Interrupt")
|
|
break mainloop
|
|
default:
|
|
}
|
|
}
|
|
|
|
// In the following three cases, we will interrupt the execution of the transaction.
|
|
// (1) new head block event arrival, the interrupt signal is 1
|
|
// (2) worker start or restart, the interrupt signal is 1
|
|
// (3) worker recreate the sealing block with any newly arrived transactions, the interrupt signal is 2.
|
|
// For the first two cases, the semi-finished work will be discarded.
|
|
// For the third case, the semi-finished work will be submitted to the consensus engine.
|
|
if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone {
|
|
// Notify resubmit loop to increase resubmitting interval due to too frequent commits.
|
|
if atomic.LoadInt32(interrupt) == commitInterruptResubmit {
|
|
ratio := float64(gasLimit-env.gasPool.Gas()) / float64(gasLimit)
|
|
if ratio < 0.1 {
|
|
// nolint:goconst
|
|
ratio = 0.1
|
|
}
|
|
w.resubmitAdjustCh <- &intervalAdjust{
|
|
ratio: ratio,
|
|
inc: true,
|
|
}
|
|
}
|
|
// nolint:goconst
|
|
breakCause = "interrupt"
|
|
|
|
return atomic.LoadInt32(interrupt) == commitInterruptNewHead
|
|
}
|
|
// If we don't have enough gas for any further transactions then we're done
|
|
if env.gasPool.Gas() < params.TxGas {
|
|
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
|
|
// nolint:goconst
|
|
breakCause = "Not enough gas for further transactions"
|
|
|
|
break
|
|
}
|
|
// Retrieve the next transaction and abort if all done
|
|
tx := txs.Peek()
|
|
if tx == nil {
|
|
// nolint:goconst
|
|
breakCause = "all transactions has been included"
|
|
break
|
|
}
|
|
// Error may be ignored here. The error has already been checked
|
|
// during transaction acceptance is the transaction pool.
|
|
//
|
|
// We use the eip155 signer regardless of the current hf.
|
|
from, _ := types.Sender(env.signer, tx)
|
|
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
|
// phase, start ignoring the sender until we do.
|
|
if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
|
|
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
|
|
|
|
txs.Pop()
|
|
|
|
continue
|
|
}
|
|
// Start executing the transaction
|
|
env.state.Prepare(tx.Hash(), env.tcount)
|
|
|
|
var start time.Time
|
|
|
|
log.OnDebug(func(log.Logging) {
|
|
start = time.Now()
|
|
})
|
|
|
|
logs, err := w.commitTransaction(env, tx, interruptCtx)
|
|
|
|
if interruptCtx != nil {
|
|
if delay := interruptCtx.Value(vm.InterruptCtxDelayKey); delay != nil {
|
|
// nolint : durationcheck
|
|
time.Sleep(time.Duration(delay.(uint)) * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case errors.Is(err, core.ErrGasLimitReached):
|
|
// Pop the current out-of-gas transaction without shifting in the next from the account
|
|
log.Trace("Gas limit exceeded for current block", "sender", from)
|
|
txs.Pop()
|
|
|
|
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", "sender", from, "nonce", tx.Nonce())
|
|
txs.Shift()
|
|
|
|
case errors.Is(err, core.ErrNonceTooHigh):
|
|
// Reorg notification data race between the transaction pool and miner, skip account =
|
|
log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
|
|
txs.Pop()
|
|
|
|
case errors.Is(err, nil):
|
|
// Everything ok, collect the logs and shift in the next transaction from the same account
|
|
coalescedLogs = append(coalescedLogs, logs...)
|
|
env.tcount++
|
|
|
|
txs.Shift()
|
|
|
|
log.OnDebug(func(lg log.Logging) {
|
|
lg("Committed new tx", "tx hash", tx.Hash(), "from", from, "to", tx.To(), "nonce", tx.Nonce(), "gas", tx.Gas(), "gasPrice", tx.GasPrice(), "value", tx.Value(), "time spent", time.Since(start))
|
|
})
|
|
|
|
case errors.Is(err, core.ErrTxTypeNotSupported):
|
|
// Pop the unsupported transaction without shifting in the next from the account
|
|
log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
|
|
txs.Pop()
|
|
|
|
default:
|
|
// Strange error, discard the transaction and get the next in line (note, the
|
|
// nonce-too-high clause will prevent us from executing in vain).
|
|
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
|
txs.Shift()
|
|
}
|
|
}
|
|
|
|
if !w.isRunning() && len(coalescedLogs) > 0 {
|
|
// We don't push the pendingLogsEvent while we are sealing. The reason is that
|
|
// when we are sealing, the worker will regenerate a sealing block every 3 seconds.
|
|
// In order to avoid pushing the repeated pendingLog, we disable the pending log pushing.
|
|
// make a copy, the state caches the logs and these logs get "upgraded" from pending to mined
|
|
// logs by filling in the block hash when the block was mined by the local miner. This can
|
|
// cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed.
|
|
cpy := make([]*types.Log, len(coalescedLogs))
|
|
for i, l := range coalescedLogs {
|
|
cpy[i] = new(types.Log)
|
|
*cpy[i] = *l
|
|
}
|
|
|
|
w.pendingLogsFeed.Send(cpy)
|
|
}
|
|
// Notify resubmit loop to decrease resubmitting interval if current interval is larger
|
|
// than the user-specified one.
|
|
if interrupt != nil {
|
|
w.resubmitAdjustCh <- &intervalAdjust{inc: false}
|
|
}
|
|
|
|
return false
|
|
}
|