diff --git a/l2geth/eth/api_backend.go b/l2geth/eth/api_backend.go index ee4db21fe3ef..86f00795a4ff 100644 --- a/l2geth/eth/api_backend.go +++ b/l2geth/eth/api_backend.go @@ -377,6 +377,14 @@ func (b *EthAPIBackend) SuggestL2GasPrice(ctx context.Context) (*big.Int, error) return b.rollupGpo.SuggestL2GasPrice(ctx) } +func (b *EthAPIBackend) SuggestPreviousL1GasPrice(ctx context.Context) (*big.Int, error) { + return b.rollupGpo.SuggestPreviousL1GasPrice(ctx) +} + +func (b *EthAPIBackend) SuggestPreviousL2GasPrice(ctx context.Context) (*big.Int, error) { + return b.rollupGpo.SuggestPreviousL2GasPrice(ctx) +} + func (b *EthAPIBackend) SetL1GasPrice(ctx context.Context, gasPrice *big.Int) error { return b.rollupGpo.SetL1GasPrice(gasPrice) } @@ -385,6 +393,14 @@ func (b *EthAPIBackend) SetL2GasPrice(ctx context.Context, gasPrice *big.Int) er return b.rollupGpo.SetL2GasPrice(gasPrice) } +func (b *EthAPIBackend) SetPreviousL1GasPrice(ctx context.Context, gasPrice *big.Int) error { + return b.rollupGpo.SetPreviousL1GasPrice(gasPrice) +} + +func (b *EthAPIBackend) SetPreviousL2GasPrice(ctx context.Context, gasPrice *big.Int) error { + return b.rollupGpo.SetPreviousL2GasPrice(gasPrice) +} + func (b *EthAPIBackend) ChainDb() ethdb.Database { return b.eth.ChainDb() } diff --git a/l2geth/eth/gasprice/rollup_gasprice.go b/l2geth/eth/gasprice/rollup_gasprice.go index ac4166dea95e..ae74297d4874 100644 --- a/l2geth/eth/gasprice/rollup_gasprice.go +++ b/l2geth/eth/gasprice/rollup_gasprice.go @@ -10,19 +10,27 @@ import ( // RollupOracle holds the L1 and L2 gas prices for fee calculation type RollupOracle struct { - l1GasPrice *big.Int - l2GasPrice *big.Int - l1GasPriceLock sync.RWMutex - l2GasPriceLock sync.RWMutex + l1GasPrice *big.Int + l2GasPrice *big.Int + previousL1GasPrice *big.Int + previousL2GasPrice *big.Int + l1GasPriceLock sync.RWMutex + l2GasPriceLock sync.RWMutex + previousL1GasPriceLock sync.RWMutex + previousL2GasPriceLock sync.RWMutex } // NewRollupOracle returns an initialized RollupOracle func NewRollupOracle() *RollupOracle { return &RollupOracle{ - l1GasPrice: new(big.Int), - l2GasPrice: new(big.Int), - l1GasPriceLock: sync.RWMutex{}, - l2GasPriceLock: sync.RWMutex{}, + l1GasPrice: new(big.Int), + l2GasPrice: new(big.Int), + previousL1GasPrice: new(big.Int), + previousL2GasPrice: new(big.Int), + l1GasPriceLock: sync.RWMutex{}, + l2GasPriceLock: sync.RWMutex{}, + previousL1GasPriceLock: sync.RWMutex{}, + previousL2GasPriceLock: sync.RWMutex{}, } } @@ -43,6 +51,23 @@ func (gpo *RollupOracle) SetL1GasPrice(gasPrice *big.Int) error { return nil } +// SuggestL1GasPrice returns the gas price which should be charged per byte of published +// data by the sequencer. +func (gpo *RollupOracle) SuggestPreviousL1GasPrice(ctx context.Context) (*big.Int, error) { + gpo.previousL1GasPriceLock.RLock() + defer gpo.previousL1GasPriceLock.RUnlock() + return gpo.previousL1GasPrice, nil +} + +// SetL1GasPrice returns the current L1 gas price +func (gpo *RollupOracle) SetPreviousL1GasPrice(gasPrice *big.Int) error { + gpo.previousL1GasPriceLock.Lock() + defer gpo.previousL1GasPriceLock.Unlock() + gpo.previousL1GasPrice = gasPrice + log.Info("Set Previous L1 Gas Price", "gasprice", gpo.previousL1GasPrice) + return nil +} + // SuggestL2GasPrice returns the gas price which should be charged per unit of gas // set manually by the sequencer depending on congestion func (gpo *RollupOracle) SuggestL2GasPrice(ctx context.Context) (*big.Int, error) { @@ -59,3 +84,20 @@ func (gpo *RollupOracle) SetL2GasPrice(gasPrice *big.Int) error { log.Info("Set L2 Gas Price", "gasprice", gpo.l2GasPrice) return nil } + +// SuggestL2GasPrice returns the gas price which should be charged per unit of gas +// set manually by the sequencer depending on congestion +func (gpo *RollupOracle) SuggestPreviousL2GasPrice(ctx context.Context) (*big.Int, error) { + gpo.previousL2GasPriceLock.RLock() + defer gpo.previousL2GasPriceLock.RUnlock() + return gpo.previousL2GasPrice, nil +} + +// SetL2GasPrice returns the current L2 gas price +func (gpo *RollupOracle) SetPreviousL2GasPrice(gasPrice *big.Int) error { + gpo.previousL2GasPriceLock.Lock() + defer gpo.previousL2GasPriceLock.Unlock() + gpo.previousL2GasPrice = gasPrice + log.Info("Set Previous L2 Gas Price", "gasprice", gpo.previousL2GasPrice) + return nil +} diff --git a/l2geth/internal/ethapi/api.go b/l2geth/internal/ethapi/api.go index a06856a7c9d8..04cc515a95c8 100644 --- a/l2geth/internal/ethapi/api.go +++ b/l2geth/internal/ethapi/api.go @@ -1959,6 +1959,17 @@ func (api *PrivateRollupAPI) SetL2GasPrice(ctx context.Context, gasPrice hexutil return api.b.SetL2GasPrice(ctx, (*big.Int)(&gasPrice)) } +// SetPreviousL1GasPrice sets the gas price to be used when quoting calldata publishing costs +// to users +func (api *PrivateRollupAPI) SetPreviousL1GasPrice(ctx context.Context, gasPrice hexutil.Big) error { + return api.b.SetPreviousL1GasPrice(ctx, (*big.Int)(&gasPrice)) +} + +// SetPreviousL2GasPrice sets the gas price to be used when executing transactions on +func (api *PrivateRollupAPI) SetPreviousL2GasPrice(ctx context.Context, gasPrice hexutil.Big) error { + return api.b.SetPreviousL2GasPrice(ctx, (*big.Int)(&gasPrice)) +} + // PublicDebugAPI is the collection of Ethereum APIs exposed over the public // debugging endpoint. type PublicDebugAPI struct { diff --git a/l2geth/internal/ethapi/backend.go b/l2geth/internal/ethapi/backend.go index cded0439d8a2..dbdc6c692778 100644 --- a/l2geth/internal/ethapi/backend.go +++ b/l2geth/internal/ethapi/backend.go @@ -95,6 +95,10 @@ type Backend interface { SetL1GasPrice(context.Context, *big.Int) error SuggestL2GasPrice(context.Context) (*big.Int, error) SetL2GasPrice(context.Context, *big.Int) error + SuggestPreviousL1GasPrice(ctx context.Context) (*big.Int, error) + SetPreviousL1GasPrice(context.Context, *big.Int) error + SuggestPreviousL2GasPrice(context.Context) (*big.Int, error) + SetPreviousL2GasPrice(context.Context, *big.Int) error IngestTransactions([]*types.Transaction) error } diff --git a/l2geth/les/api_backend.go b/l2geth/les/api_backend.go index f509627ac646..39790485d584 100644 --- a/l2geth/les/api_backend.go +++ b/l2geth/les/api_backend.go @@ -296,6 +296,26 @@ func (b *LesApiBackend) SetL2GasPrice(ctx context.Context, gasPrice *big.Int) er panic("SetExecutionPrice is not implemented") } +// NB: Non sequencer nodes cannot suggest L1 gas prices. +func (b *LesApiBackend) SuggestPreviousL1GasPrice(ctx context.Context) (*big.Int, error) { + panic("SuggestPreviousL1GasPrice not implemented") +} + +// NB: Non sequencer nodes cannot suggest L2 execution gas prices. +func (b *LesApiBackend) SuggestPreviousL2GasPrice(ctx context.Context) (*big.Int, error) { + panic("SuggestPreviousL2GasPrice not implemented") +} + +// NB: Non sequencer nodes cannot set Previous L1 gas prices. +func (b *LesApiBackend) SetPreviousL1GasPrice(ctx context.Context, gasPrice *big.Int) error { + panic("SetDataPrice is not implemented") +} + +// NB: Non sequencer nodes cannot set Previous L2 execution prices. +func (b *LesApiBackend) SetPreviousL2GasPrice(ctx context.Context, gasPrice *big.Int) error { + panic("SetExecutionPrice is not implemented") +} + func (b *LesApiBackend) ChainDb() ethdb.Database { return b.eth.chainDb } diff --git a/l2geth/rollup/fees/rollup_fee.go b/l2geth/rollup/fees/rollup_fee.go index f6870aebb746..f69066ea7280 100644 --- a/l2geth/rollup/fees/rollup_fee.go +++ b/l2geth/rollup/fees/rollup_fee.go @@ -105,8 +105,8 @@ func DecodeL2GasLimitU64(gasLimit uint64) uint64 { // PaysEnoughOpts represent the options to PaysEnough type PaysEnoughOpts struct { - UserFee, ExpectedFee *big.Int - ThresholdUp, ThresholdDown *big.Float + UserFee, ExpectedFee, ExpectedPreviousFee *big.Int + ThresholdUp, ThresholdDown *big.Float } // PaysEnough returns an error if the fee is not large enough @@ -118,15 +118,21 @@ func PaysEnough(opts *PaysEnoughOpts) error { if opts.ExpectedFee == nil { return fmt.Errorf("%w: no expected fee", errMissingInput) } + if opts.ExpectedPreviousFee == nil { + return fmt.Errorf("%w: no expected previous fee", errMissingInput) + } fee := new(big.Int).Set(opts.ExpectedFee) + previousFee := new(big.Int).Set(opts.ExpectedPreviousFee) + // Allow for a downward buffer to protect against L1 gas price volatility if opts.ThresholdDown != nil { fee = mulByFloat(fee, opts.ThresholdDown) + previousFee = mulByFloat(previousFee, opts.ThresholdDown) } // Protect the sequencer from being underpaid // if user fee < expected fee, return error - if opts.UserFee.Cmp(fee) == -1 { + if opts.UserFee.Cmp(fee) == -1 && opts.UserFee.Cmp(previousFee) == -1 { return ErrFeeTooLow } // Protect users from overpaying by too much @@ -134,8 +140,15 @@ func PaysEnough(opts *PaysEnoughOpts) error { // overpaying = user fee - expected fee overpaying := new(big.Int).Sub(opts.UserFee, opts.ExpectedFee) threshold := mulByFloat(opts.ExpectedFee, opts.ThresholdUp) - // if overpaying > threshold, return error - if overpaying.Cmp(threshold) == 1 { + + // also allow previous value + overpayingPrevious := new(big.Int).Sub(opts.UserFee, opts.ExpectedPreviousFee) + thresholdPrevious := mulByFloat(opts.ExpectedPreviousFee, opts.ThresholdUp) + + // if overpaying > threshold && + // overpayingPrevious > thresholdPrevious, return error + if (overpaying.Cmp(threshold) == 1 && + overpayingPrevious.Cmp(thresholdPrevious) == 1) { return ErrFeeTooHigh } } diff --git a/l2geth/rollup/fees/rollup_fee_test.go b/l2geth/rollup/fees/rollup_fee_test.go index cc2925ac5d0c..a8d1961ce798 100644 --- a/l2geth/rollup/fees/rollup_fee_test.go +++ b/l2geth/rollup/fees/rollup_fee_test.go @@ -112,82 +112,91 @@ func TestPaysEnough(t *testing.T) { }{ "missing-gas-price": { opts: &PaysEnoughOpts{ - UserFee: nil, - ExpectedFee: new(big.Int), - ThresholdUp: nil, - ThresholdDown: nil, + UserFee: nil, + ExpectedFee: new(big.Int), + ExpectedPreviousFee: new(big.Int), + ThresholdUp: nil, + ThresholdDown: nil, }, err: errMissingInput, }, "missing-fee": { opts: &PaysEnoughOpts{ - UserFee: nil, - ExpectedFee: nil, - ThresholdUp: nil, - ThresholdDown: nil, + UserFee: nil, + ExpectedFee: nil, + ExpectedPreviousFee: nil, + ThresholdUp: nil, + ThresholdDown: nil, }, err: errMissingInput, }, "equal-fee": { opts: &PaysEnoughOpts{ - UserFee: common.Big1, - ExpectedFee: common.Big1, - ThresholdUp: nil, - ThresholdDown: nil, + UserFee: common.Big1, + ExpectedFee: common.Big1, + ExpectedPreviousFee: common.Big1, + ThresholdUp: nil, + ThresholdDown: nil, }, err: nil, }, "fee-too-low": { opts: &PaysEnoughOpts{ - UserFee: common.Big1, - ExpectedFee: common.Big2, - ThresholdUp: nil, - ThresholdDown: nil, + UserFee: common.Big1, + ExpectedFee: common.Big2, + ExpectedPreviousFee: common.Big2, + ThresholdUp: nil, + ThresholdDown: nil, }, err: ErrFeeTooLow, }, "fee-threshold-down": { opts: &PaysEnoughOpts{ - UserFee: common.Big1, - ExpectedFee: common.Big2, - ThresholdUp: nil, - ThresholdDown: new(big.Float).SetFloat64(0.5), + UserFee: common.Big1, + ExpectedFee: common.Big2, + ExpectedPreviousFee: common.Big2, + ThresholdUp: nil, + ThresholdDown: new(big.Float).SetFloat64(0.5), }, err: nil, }, "fee-threshold-up": { opts: &PaysEnoughOpts{ - UserFee: common.Big256, - ExpectedFee: common.Big1, - ThresholdUp: new(big.Float).SetFloat64(1.5), - ThresholdDown: nil, + UserFee: common.Big256, + ExpectedFee: common.Big1, + ExpectedPreviousFee: common.Big1, + ThresholdUp: new(big.Float).SetFloat64(1.5), + ThresholdDown: nil, }, err: ErrFeeTooHigh, }, "fee-too-low-high": { opts: &PaysEnoughOpts{ - UserFee: new(big.Int).SetUint64(10_000), - ExpectedFee: new(big.Int).SetUint64(1), - ThresholdUp: new(big.Float).SetFloat64(3), - ThresholdDown: new(big.Float).SetFloat64(0.8), + UserFee: new(big.Int).SetUint64(10_000), + ExpectedFee: new(big.Int).SetUint64(1), + ExpectedPreviousFee: new(big.Int).SetUint64(1), + ThresholdUp: new(big.Float).SetFloat64(3), + ThresholdDown: new(big.Float).SetFloat64(0.8), }, err: ErrFeeTooHigh, }, "fee-too-low-down": { opts: &PaysEnoughOpts{ - UserFee: new(big.Int).SetUint64(1), - ExpectedFee: new(big.Int).SetUint64(10_000), - ThresholdUp: new(big.Float).SetFloat64(3), - ThresholdDown: new(big.Float).SetFloat64(0.8), + UserFee: new(big.Int).SetUint64(1), + ExpectedFee: new(big.Int).SetUint64(10_000), + ExpectedPreviousFee: new(big.Int).SetUint64(10_000), + ThresholdUp: new(big.Float).SetFloat64(3), + ThresholdDown: new(big.Float).SetFloat64(0.8), }, err: ErrFeeTooLow, }, "fee-too-low-down-2": { opts: &PaysEnoughOpts{ - UserFee: new(big.Int).SetUint64(0), - ExpectedFee: new(big.Int).SetUint64(10_000), - ThresholdUp: new(big.Float).SetFloat64(3), - ThresholdDown: new(big.Float).SetFloat64(0.8), + UserFee: new(big.Int).SetUint64(0), + ExpectedFee: new(big.Int).SetUint64(10_000), + ExpectedPreviousFee: new(big.Int).SetUint64(10_000), + ThresholdUp: new(big.Float).SetFloat64(3), + ThresholdDown: new(big.Float).SetFloat64(0.8), }, err: ErrFeeTooLow, }, diff --git a/l2geth/rollup/sync_service.go b/l2geth/rollup/sync_service.go index 02087799eb17..5a6f17311ea0 100644 --- a/l2geth/rollup/sync_service.go +++ b/l2geth/rollup/sync_service.go @@ -460,8 +460,8 @@ func (s *SyncService) sequence() error { } func (s *SyncService) syncQueueToTip() error { - indexWrapper := func(inf *EnqueueInfo)(indexGetter) { - return func()(*uint64, error) { + indexWrapper := func(inf *EnqueueInfo) indexGetter { + return func() (*uint64, error) { return inf.QueueIndex, nil } } @@ -481,7 +481,7 @@ func (s *SyncService) syncQueueToTip() error { return fmt.Errorf("Cannot sync queue to tip: %w", err) } - if s.GetNextEnqueueIndex() == *inf.QueueIndex+1 && *inf.BaseBlock > s.GetLatestL1BlockNumber() { + if s.GetNextEnqueueIndex() == *inf.QueueIndex+1 && *inf.BaseBlock > s.GetLatestL1BlockNumber() { // moved from the updateContext() function current := time.Unix(int64(s.GetLatestL1Timestamp()), 0) next := time.Unix(int64(*inf.BaseTime), 0) @@ -523,6 +523,12 @@ func (s *SyncService) updateL1GasPrice() error { if err != nil { return fmt.Errorf("cannot fetch L1 gas price: %w", err) } + previousL1GasPrice, err := s.RollupGpo.SuggestL1GasPrice(context.Background()) + if err == nil { + s.RollupGpo.SetPreviousL1GasPrice(previousL1GasPrice) + } else { + s.RollupGpo.SetPreviousL1GasPrice(l1GasPrice) + } s.RollupGpo.SetL1GasPrice(l1GasPrice) return nil } @@ -539,6 +545,12 @@ func (s *SyncService) updateL2GasPrice(statedb *state.StateDB) error { } } result := statedb.GetState(l2GasPriceOracleAddress, l2GasPriceSlot) + previousL2GasPrice, err := s.RollupGpo.SuggestL2GasPrice(context.Background()) + if err == nil { + s.RollupGpo.SetPreviousL2GasPrice(previousL2GasPrice) + } else { + s.RollupGpo.SetPreviousL2GasPrice(result.Big()) + } s.RollupGpo.SetL2GasPrice(result.Big()) return nil } @@ -805,8 +817,8 @@ func (s *SyncService) applyTransactionToTip(tx *types.Transaction) error { bn := s.GetLatestL1BlockNumber() tx.SetL1Timestamp(ts) tx.SetL1BlockNumber(bn) - // In a testing environment with an "automine" L1, we may receive two blocks with the same L1Timestamp. - // Checking for L1BLockNumber() as well as Timestamp() will ensure that the context is updated in this situation. + // In a testing environment with an "automine" L1, we may receive two blocks with the same L1Timestamp. + // Checking for L1BLockNumber() as well as Timestamp() will ensure that the context is updated in this situation. } else if tx.L1Timestamp() > s.GetLatestL1Timestamp() || tx.L1BlockNumber().Uint64() > s.GetLatestL1BlockNumber() { if tx.L1Timestamp() == s.GetLatestL1Timestamp() { log.Warn("Duplicate L1Timestamp() detected in applyTransactionToTip", "ts", tx.L1Timestamp(), "l1block", tx.L1BlockNumber().Uint64()) @@ -903,6 +915,14 @@ func (s *SyncService) verifyFee(tx *types.Transaction) error { if err != nil { return err } + previousL1GasPrice, err := s.RollupGpo.SuggestPreviousL1GasPrice(context.Background()) + if err != nil { + return err + } + previousL2GasPrice, err := s.RollupGpo.SuggestPreviousL2GasPrice(context.Background()) + if err != nil { + return err + } // Calculate the fee based on decoded L2 gas limit gas := new(big.Int).SetUint64(tx.Gas()) l2GasLimit := fees.DecodeL2GasLimit(gas) @@ -916,19 +936,22 @@ func (s *SyncService) verifyFee(tx *types.Transaction) error { // Only count the calldata here as the overhead of the fully encoded // RLP transaction is handled inside of EncodeL2GasLimit expectedTxGasLimit := fees.EncodeTxGasLimit(tx.Data(), l1GasPrice, l2GasLimit, l2GasPrice) + expectedPreviousTxGasLimit := fees.EncodeTxGasLimit(tx.Data(), previousL1GasPrice, l2GasLimit, previousL2GasPrice) // This should only happen if the unscaled transaction fee is greater than 18.44 ETH - if !expectedTxGasLimit.IsUint64() { + if !expectedTxGasLimit.IsUint64() || !expectedPreviousTxGasLimit.IsUint64() { return fmt.Errorf("fee overflow: %s", expectedTxGasLimit.String()) } userFee := new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), tx.GasPrice()) expectedFee := new(big.Int).Mul(expectedTxGasLimit, fees.BigTxGasPrice) + expectedPreviousFee := new(big.Int).Mul(expectedPreviousTxGasLimit, fees.BigTxGasPrice) opts := fees.PaysEnoughOpts{ - UserFee: userFee, - ExpectedFee: expectedFee, - ThresholdUp: s.feeThresholdUp, - ThresholdDown: s.feeThresholdDown, + UserFee: userFee, + ExpectedFee: expectedFee, + ExpectedPreviousFee: expectedPreviousFee, + ThresholdUp: s.feeThresholdUp, + ThresholdDown: s.feeThresholdDown, } // Check the error type and return the correct error message to the user if err := fees.PaysEnough(&opts); err != nil {