diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b1d23fdd19..12993cfa80 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -156,6 +156,7 @@ var ( utils.RollupHistoricalRPCFlag, utils.RollupHistoricalRPCTimeoutFlag, utils.RollupDisableTxPoolGossipFlag, + utils.RollupComputePendingBlock, configFileFlag, }, utils.NetworkFlags, utils.DatabasePathFlags) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7b17390d97..0d3641afcc 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -941,6 +941,11 @@ var ( Usage: "Disable transaction pool gossip.", Category: flags.RollupCategory, } + RollupComputePendingBlock = &cli.BoolFlag{ + Name: "rollup.computependingblock", + Usage: "By default the pending block equals the latest block to save resources and not leak txs from the tx-pool, this flag enables computing of the pending block from the tx-pool instead.", + Category: flags.RollupCategory, + } // Metrics flags MetricsEnabledFlag = &cli.BoolFlag{ @@ -1696,6 +1701,9 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { if ctx.IsSet(MinerNewPayloadTimeout.Name) { cfg.NewPayloadTimeout = ctx.Duration(MinerNewPayloadTimeout.Name) } + if ctx.IsSet(RollupComputePendingBlock.Name) { + cfg.RollupComputePendingBlock = ctx.Bool(RollupComputePendingBlock.Name) + } } func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) { diff --git a/core/types/receipt.go b/core/types/receipt.go index 874e10efb0..8e0b3a9ef5 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -536,7 +536,8 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu if !txs[i].IsDepositTx() { gas := txs[i].RollupDataGas().DataGas(time, config) rs[i].L1GasPrice = l1Basefee - rs[i].L1GasUsed = new(big.Int).SetUint64(gas) + // GasUsed reported in receipt should include the overhead + rs[i].L1GasUsed = new(big.Int).Add(new(big.Int).SetUint64(gas), overhead) rs[i].L1Fee = L1Cost(gas, l1Basefee, overhead, scalar) rs[i].FeeScalar = feeScalar } diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 3f3df03983..2014fad2b1 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -347,6 +347,115 @@ func TestDeriveFields(t *testing.T) { } } +func TestDeriveOptimismTxReceipt(t *testing.T) { + to4 := common.HexToAddress("0x4") + // Create a few transactions to have receipts for + txs := Transactions{ + NewTx(&DepositTx{ + To: nil, // contract creation + Value: big.NewInt(6), + Gas: 50, + // System config with L1Scalar=2_000_000 (becomes 2 after division), L1Overhead=2500, L1BaseFee=5000 + Data: common.Hex2Bytes("015d8eb900000000000000000000000000000000000000000000000026b39534042076f70000000000000000000000000000000000000000000000007e33b7c4995967580000000000000000000000000000000000000000000000000000000000001388547dea8ff339566349ed0ef6384876655d1b9b955e36ac165c6b8ab69b9af5cd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123400000000000000000000000000000000000000000000000000000000000009c400000000000000000000000000000000000000000000000000000000001e8480"), + }), + NewTx(&DynamicFeeTx{ + To: &to4, + Nonce: 4, + Value: big.NewInt(4), + Gas: 4, + GasTipCap: big.NewInt(44), + GasFeeCap: big.NewInt(1045), + Data: []byte{0, 1, 255, 0}, + }), + } + depNonce := uint64(7) + blockNumber := big.NewInt(1) + blockHash := common.BytesToHash([]byte{0x03, 0x14}) + + // Create the corresponding receipts + receipts := Receipts{ + &Receipt{ + Type: DepositTxType, + PostState: common.Hash{5}.Bytes(), + CumulativeGasUsed: 50 + 15, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x33}), + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 0, + }, + { + Address: common.BytesToAddress([]byte{0x03, 0x33}), + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 1, + }, + }, + TxHash: txs[0].Hash(), + ContractAddress: common.HexToAddress("0x3bb898b4bbe24f68a4e9be46cfe72d1787fd74f4"), + GasUsed: 65, + EffectiveGasPrice: big.NewInt(0), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 0, + DepositNonce: &depNonce, + }, + &Receipt{ + Type: DynamicFeeTxType, + PostState: common.Hash{4}.Bytes(), + CumulativeGasUsed: 10, + Logs: []*Log{}, + // derived fields: + TxHash: txs[1].Hash(), + GasUsed: 18446744073709551561, + EffectiveGasPrice: big.NewInt(1044), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 1, + L1GasPrice: big.NewInt(5000), + L1GasUsed: big.NewInt(3976), + L1Fee: big.NewInt(39760000), + FeeScalar: big.NewFloat(2), + }, + } + + // Re-derive receipts. + basefee := big.NewInt(1000) + derivedReceipts := clearComputedFieldsOnReceipts(receipts) + err := Receipts(derivedReceipts).DeriveFields(params.OptimismTestConfig, blockHash, blockNumber.Uint64(), 0, basefee, txs) + if err != nil { + t.Fatalf("DeriveFields(...) = %v, want ", err) + } + + // Check diff of receipts against derivedReceipts. + r1, err := json.MarshalIndent(receipts, "", " ") + if err != nil { + t.Fatal("error marshaling input receipts:", err) + } + r2, err := json.MarshalIndent(derivedReceipts, "", " ") + if err != nil { + t.Fatal("error marshaling derived receipts:", err) + } + d := diff.Diff(string(r1), string(r2)) + if d != "" { + t.Fatal("receipts differ:", d) + } + + // Check that we preserved the invariant: l1Fee = l1GasPrice * l1GasUsed * l1FeeScalar + // but with more difficult int math... + l2Rcpt := derivedReceipts[1] + l1GasCost := new(big.Int).Mul(l2Rcpt.L1GasPrice, l2Rcpt.L1GasUsed) + l1Fee := new(big.Float).Mul(new(big.Float).SetInt(l1GasCost), l2Rcpt.FeeScalar) + require.Equal(t, new(big.Float).SetInt(l2Rcpt.L1Fee), l1Fee) +} + // TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt // rlp decoder, which failed due to a shadowing error. func TestTypedReceiptEncodingDecoding(t *testing.T) { diff --git a/eth/api.go b/eth/api.go index 92b7f00fd4..ed9d9c72d2 100644 --- a/eth/api.go +++ b/eth/api.go @@ -265,6 +265,9 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { // both the pending block as well as the pending state from // the miner and operate on those _, stateDb := api.eth.miner.Pending() + if stateDb == nil { + return state.Dump{}, errors.New("no pending state") + } return stateDb.RawDump(opts), nil } var header *types.Header @@ -350,6 +353,9 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex // both the pending block as well as the pending state from // the miner and operate on those _, stateDb = api.eth.miner.Pending() + if stateDb == nil { + return state.IteratorDump{}, errors.New("no pending state") + } } else { var header *types.Header if number == rpc.LatestBlockNumber { diff --git a/eth/api_backend.go b/eth/api_backend.go index 2f339d7a33..d58f6ca86a 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -199,7 +199,11 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B // Pending state is only known by the miner if number == rpc.PendingBlockNumber { block, state := b.eth.miner.Pending() - return state, block.Header(), nil + if block != nil { + return state, block.Header(), nil + } else { + number = rpc.LatestBlockNumber + } } // Otherwise resolve the block number and return its state header, err := b.HeaderByNumber(ctx, number) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 97575366f4..60ab06b1ec 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -774,7 +774,7 @@ func (s *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockN header, err := s.b.HeaderByNumber(ctx, number) if header != nil && err == nil { response := s.rpcMarshalHeader(ctx, header) - if number == rpc.PendingBlockNumber { + if number == rpc.PendingBlockNumber && s.b.ChainConfig().Optimism == nil { // don't remove info if optimism // Pending header need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { response[field] = nil @@ -803,7 +803,7 @@ func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNu block, err := s.b.BlockByNumber(ctx, number) if block != nil && err == nil { response, err := s.rpcMarshalBlock(ctx, block, true, fullTx) - if err == nil && number == rpc.PendingBlockNumber { + if err == nil && number == rpc.PendingBlockNumber && s.b.ChainConfig().Optimism == nil { // don't remove info if optimism // Pending blocks need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { response[field] = nil diff --git a/miner/miner.go b/miner/miner.go index aa17a57967..d07c0709f6 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -62,6 +62,8 @@ type Config struct { Noverify bool // Disable remote mining solution verification(only useful in ethash). NewPayloadTimeout time.Duration // The maximum time allowance for creating a new payload + + RollupComputePendingBlock bool // Compute the pending block from tx-pool, instead of copying the latest-block } // DefaultConfig contains default settings for miner. diff --git a/miner/worker.go b/miner/worker.go index f09bc32058..7829b6e2bf 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -384,6 +384,9 @@ func (w *worker) enablePreseal() { // pending returns the pending state and corresponding block. func (w *worker) pending() (*types.Block, *state.StateDB) { + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + return nil, nil // when not computing the pending block, there is never a pending state + } // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() @@ -395,6 +398,12 @@ func (w *worker) pending() (*types.Block, *state.StateDB) { // pendingBlock returns pending block. func (w *worker) pendingBlock() *types.Block { + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + // For compatibility when not computing a pending block, we serve the latest block as "pending" + headHeader := w.eth.BlockChain().CurrentHeader() + headBlock := w.eth.BlockChain().GetBlock(headHeader.Hash(), headHeader.Number.Uint64()) + return headBlock + } // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() @@ -403,6 +412,9 @@ func (w *worker) pendingBlock() *types.Block { // pendingBlockAndReceipts returns pending block and corresponding receipts. func (w *worker) pendingBlockAndReceipts() (*types.Block, types.Receipts) { + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + return nil, nil // when not computing the pending block, there are no pending receipts, and thus no pending logs + } // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() @@ -458,6 +470,19 @@ func recalcRecommit(minRecommit, prev time.Duration, target float64, inc bool) t // newWorkLoop is a standalone goroutine to submit new sealing work upon received events. func (w *worker) newWorkLoop(recommit time.Duration) { defer w.wg.Done() + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + for { // do not update the pending-block, instead drain work without doing it, to keep producers from blocking. + select { + case <-w.startCh: + case <-w.chainHeadCh: + case <-w.resubmitIntervalCh: + case <-w.resubmitAdjustCh: + case <-w.exitCh: + return + } + } + } + var ( interrupt *int32 minRecommit = recommit // minimal resubmit interval specified by user. @@ -620,6 +645,9 @@ func (w *worker) mainLoop() { } case ev := <-w.txsCh: + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + continue // don't update the pending-block snapshot if we are not computing the pending block + } // Apply transactions to the pending state if we're not sealing // // Note all transactions received may not be continuous with transactions