diff --git a/op-batcher/batcher/txmgr.go b/op-batcher/batcher/txmgr.go index 3cc6e89812cc..3d19879c9a1a 100644 --- a/op-batcher/batcher/txmgr.go +++ b/op-batcher/batcher/txmgr.go @@ -6,6 +6,7 @@ import ( "math/big" "time" + opservice "github.com/ethereum-optimism/optimism/op-service" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -86,10 +87,14 @@ func (t *TransactionManager) calcGasTipAndFeeCap(ctx context.Context) (gasTipCap if err != nil || head == nil { return nil, nil, fmt.Errorf("failed to get L1 head block for fee cap: %w", err) } - if head.BaseFee == nil { - return nil, nil, fmt.Errorf("failed to get L1 basefee in block %d for fee cap", head.Number) + if opservice.ForBSC { + gasFeeCap = txmgr.CalcGasFeeCap(big.NewInt(0), gasTipCap) + } else { + if head.BaseFee == nil { + return nil, nil, fmt.Errorf("failed to get L1 basefee in block %d for fee cap", head.Number) + } + gasFeeCap = txmgr.CalcGasFeeCap(head.BaseFee, gasTipCap) } - gasFeeCap = txmgr.CalcGasFeeCap(head.BaseFee, gasTipCap) return gasTipCap, gasFeeCap, nil } @@ -110,14 +115,20 @@ func (t *TransactionManager) CraftTx(ctx context.Context, data []byte) (*types.T return nil, fmt.Errorf("failed to get nonce: %w", err) } - rawTx := &types.DynamicFeeTx{ - ChainID: t.chainID, - Nonce: nonce, - To: &t.batchInboxAddress, - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - Data: data, + rawTx := &types.LegacyTx{ + Nonce: nonce, + To: &t.batchInboxAddress, + GasPrice: big.NewInt(0).Add(gasTipCap, gasFeeCap), + Data: data, } + // rawTx := &types.DynamicFeeTx{ + // ChainID: t.chainID, + // Nonce: nonce, + // To: &t.batchInboxAddress, + // GasTipCap: gasTipCap, + // GasFeeCap: gasFeeCap, + // Data: data, + // } t.log.Info("creating tx", "to", rawTx.To, "from", t.senderAddress) gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index f8b5c8454f32..b2182e752633 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -20,6 +20,8 @@ import ( "github.com/ethereum-optimism/optimism/op-chain-ops/state" "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + opservice "github.com/ethereum-optimism/optimism/op-service" ) var ( @@ -420,8 +422,10 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage if block.Number() == nil { return storage, errors.New("block number not set") } - if block.BaseFee() == nil { - return storage, errors.New("block base fee not set") + if !opservice.ForBSC { + if block.BaseFee() == nil { + return storage, errors.New("block base fee not set") + } } storage["L2ToL1MessagePasser"] = state.StorageValues{ @@ -434,8 +438,8 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage "msgNonce": 0, } storage["L1Block"] = state.StorageValues{ - "number": block.Number(), - "timestamp": block.Time(), + "number": block.Number(), + "timestamp": block.Time(), "basefee": block.BaseFee(), "hash": block.Hash(), "sequenceNumber": 0, @@ -443,6 +447,9 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage "l1FeeOverhead": config.GasPriceOracleOverhead, "l1FeeScalar": config.GasPriceOracleScalar, } + if opservice.ForBSC { + storage["L1Block"]["basefee"] = derive.BSCFakeBaseFee + } storage["LegacyERC20ETH"] = state.StorageValues{ "_name": "Ether", "_symbol": "ETH", diff --git a/op-e2e/actions/action.go b/op-e2e/actions/action.go index 53b710a44e21..50ffadb35147 100644 --- a/op-e2e/actions/action.go +++ b/op-e2e/actions/action.go @@ -5,6 +5,7 @@ import ( "os" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" + opservice "github.com/ethereum-optimism/optimism/op-service" ) var enableParallelTesting bool = true @@ -13,6 +14,7 @@ func init() { if os.Getenv("OP_E2E_DISABLE_PARALLEL") == "true" { enableParallelTesting = false } + opservice.ForBSC = false } func parallel(t e2eutils.TestingBase) { diff --git a/op-e2e/system_test.go b/op-e2e/system_test.go index ce6b30bccd02..07e1bdcb5450 100644 --- a/op-e2e/system_test.go +++ b/op-e2e/system_test.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/withdrawals" + opservice "github.com/ethereum-optimism/optimism/op-service" ) var enableParallelTesting bool = true @@ -53,6 +54,7 @@ func init() { if os.Getenv("OP_E2E_DISABLE_PARALLEL") == "true" { enableParallelTesting = false } + opservice.ForBSC = false } func parallel(t *testing.T) { diff --git a/op-node/eth/heads.go b/op-node/eth/heads.go index af92990f0f1d..0ea669553a2d 100644 --- a/op-node/eth/heads.go +++ b/op-node/eth/heads.go @@ -8,6 +8,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" + + opservice "github.com/ethereum-optimism/optimism/op-service" ) // HeadSignalFn is used as callback function to accept head-signals @@ -48,8 +50,11 @@ func WatchHeadChanges(ctx context.Context, src NewHeadSource, fn HeadSignalFn) ( type L1BlockRefsSource interface { L1BlockRefByLabel(ctx context.Context, label BlockLabel) (L1BlockRef, error) + L1BlockRefByNumber(ctx context.Context, num uint64) (L1BlockRef, error) } +var finalizedBlockNumberForBSC uint64 = 15 + // PollBlockChanges opens a polling loop to fetch the L1 block reference with the given label, // on provided interval and with request timeout. Results are returned with provided callback fn, // which may block to pause/back-pressure polling. @@ -72,7 +77,24 @@ func PollBlockChanges(ctx context.Context, log log.Logger, src L1BlockRefsSource if err != nil { log.Warn("failed to poll L1 block", "label", label, "err", err) } else { - fn(ctx, ref) + if opservice.ForBSC { + reqCtx, reqCancel := context.WithTimeout(ctx, timeout) + number := ref.Number + if number < finalizedBlockNumberForBSC { + number = 0 + } else { + number -= finalizedBlockNumberForBSC + } + ref, err := src.L1BlockRefByNumber(reqCtx, number) + reqCancel() + if err != nil { + log.Warn("failed to poll L1 block", "number", number, "err", err) + } else { + fn(ctx, ref) + } + } else { + fn(ctx, ref) + } } case <-ctx.Done(): return ctx.Err() diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index df0deb56be5f..1f6c418510fe 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -99,6 +99,13 @@ var ( Usage: "Initialize the sequencer in a stopped state. The sequencer can be started using the admin_startSequencer RPC", EnvVar: prefixEnvVar("SEQUENCER_STOPPED"), } + SequencerMaxSafeLagFlag = cli.Uint64Flag{ + Name: "sequencer.max-safe-lag", + Usage: "Maximum number of L2 blocks for restricting the distance between L2 safe and unsafe", + EnvVar: prefixEnvVar("SEQUENCER_MAX_SAFE_LAG"), + Required: false, + Value: 0, + } SequencerL1Confs = cli.Uint64Flag{ Name: "sequencer.l1-confs", Usage: "Number of L1 blocks to keep distance from the L1 head as a sequencer for picking an L1 origin.", @@ -203,6 +210,7 @@ var optionalFlags = append([]cli.Flag{ VerifierL1Confs, SequencerEnabledFlag, SequencerStoppedFlag, + SequencerMaxSafeLagFlag, SequencerL1Confs, L1EpochPollIntervalFlag, LogLevelFlag, diff --git a/op-node/node/node.go b/op-node/node/node.go index ed194818d2f8..b44ce2fce605 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -145,9 +145,9 @@ func (n *OpNode) initL1(ctx context.Context, cfg *Config) error { // Poll for the safe L1 block and finalized block, // which only change once per epoch at most and may be delayed. - n.l1SafeSub = eth.PollBlockChanges(n.resourcesCtx, n.log, n.l1Source, n.OnNewL1Safe, eth.Safe, + n.l1SafeSub = eth.PollBlockChanges(n.resourcesCtx, n.log, n.l1Source, n.OnNewL1Safe, eth.Unsafe, cfg.L1EpochPollInterval, time.Second*10) - n.l1FinalizedSub = eth.PollBlockChanges(n.resourcesCtx, n.log, n.l1Source, n.OnNewL1Finalized, eth.Finalized, + n.l1FinalizedSub = eth.PollBlockChanges(n.resourcesCtx, n.log, n.l1Source, n.OnNewL1Finalized, eth.Unsafe, cfg.L1EpochPollInterval, time.Second*10) return nil } diff --git a/op-node/rollup/derive/l1_block_info.go b/op-node/rollup/derive/l1_block_info.go index 0e4733d71069..7c45ac08571d 100644 --- a/op-node/rollup/derive/l1_block_info.go +++ b/op-node/rollup/derive/l1_block_info.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-node/eth" + opservice "github.com/ethereum-optimism/optimism/op-service" ) const ( @@ -54,11 +55,13 @@ func (info *L1BlockInfo) MarshalBinary() ([]byte, error) { offset += 32 binary.BigEndian.PutUint64(data[offset+24:offset+32], info.Time) offset += 32 - // Ensure that the baseFee is not too large. - if info.BaseFee.BitLen() > 256 { - return nil, fmt.Errorf("base fee exceeds 256 bits: %d", info.BaseFee) + if info.BaseFee != nil { + // Ensure that the baseFee is not too large. + if info.BaseFee.BitLen() > 256 { + return nil, fmt.Errorf("base fee exceeds 256 bits: %d", info.BaseFee) + } + info.BaseFee.FillBytes(data[offset : offset+32]) } - info.BaseFee.FillBytes(data[offset : offset+32]) offset += 32 copy(data[offset:offset+32], info.BlockHash.Bytes()) offset += 32 @@ -117,6 +120,8 @@ func L1InfoDepositTxData(data []byte) (L1BlockInfo, error) { return info, err } +var BSCFakeBaseFee = big.NewInt(5000000000) + // L1InfoDeposit creates a L1 Info deposit transaction based on the L1 block, // and the L2 block-height difference with the start of the epoch. func L1InfoDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfig, regolith bool) (*types.DepositTx, error) { @@ -130,6 +135,9 @@ func L1InfoDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfi L1FeeOverhead: sysCfg.Overhead, L1FeeScalar: sysCfg.Scalar, } + if opservice.ForBSC { + infoDat.BaseFee = BSCFakeBaseFee + } data, err := infoDat.MarshalBinary() if err != nil { return nil, err diff --git a/op-node/rollup/derive/l1_block_info_test.go b/op-node/rollup/derive/l1_block_info_test.go index 721046178eee..7a841e5b5f41 100644 --- a/op-node/rollup/derive/l1_block_info_test.go +++ b/op-node/rollup/derive/l1_block_info_test.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/testutils" + opservice "github.com/ethereum-optimism/optimism/op-service" ) var _ eth.BlockInfo = (*testutils.MockBlockInfo)(nil) @@ -35,6 +36,7 @@ func randomL1Cfg(rng *rand.Rand, l1Info eth.BlockInfo) eth.SystemConfig { var MockDepositContractAddr = common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeef00000000") func TestParseL1InfoDepositTxData(t *testing.T) { + opservice.ForBSC = false randomSeqNr := func(rng *rand.Rand) uint64 { return rng.Uint64() } diff --git a/op-node/rollup/driver/config.go b/op-node/rollup/driver/config.go index e59ae55c9bae..906770e8af32 100644 --- a/op-node/rollup/driver/config.go +++ b/op-node/rollup/driver/config.go @@ -16,4 +16,7 @@ type Config struct { // SequencerStopped is false when the driver should sequence new blocks. SequencerStopped bool `json:"sequencer_stopped"` + + // SequencerMaxSafeLag is the maximum number of L2 blocks for restricting the distance between L2 safe and unsafe. + SequencerMaxSafeLag uint64 `json:"sequencer_max_safe_lag"` } diff --git a/op-node/service.go b/op-node/service.go index 39c3a17ab7d9..ce737dcdefff 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -136,10 +136,11 @@ func NewL2EndpointConfig(ctx *cli.Context, log log.Logger) (*node.L2EndpointConf func NewDriverConfig(ctx *cli.Context) (*driver.Config, error) { return &driver.Config{ - VerifierConfDepth: ctx.GlobalUint64(flags.VerifierL1Confs.Name), - SequencerConfDepth: ctx.GlobalUint64(flags.SequencerL1Confs.Name), - SequencerEnabled: ctx.GlobalBool(flags.SequencerEnabledFlag.Name), - SequencerStopped: ctx.GlobalBool(flags.SequencerStoppedFlag.Name), + VerifierConfDepth: ctx.GlobalUint64(flags.VerifierL1Confs.Name), + SequencerConfDepth: ctx.GlobalUint64(flags.SequencerL1Confs.Name), + SequencerEnabled: ctx.GlobalBool(flags.SequencerEnabledFlag.Name), + SequencerStopped: ctx.GlobalBool(flags.SequencerStoppedFlag.Name), + SequencerMaxSafeLag: ctx.GlobalUint64(flags.SequencerMaxSafeLagFlag.Name), }, nil } diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index cde9c485b673..c38d301b327f 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -19,11 +19,13 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/urfave/cli" "github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/sources" + opservice "github.com/ethereum-optimism/optimism/op-service" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" oplog "github.com/ethereum-optimism/optimism/op-service/log" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" @@ -321,6 +323,39 @@ func (l *L2OutputSubmitter) FetchNextOutputInfo(ctx context.Context) (*eth.Outpu return output, true, nil } +// calcGasTipAndFeeCap queries L1 to determine what a suitable miner tip & basefee limit would be for timely inclusion +func (t *L2OutputSubmitter) calcGasTipAndFeeCap(ctx context.Context) (gasTipCap *big.Int, gasFeeCap *big.Int, err error) { + networkTimeout := 10 * time.Second + childCtx, cancel := context.WithTimeout(ctx, networkTimeout) + gasTipCap, err = t.l1Client.SuggestGasTipCap(childCtx) + cancel() + if err != nil { + return nil, nil, fmt.Errorf("failed to get suggested gas tip cap: %w", err) + } + + if gasTipCap == nil { + t.log.Warn("unexpected unset gasTipCap, using default 2 gwei") + gasTipCap = new(big.Int).SetUint64(params.GWei * 2) + } + + childCtx, cancel = context.WithTimeout(ctx, networkTimeout) + head, err := t.l1Client.HeaderByNumber(childCtx, nil) + cancel() + if err != nil || head == nil { + return nil, nil, fmt.Errorf("failed to get L1 head block for fee cap: %w", err) + } + if opservice.ForBSC { + gasFeeCap = txmgr.CalcGasFeeCap(big.NewInt(0), gasTipCap) + } else { + if head.BaseFee == nil { + return nil, nil, fmt.Errorf("failed to get L1 basefee in block %d for fee cap", head.Number) + } + gasFeeCap = txmgr.CalcGasFeeCap(head.BaseFee, gasTipCap) + } + + return gasTipCap, gasFeeCap, nil +} + // CreateProposalTx transforms an output response into a signed output transaction. // It does not send the transaction to the transaction pool. func (l *L2OutputSubmitter) CreateProposalTx(ctx context.Context, output *eth.OutputResponse) (*types.Transaction, error) { @@ -330,21 +365,28 @@ func (l *L2OutputSubmitter) CreateProposalTx(ctx context.Context, output *eth.Ou return nil, err } + gasTipCap, gasFeeCap, err := l.calcGasTipAndFeeCap(ctx) + if err != nil { + return nil, err + } + opts := &bind.TransactOpts{ From: l.from, Signer: func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { return l.signerFn(ctx, addr, tx) }, - Context: ctx, - Nonce: new(big.Int).SetUint64(nonce), - NoSend: true, + Context: ctx, + Nonce: new(big.Int).SetUint64(nonce), + GasPrice: big.NewInt(0).Add(gasTipCap, gasFeeCap), + NoSend: true, } tx, err := l.l2ooContract.ProposeL2Output( opts, output.OutputRoot, new(big.Int).SetUint64(output.BlockRef.Number), - output.Status.CurrentL1.Hash, + // output.Status.CurrentL1.Hash, + [32]byte{}, new(big.Int).SetUint64(output.Status.CurrentL1.Number)) if err != nil { l.log.Error("failed to create the ProposeL2Output transaction", "err", err) diff --git a/op-service/util.go b/op-service/util.go index 2f282dccffa0..41e1497c6a34 100644 --- a/op-service/util.go +++ b/op-service/util.go @@ -9,6 +9,8 @@ import ( "time" ) +var ForBSC = true + func PrefixEnvVar(prefix, suffix string) string { return prefix + "_" + suffix } diff --git a/op-signer/client/transaction_args.go b/op-signer/client/transaction_args.go index 480d46a15e10..7cfec5069fdb 100644 --- a/op-signer/client/transaction_args.go +++ b/op-signer/client/transaction_args.go @@ -65,20 +65,28 @@ func (args *TransactionArgs) data() []byte { // ToTransaction converts the arguments to a transaction. func (args *TransactionArgs) ToTransaction() *types.Transaction { var data types.TxData - al := types.AccessList{} - if args.AccessList != nil { - al = *args.AccessList - } - data = &types.DynamicFeeTx{ - To: args.To, - ChainID: (*big.Int)(args.ChainID), - Nonce: uint64(*args.Nonce), - Gas: uint64(*args.Gas), - GasFeeCap: (*big.Int)(args.MaxFeePerGas), - GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), - Value: (*big.Int)(args.Value), - Data: args.data(), - AccessList: al, + // al := types.AccessList{} + // if args.AccessList != nil { + // al = *args.AccessList + // } + // data = &types.DynamicFeeTx{ + // To: args.To, + // ChainID: (*big.Int)(args.ChainID), + // Nonce: uint64(*args.Nonce), + // Gas: uint64(*args.Gas), + // GasFeeCap: (*big.Int)(args.MaxFeePerGas), + // GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), + // Value: (*big.Int)(args.Value), + // Data: args.data(), + // AccessList: al, + // } + data = &types.LegacyTx{ + To: args.To, + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasPrice: args.GasPrice.ToInt(), + Value: (*big.Int)(args.Value), + Data: args.data(), } return types.NewTx(data) }