From af4bac17a918949f2bfd97484046cda0a8d460a7 Mon Sep 17 00:00:00 2001 From: avalonche Date: Thu, 13 Jun 2024 08:18:28 +1000 Subject: [PATCH 1/3] interop premine --- core/gen_genesis.go | 30 +++--- core/state_processor.go | 62 +++++++++++ core/txpool/txpool.go | 3 + core/types/transaction.go | 19 ++++ eth/api_backend.go | 31 ++++++ eth/gasprice/optimism-gasprice_test.go | 8 +- eth/protocols/snap/progress_test.go | 2 +- internal/ethapi/api.go | 55 ++++++++++ internal/ethapi/api_test.go | 9 +- internal/ethapi/backend.go | 1 + internal/ethapi/transaction_args_test.go | 3 + miner/miner.go | 8 ++ miner/simulated.go | 128 +++++++++++++++++++++++ miner/worker.go | 89 ++++++++++++++++ 14 files changed, 425 insertions(+), 23 deletions(-) create mode 100644 miner/simulated.go diff --git a/core/gen_genesis.go b/core/gen_genesis.go index 3a57ec65e8..4c0be7ed4d 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -19,22 +19,22 @@ var _ = (*genesisSpecMarshaling)(nil) // MarshalJSON marshals as JSON. func (g Genesis) MarshalJSON() ([]byte, error) { type Genesis struct { - Config *params.ChainConfig `json:"config"` - Nonce math.HexOrDecimal64 `json:"nonce"` - Timestamp math.HexOrDecimal64 `json:"timestamp"` - ExtraData hexutil.Bytes `json:"extraData"` - GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` - Mixhash common.Hash `json:"mixHash"` - Coinbase common.Address `json:"coinbase"` + Config *params.ChainConfig `json:"config"` + Nonce math.HexOrDecimal64 `json:"nonce"` + Timestamp math.HexOrDecimal64 `json:"timestamp"` + ExtraData hexutil.Bytes `json:"extraData"` + GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` + Mixhash common.Hash `json:"mixHash"` + Coinbase common.Address `json:"coinbase"` Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` - Number math.HexOrDecimal64 `json:"number"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - ParentHash common.Hash `json:"parentHash"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` - ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` - BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` - StateHash *common.Hash `json:"stateHash,omitempty"` + Number math.HexOrDecimal64 `json:"number"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + ParentHash common.Hash `json:"parentHash"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` + BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` + StateHash *common.Hash `json:"stateHash,omitempty"` } var enc Genesis enc.Config = g.Config diff --git a/core/state_processor.go b/core/state_processor.go index e11041a87c..a10b510e99 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -187,6 +187,68 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } +func ApplyTransactionWithResult(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, *ExecutionResult, error) { + msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee) + if err != nil { + return nil, nil, err + } + // Create a new context to be used in the EVM environment + blockContext := NewEVMBlockContext(header, bc, author, config, statedb) + txContext := NewEVMTxContext(msg) + vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg) + return applyTransactionWithResult(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) +} + +func applyTransactionWithResult(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, *ExecutionResult, error) { + // Create a new context to be used in the EVM environment. + txContext := NewEVMTxContext(msg) + evm.Reset(txContext, statedb) + + // Apply the transaction to the current state (included in the env). + result, err := ApplyMessage(evm, msg, gp) + if err != nil { + return nil, nil, err + } + + // Update the state with pending changes. + var root []byte + if config.IsByzantium(blockNumber) { + statedb.Finalise(true) + } else { + root = statedb.IntermediateRoot(config.IsEIP158(blockNumber)).Bytes() + } + *usedGas += result.UsedGas + + // Create a new receipt for the transaction, storing the intermediate root and gas used + // by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas} + if result.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } + receipt.TxHash = tx.Hash() + receipt.GasUsed = result.UsedGas + + if tx.Type() == types.BlobTxType { + receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob) + receipt.BlobGasPrice = evm.Context.BlobBaseFee + } + + // If the transaction created a contract, store the creation address in the receipt. + if msg.To == nil { + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) + } + + // Set the receipt logs and create the bloom filter. + receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + receipt.BlockHash = blockHash + receipt.BlockNumber = blockNumber + receipt.TransactionIndex = uint(statedb.TxIndex()) + return receipt, result, err +} + // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // contract. This method is exported to be used in tests. func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) { diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index ff31ffc200..4507e824a2 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -77,6 +77,9 @@ type TxPool struct { term chan struct{} // Termination channel to detect a closed pool sync chan chan error // Testing / simulator channel to block until internal reset is done + + mevBundles []types.MevBundle + mu sync.RWMutex } // New creates a new transaction pool to gather, sort and filter inbound diff --git a/core/types/transaction.go b/core/types/transaction.go index 95ecac5fae..156e9664e3 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" ) var ( @@ -672,3 +673,21 @@ func copyAddressPtr(a *common.Address) *common.Address { cpy := *a return &cpy } + +type MevBundle struct { + Txs Transactions + BlockNumber *big.Int + MinTimestamp uint64 + MaxTimestamp uint64 + Hash common.Hash +} + +type SimulatedBundle struct { + MevGasPrice *uint256.Int + TotalProfit *uint256.Int + TotalGasUsed uint64 + Logs []*Log + Revert []byte + ExecError string + OriginalBundle MevBundle +} diff --git a/eth/api_backend.go b/eth/api_backend.go index d92984ae21..a6f73aaecd 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -43,6 +43,7 @@ import ( "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "golang.org/x/crypto/sha3" ) // EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes @@ -319,6 +320,36 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0] } +func (b *EthAPIBackend) SendInteropBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64) (*types.SimulatedBundle, error) { + for _, tx := range txs { + if b.ChainConfig().IsOptimism() && tx.Type() == types.BlobTxType { + return nil, types.ErrTxTypeNotSupported + } + } + + bundleHasher := sha3.NewLegacyKeccak256() + for _, tx := range txs { + _, err := bundleHasher.Write(tx.Hash().Bytes()) + if err != nil { + return nil, err + } + } + bundleHash := common.BytesToHash(bundleHasher.Sum(nil)) + if blockNumber == rpc.PendingBlockNumber { + bundle := types.MevBundle{ + Txs: txs, + BlockNumber: big.NewInt(int64(b.eth.blockchain.CurrentBlock().Number.Uint64() + 1)), + MinTimestamp: minTimestamp, + MaxTimestamp: maxTimestamp, + Hash: bundleHash, + } + + return b.eth.miner.SimulateBundle(bundle) + } + + return nil, errors.New("only pending block is supported") +} + func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { pending := b.eth.txPool.Pending(txpool.PendingFilter{}) var txs types.Transactions diff --git a/eth/gasprice/optimism-gasprice_test.go b/eth/gasprice/optimism-gasprice_test.go index dd0140839d..c0431c6d75 100644 --- a/eth/gasprice/optimism-gasprice_test.go +++ b/eth/gasprice/optimism-gasprice_test.go @@ -112,22 +112,22 @@ func TestSuggestOptimismPriorityFee(t *testing.T) { }{ { // block well under capacity, expect min priority fee suggestion - txdata: []testTxData{testTxData{params.GWei, 21000}}, + txdata: []testTxData{{params.GWei, 21000}}, want: minSuggestion, }, { // 2 txs, still under capacity, expect min priority fee suggestion - txdata: []testTxData{testTxData{params.GWei, 21000}, testTxData{params.GWei, 21000}}, + txdata: []testTxData{{params.GWei, 21000}, {params.GWei, 21000}}, want: minSuggestion, }, { // 2 txs w same priority fee (1 gwei), but second tx puts it right over capacity - txdata: []testTxData{testTxData{params.GWei, 21000}, testTxData{params.GWei, 21001}}, + txdata: []testTxData{{params.GWei, 21000}, {params.GWei, 21001}}, want: big.NewInt(1100000000), // 10 percent over 1 gwei, the median }, { // 3 txs, full block. return 10% over the median tx (10 gwei * 10% == 11 gwei) - txdata: []testTxData{testTxData{10 * params.GWei, 21000}, testTxData{1 * params.GWei, 21000}, testTxData{100 * params.GWei, 21000}}, + txdata: []testTxData{{10 * params.GWei, 21000}, {1 * params.GWei, 21000}, {100 * params.GWei, 21000}}, want: big.NewInt(11 * params.GWei), }, } diff --git a/eth/protocols/snap/progress_test.go b/eth/protocols/snap/progress_test.go index 9d923bd2f5..1d9a6b8474 100644 --- a/eth/protocols/snap/progress_test.go +++ b/eth/protocols/snap/progress_test.go @@ -80,7 +80,7 @@ func makeLegacyProgress() legacyProgress { Next: common.Hash{}, Last: common.Hash{0x77}, SubTasks: map[common.Hash][]*legacyStorageTask{ - common.Hash{0x1}: { + {0x1}: { { Next: common.Hash{}, Last: common.Hash{0xff}, diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index a0b64c86e3..1d6b07eeac 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -2204,6 +2204,61 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash()) } +// SendInteropBundleArgs represents the arguments for a SendInteropBundle call. +type SendInteropBundleArgs struct { + Txs []hexutil.Bytes `json:"txs"` + BlockNumber rpc.BlockNumber `json:"blockNumber"` + MinTimestamp *uint64 `json:"minTimestamp"` + MaxTimestamp *uint64 `json:"maxTimestamp"` +} + +type SendInteropBundleResponse struct { + BlockNumber uint64 `json:"blockNumber"` + Timestamp uint64 `json:"timestamp"` + Logs []*types.Log `json:"logs"` + BundleHash common.Hash `json:"bundleHash"` +} + +// SendInteropBundle will add the signed transaction to the transaction pool. +// The sender is responsible for signing the transaction and using the correct nonce and ensuring validity +func (s *TransactionAPI) SendInteropBundle(ctx context.Context, args SendInteropBundleArgs) (*SendInteropBundleResponse, error) { + var txs types.Transactions + if len(args.Txs) == 0 { + return nil, errors.New("bundle missing txs") + } + if args.BlockNumber == 0 { + args.BlockNumber = rpc.PendingBlockNumber + } + + for _, encodedTx := range args.Txs { + tx := new(types.Transaction) + if err := tx.UnmarshalBinary(encodedTx); err != nil { + return nil, err + } + txs = append(txs, tx) + } + + var minTimestamp, maxTimestamp uint64 + if args.MinTimestamp != nil { + minTimestamp = *args.MinTimestamp + } + if args.MaxTimestamp != nil { + maxTimestamp = *args.MaxTimestamp + } + + bundle, err := s.b.SendInteropBundle(ctx, txs, args.BlockNumber, minTimestamp, maxTimestamp) + if err != nil { + return nil, err + } + + return &SendInteropBundleResponse{ + BlockNumber: s.b.CurrentHeader().Number.Uint64() + 1, + Timestamp: s.b.CurrentHeader().Time + 2, // assume l2 slot time is 2 seconds + BundleHash: bundle.OriginalBundle.Hash, + Logs: bundle.Logs, + }, nil +} + // DebugAPI is the collection of Ethereum APIs exposed over the debugging // namespace. type DebugAPI struct { diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 313ce1c10e..09a8ebd243 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -744,6 +744,9 @@ func (b testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) even func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { panic("implement me") } +func (b testBackend) SendInteropBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64) (*types.SimulatedBundle, error) { + panic("implement me") +} func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) { tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash) return true, tx, blockHash, blockNumber, index, nil @@ -917,7 +920,7 @@ func TestEstimateGas(t *testing.T) { From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1)), - BlobHashes: []common.Hash{common.Hash{0x01, 0x22}}, + BlobHashes: []common.Hash{{0x01, 0x22}}, BlobFeeCap: (*hexutil.Big)(big.NewInt(1)), }, want: 21000, @@ -1105,7 +1108,7 @@ func TestCall(t *testing.T) { call: TransactionArgs{ From: &accounts[1].addr, To: &randomAccounts[2].addr, - BlobHashes: []common.Hash{common.Hash{0x01, 0x22}}, + BlobHashes: []common.Hash{{0x01, 0x22}}, BlobFeeCap: (*hexutil.Big)(big.NewInt(1)), }, overrides: StateOverride{ @@ -1232,7 +1235,7 @@ func TestSendBlobTransaction(t *testing.T) { From: &b.acc.Address, To: &to, Value: (*hexutil.Big)(big.NewInt(1)), - BlobHashes: []common.Hash{common.Hash{0x01, 0x22}}, + BlobHashes: []common.Hash{{0x01, 0x22}}, }) if err != nil { t.Fatalf("failed to fill tx defaults: %v\n", err) diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 7c0e655d20..7987215744 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -75,6 +75,7 @@ type Backend interface { // Transaction pool API SendTx(ctx context.Context, signedTx *types.Transaction) error + SendInteropBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64) (*types.SimulatedBundle, error) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) GetPoolTransactions() (types.Transactions, error) GetPoolTransaction(txHash common.Hash) *types.Transaction diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index f98683baca..a865bb5eca 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -377,6 +377,9 @@ func (b *backendMock) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) eve return nil } func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { return nil } +func (b *backendMock) SendInteropBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp, maxTimestamp uint64) (*types.SimulatedBundle, error) { + return nil, nil +} func (b *backendMock) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) { return false, nil, [32]byte{}, 0, 0, nil } diff --git a/miner/miner.go b/miner/miner.go index 85b3d8d13d..104a13b117 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -258,3 +258,11 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript func (miner *Miner) BuildPayload(args *BuildPayloadArgs) (*Payload, error) { return miner.worker.buildPayload(args) } + +func (miner *Miner) SimulateBundle(bundle types.MevBundle) (*types.SimulatedBundle, error) { + return miner.worker.simulateBundle(bundle) +} + +func (mint *Miner) GetTopBundle() (*types.SimulatedBundle, error) { + return mint.worker.getTopBundle() +} diff --git a/miner/simulated.go b/miner/simulated.go new file mode 100644 index 0000000000..43c0026f25 --- /dev/null +++ b/miner/simulated.go @@ -0,0 +1,128 @@ +package miner + +import ( + "errors" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/holiman/uint256" +) + +var ( + ErrInvalidInclusion = errors.New("invalid inclusion") + + ErrTxFailed = errors.New("tx failed") + ErrNegativeProfit = errors.New("negative profit") + ErrInvalidBundle = errors.New("invalid bundle") +) + +func (w *worker) simulateBundle(bundle types.MevBundle) (*types.SimulatedBundle, error) { + if bundle.BlockNumber.Int64() != rpc.PendingBlockNumber.Int64() { + return nil, ErrInvalidInclusion + } + + header := w.chain.CurrentBlock() + statedb, err := w.chain.StateAt(header.Root) + if err != nil { + return nil, err + } + + gp := new(core.GasPool).AddGas(header.GasLimit) + + var ( + coinbaseDelta = new(uint256.Int) + coinbaseBefore *uint256.Int + txIdx int + revert []byte + execError string + gasUsed uint64 + logs []*types.Log + totalProfit = new(uint256.Int) + ) + for _, tx := range bundle.Txs { + coinbaseDelta.Set(common.U2560) + coinbaseBefore = statedb.GetBalance(header.Coinbase) + + if tx != nil { + statedb.SetTxContext(tx.Hash(), txIdx) + txIdx++ + receipt, result, err := core.ApplyTransactionWithResult(w.chainConfig, w.chain, &header.Coinbase, gp, statedb, header, tx, &gasUsed, *w.chain.GetVMConfig()) + if err != nil { + return nil, err + } + revert = result.Revert() + if result.Err != nil { + execError = result.Err.Error() + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, ErrTxFailed + } + gasUsed += receipt.GasUsed + logs = append(logs, receipt.Logs...) + + } + + coinbaseAfter := statedb.GetBalance(header.Coinbase) + coinbaseDelta.Set(coinbaseAfter) + coinbaseDelta.Sub(coinbaseDelta, coinbaseBefore) + + totalProfit.Add(totalProfit, coinbaseDelta) + } + + if coinbaseDelta.Sign() < 0 { + return nil, ErrNegativeProfit + } + mevGasPrice := new(uint256.Int).Div(totalProfit, new(uint256.Int).SetUint64(gasUsed)) + simBundle := &types.SimulatedBundle{ + MevGasPrice: mevGasPrice, + TotalProfit: totalProfit, + TotalGasUsed: gasUsed, + Logs: logs, + Revert: revert, + ExecError: execError, + OriginalBundle: bundle, + } + w.mu.Lock() + defer w.mu.Unlock() + if execError != "" && totalProfit.Sign() < 0 { + w.simulatedBundles = append(w.simulatedBundles, simBundle) + } + return simBundle, nil +} + +func (w *worker) getTopBundle() (*types.SimulatedBundle, error) { + w.mu.Lock() + defer w.mu.Unlock() + + sort.SliceStable(w.simulatedBundles, func(i, j int) bool { + return w.simulatedBundles[j].TotalProfit.Cmp(w.simulatedBundles[i].TotalProfit) < 0 + }) + if len(w.simulatedBundles) > 0 { + return w.simulatedBundles[0], nil + } + return nil, ErrInvalidBundle +} + +func (w *worker) filterBundles() []*types.SimulatedBundle { + w.mu.Lock() + defer w.mu.Unlock() + + // returned values + var ret []*types.SimulatedBundle + + for _, bundle := range w.simulatedBundles { + if bundle.OriginalBundle.BlockNumber.Cmp(w.current.header.Number) > 0 { + continue + } + + ret = append(ret, bundle) + } + w.simulatedBundles = ret + sort.SliceStable(w.simulatedBundles, func(i, j int) bool { + return w.simulatedBundles[j].TotalProfit.Cmp(w.simulatedBundles[i].TotalProfit) > 0 + }) + return w.simulatedBundles +} diff --git a/miner/worker.go b/miner/worker.go index a69e564410..77d522bcec 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -78,6 +78,7 @@ const ( ) var ( + errCouldNotApplyTransaction = errors.New("could not apply transaction") errBlockInterruptedByNewHead = errors.New("new head arrived while building block") errBlockInterruptedByRecommit = errors.New("recommit interrupt while building block") errBlockInterruptedByTimeout = errors.New("timeout while building block") @@ -245,6 +246,8 @@ type worker struct { skipSealHook func(*task) bool // Method to decide whether skipping the sealing. fullTaskHook func() // Method to call before pushing the full sealing task. resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval. + + simulatedBundles []*types.SimulatedBundle } func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *worker { @@ -846,6 +849,82 @@ func (w *worker) applyTransaction(env *environment, tx *types.Transaction) (*typ return receipt, err } +func (w *worker) commitBundle(env *environment, bundle types.MevBundle, interrupt *atomic.Int32) error { + gasLimit := env.header.GasLimit + if env.gasPool == nil { + env.gasPool = new(core.GasPool).AddGas(gasLimit) + } + var coalescedLogs []*types.Log + + for _, tx := range bundle.Txs { + // Check interruption signal and abort building if it's fired. + if interrupt != nil { + if signal := interrupt.Load(); signal != commitInterruptNone { + return signalToErr(signal) + } + } + // 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) + break + } + // If we don't have enough space for the next transaction, skip the account. + if env.gasPool.Gas() < tx.Gas() { + log.Trace("Not enough gas left for transaction", "hash", tx.Hash(), "left", env.gasPool.Gas(), "needed", tx.Gas()) + + continue + } + + // Error may be ignored here. The error has already been checked + // during transaction acceptance is the transaction pool. + 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 replay protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block) + return errCouldNotApplyTransaction + } + // Start executing the transaction + env.state.SetTxContext(tx.Hash(), env.tcount) + + logs, err := w.commitTransaction(env, tx) + switch { + 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", tx.Hash(), "sender", from, "nonce", tx.Nonce()) + return errCouldNotApplyTransaction + + 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++ + + default: + // Transaction is regarded as invalid, drop all consecutive transactions from + // the same sender because of `nonce-too-high` clause. + log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) + return errCouldNotApplyTransaction + } + } + 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) + } + return nil +} + func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { gasLimit := env.header.GasLimit if env.gasPool == nil { @@ -1121,6 +1200,16 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err if env.header.ExcessBlobGas != nil { filter.BlobFee = uint256.MustFromBig(eip4844.CalcBlobFee(*env.header.ExcessBlobGas)) } + + bundles := w.filterBundles() + for _, bundle := range bundles { + if err := w.commitBundle(env, bundle.OriginalBundle, interrupt); err != nil { + log.Warn("Failed to force-include bundle", "err", err) + continue + } + break + } + filter.OnlyPlainTxs, filter.OnlyBlobTxs = true, false pendingPlainTxs := w.eth.TxPool().Pending(filter) From 05d72e16b31fa8ac3cd1efc11f76b58733073099 Mon Sep 17 00:00:00 2001 From: avalonche Date: Thu, 13 Jun 2024 13:22:45 +1000 Subject: [PATCH 2/3] remove block number check --- miner/simulated.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/miner/simulated.go b/miner/simulated.go index 43c0026f25..22f96fc85c 100644 --- a/miner/simulated.go +++ b/miner/simulated.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rpc" + // "github.com/ethereum/go-ethereum/rpc" "github.com/holiman/uint256" ) @@ -20,9 +20,9 @@ var ( ) func (w *worker) simulateBundle(bundle types.MevBundle) (*types.SimulatedBundle, error) { - if bundle.BlockNumber.Int64() != rpc.PendingBlockNumber.Int64() { - return nil, ErrInvalidInclusion - } + // if bundle.BlockNumber.Int64() != rpc.PendingBlockNumber.Int64() { + // return nil, ErrInvalidInclusion + // } header := w.chain.CurrentBlock() statedb, err := w.chain.StateAt(header.Root) From 84206e1a8b413e641b6d34751eb9f116743d2079 Mon Sep 17 00:00:00 2001 From: avalonche Date: Fri, 14 Jun 2024 05:43:32 +1000 Subject: [PATCH 3/3] bug fixes --- eth/api_backend.go | 2 +- miner/simulated.go | 21 +++++++++------------ miner/worker.go | 4 +++- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/eth/api_backend.go b/eth/api_backend.go index a6f73aaecd..3d202b659f 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -338,7 +338,7 @@ func (b *EthAPIBackend) SendInteropBundle(ctx context.Context, txs types.Transac if blockNumber == rpc.PendingBlockNumber { bundle := types.MevBundle{ Txs: txs, - BlockNumber: big.NewInt(int64(b.eth.blockchain.CurrentBlock().Number.Uint64() + 1)), + BlockNumber: big.NewInt(int64(b.eth.blockchain.CurrentBlock().Number.Uint64() + 2)), MinTimestamp: minTimestamp, MaxTimestamp: maxTimestamp, Hash: bundleHash, diff --git a/miner/simulated.go b/miner/simulated.go index 22f96fc85c..0fd8645f73 100644 --- a/miner/simulated.go +++ b/miner/simulated.go @@ -2,28 +2,23 @@ package miner import ( "errors" + "math/big" "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - // "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/log" "github.com/holiman/uint256" ) var ( - ErrInvalidInclusion = errors.New("invalid inclusion") - ErrTxFailed = errors.New("tx failed") ErrNegativeProfit = errors.New("negative profit") ErrInvalidBundle = errors.New("invalid bundle") ) func (w *worker) simulateBundle(bundle types.MevBundle) (*types.SimulatedBundle, error) { - // if bundle.BlockNumber.Int64() != rpc.PendingBlockNumber.Int64() { - // return nil, ErrInvalidInclusion - // } - header := w.chain.CurrentBlock() statedb, err := w.chain.StateAt(header.Root) if err != nil { @@ -85,9 +80,10 @@ func (w *worker) simulateBundle(bundle types.MevBundle) (*types.SimulatedBundle, ExecError: execError, OriginalBundle: bundle, } - w.mu.Lock() - defer w.mu.Unlock() - if execError != "" && totalProfit.Sign() < 0 { + log.Info("simulated bundle", "bundle", bundle.Hash, "totalProfit", totalProfit, "gasUsed", gasUsed, "execError", execError) + if revert == nil && totalProfit.Sign() >= 0 { + w.mu.Lock() + defer w.mu.Unlock() w.simulatedBundles = append(w.simulatedBundles, simBundle) } return simBundle, nil @@ -106,7 +102,7 @@ func (w *worker) getTopBundle() (*types.SimulatedBundle, error) { return nil, ErrInvalidBundle } -func (w *worker) filterBundles() []*types.SimulatedBundle { +func (w *worker) filterBundles(blockNumber *big.Int) []*types.SimulatedBundle { w.mu.Lock() defer w.mu.Unlock() @@ -114,7 +110,8 @@ func (w *worker) filterBundles() []*types.SimulatedBundle { var ret []*types.SimulatedBundle for _, bundle := range w.simulatedBundles { - if bundle.OriginalBundle.BlockNumber.Cmp(w.current.header.Number) > 0 { + log.Info("filtering bundle", "bundle", bundle.OriginalBundle.Hash, "block", bundle.OriginalBundle.BlockNumber, "current", blockNumber.String()) + if bundle.OriginalBundle.BlockNumber.Cmp(blockNumber) < 0 { continue } diff --git a/miner/worker.go b/miner/worker.go index 77d522bcec..cd6e040967 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1201,7 +1201,8 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err filter.BlobFee = uint256.MustFromBig(eip4844.CalcBlobFee(*env.header.ExcessBlobGas)) } - bundles := w.filterBundles() + log.Info("Trying to include bundles", "bundles", len(w.simulatedBundles)) + bundles := w.filterBundles(env.header.Number) for _, bundle := range bundles { if err := w.commitBundle(env, bundle.OriginalBundle, interrupt); err != nil { log.Warn("Failed to force-include bundle", "err", err) @@ -1209,6 +1210,7 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err } break } + log.Info("included bundles", "bundles", len(bundles)) filter.OnlyPlainTxs, filter.OnlyBlobTxs = true, false pendingPlainTxs := w.eth.TxPool().Pending(filter)