Commit 79f66e96 authored by Georgios Konstantopoulos's avatar Georgios Konstantopoulos Committed by GitHub

feat: set L2 execution price manually (#557)

* refactor(l2geth): rename L1GasPrice to DataPrice

* feat(l2geth/rollup-gpo): allow execution price to be specified

* feat(l2geth/api): use the execution price instead of the historical gasprice

* feat(l2geth): allow configuring the l2 execution price

* fix(integration-tests): adjust gas costs to match 0-execution price

* fix(integration-tests): pin down l1 gas estimation costs to the expected values

* chore: add changeset

* fix(sync-service): adjust tests for the refactor

* feat(rollup-gasprice): make Rollup GPO methods thread safe
parent e713cd05
---
"@eth-optimism/l2geth": patch
---
Use constant execution price, which is set by the sequencer
...@@ -45,13 +45,13 @@ describe('Native ETH Integration Tests', async () => { ...@@ -45,13 +45,13 @@ describe('Native ETH Integration Tests', async () => {
const amount = utils.parseEther('0.5') const amount = utils.parseEther('0.5')
const addr = '0x' + '1234'.repeat(10) const addr = '0x' + '1234'.repeat(10)
const gas = await env.ovmEth.estimateGas.transfer(addr, amount) const gas = await env.ovmEth.estimateGas.transfer(addr, amount)
expect(gas).to.be.deep.eq(BigNumber.from(213546)) expect(gas).to.be.deep.eq(BigNumber.from(17344))
}) })
it('Should estimate gas for ETH withdraw', async () => { it('Should estimate gas for ETH withdraw', async () => {
const amount = utils.parseEther('0.5') const amount = utils.parseEther('0.5')
const gas = await env.ovmEth.estimateGas.withdraw(amount) const gas = await env.ovmEth.estimateGas.withdraw(amount)
expect(gas).to.be.deep.eq(BigNumber.from(503789)) expect(gas).to.be.deep.eq(BigNumber.from(14400))
}) })
}) })
......
...@@ -150,18 +150,27 @@ describe('Basic RPC tests', () => { ...@@ -150,18 +150,27 @@ describe('Basic RPC tests', () => {
describe('eth_estimateGas (returns the fee)', () => { describe('eth_estimateGas (returns the fee)', () => {
it('should return a gas estimate that grows with the size of data', async () => { it('should return a gas estimate that grows with the size of data', async () => {
const dataLen = [0, 2, 8, 64, 256] const dataLen = [0, 2, 8, 64, 256]
const l1GasPrice = await env.l1Wallet.provider.getGasPrice()
// 96 bytes * 16 per non zero byte
const onesCost = BigNumber.from(96).mul(16)
const expectedCost = dataLen
.map(len => BigNumber.from(len).mul(4))
.map(zerosCost => zerosCost.add(onesCost))
let last = BigNumber.from(0)
// Repeat this test for a series of possible transaction sizes. // Repeat this test for a series of possible transaction sizes.
for (const len of dataLen) { for (let i = 0; i < dataLen.length; i++) {
const estimate = await l2Provider.estimateGas({ const estimate = await l2Provider.estimateGas({
...DEFAULT_TRANSACTION, ...DEFAULT_TRANSACTION,
data: '0x' + '00'.repeat(len), data: '0x' + '00'.repeat(dataLen[i]),
from: '0x' + '1234'.repeat(10), from: '0x' + '1234'.repeat(10),
}) })
expect(estimate.gt(last)).to.be.true // we normalize by gwei here because the RPC does it as well, since the
last = estimate // user provides a 1gwei gas price when submitting txs via the eth_gasPrice
// rpc call
const expected = expectedCost[i].mul(l1GasPrice).div(GWEI)
expect(estimate).to.be.deep.eq(expected)
} }
}) })
}) })
......
...@@ -164,7 +164,8 @@ var ( ...@@ -164,7 +164,8 @@ var (
utils.RollupStateDumpPathFlag, utils.RollupStateDumpPathFlag,
utils.RollupDiffDbFlag, utils.RollupDiffDbFlag,
utils.RollupMaxCalldataSizeFlag, utils.RollupMaxCalldataSizeFlag,
utils.RollupL1GasPriceFlag, utils.RollupDataPriceFlag,
utils.RollupExecutionPriceFlag,
} }
rpcFlags = []cli.Flag{ rpcFlags = []cli.Flag{
......
...@@ -78,7 +78,8 @@ var AppHelpFlagGroups = []flagGroup{ ...@@ -78,7 +78,8 @@ var AppHelpFlagGroups = []flagGroup{
utils.RollupStateDumpPathFlag, utils.RollupStateDumpPathFlag,
utils.RollupDiffDbFlag, utils.RollupDiffDbFlag,
utils.RollupMaxCalldataSizeFlag, utils.RollupMaxCalldataSizeFlag,
utils.RollupL1GasPriceFlag, utils.RollupDataPriceFlag,
utils.RollupExecutionPriceFlag,
}, },
}, },
{ {
......
...@@ -879,11 +879,17 @@ var ( ...@@ -879,11 +879,17 @@ var (
Value: eth.DefaultConfig.Rollup.MaxCallDataSize, Value: eth.DefaultConfig.Rollup.MaxCallDataSize,
EnvVar: "ROLLUP_MAX_CALLDATA_SIZE", EnvVar: "ROLLUP_MAX_CALLDATA_SIZE",
} }
RollupL1GasPriceFlag = BigFlag{ RollupDataPriceFlag = BigFlag{
Name: "rollup.l1gasprice", Name: "rollup.dataprice",
Usage: "The L1 gas price to use for the sequencer fees", Usage: "The L1 calldata price to use for the sequencer fees",
Value: eth.DefaultConfig.Rollup.L1GasPrice, Value: eth.DefaultConfig.Rollup.DataPrice,
EnvVar: "ROLLUP_L1_GASPRICE", EnvVar: "ROLLUP_DATAPRICE",
}
RollupExecutionPriceFlag = BigFlag{
Name: "rollup.executionprice",
Usage: "The execution gas price to use for the sequencer fees",
Value: eth.DefaultConfig.Rollup.ExecutionPrice,
EnvVar: "ROLLUP_EXECUTIONPRICE",
} }
) )
...@@ -1158,8 +1164,11 @@ func setRollup(ctx *cli.Context, cfg *rollup.Config) { ...@@ -1158,8 +1164,11 @@ func setRollup(ctx *cli.Context, cfg *rollup.Config) {
if ctx.GlobalIsSet(RollupTimstampRefreshFlag.Name) { if ctx.GlobalIsSet(RollupTimstampRefreshFlag.Name) {
cfg.TimestampRefreshThreshold = ctx.GlobalDuration(RollupTimstampRefreshFlag.Name) cfg.TimestampRefreshThreshold = ctx.GlobalDuration(RollupTimstampRefreshFlag.Name)
} }
if ctx.GlobalIsSet(RollupL1GasPriceFlag.Name) { if ctx.GlobalIsSet(RollupDataPriceFlag.Name) {
cfg.L1GasPrice = GlobalBig(ctx, RollupL1GasPriceFlag.Name) cfg.DataPrice = GlobalBig(ctx, RollupDataPriceFlag.Name)
}
if ctx.GlobalIsSet(RollupExecutionPriceFlag.Name) {
cfg.ExecutionPrice = GlobalBig(ctx, RollupExecutionPriceFlag.Name)
} }
} }
......
...@@ -46,7 +46,7 @@ type EthAPIBackend struct { ...@@ -46,7 +46,7 @@ type EthAPIBackend struct {
extRPCEnabled bool extRPCEnabled bool
eth *Ethereum eth *Ethereum
gpo *gasprice.Oracle gpo *gasprice.Oracle
l1gpo *gasprice.L1Oracle rollupGpo *gasprice.RollupOracle
verifier bool verifier bool
gasLimit uint64 gasLimit uint64
UsingOVM bool UsingOVM bool
...@@ -381,11 +381,19 @@ func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { ...@@ -381,11 +381,19 @@ func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
} }
func (b *EthAPIBackend) SuggestDataPrice(ctx context.Context) (*big.Int, error) { func (b *EthAPIBackend) SuggestDataPrice(ctx context.Context) (*big.Int, error) {
return b.l1gpo.SuggestDataPrice(ctx) return b.rollupGpo.SuggestDataPrice(ctx)
} }
func (b *EthAPIBackend) SetL1GasPrice(ctx context.Context, gasPrice *big.Int) { func (b *EthAPIBackend) SuggestExecutionPrice(ctx context.Context) (*big.Int, error) {
b.l1gpo.SetL1GasPrice(gasPrice) return b.rollupGpo.SuggestExecutionPrice(ctx)
}
func (b *EthAPIBackend) SetDataPrice(ctx context.Context, gasPrice *big.Int) {
b.rollupGpo.SetDataPrice(gasPrice)
}
func (b *EthAPIBackend) SetExecutionPrice(ctx context.Context, gasPrice *big.Int) {
b.rollupGpo.SetExecutionPrice(gasPrice)
} }
func (b *EthAPIBackend) ChainDb() ethdb.Database { func (b *EthAPIBackend) ChainDb() ethdb.Database {
......
...@@ -15,7 +15,6 @@ func TestGasLimit(t *testing.T) { ...@@ -15,7 +15,6 @@ func TestGasLimit(t *testing.T) {
extRPCEnabled: false, extRPCEnabled: false,
eth: nil, eth: nil,
gpo: nil, gpo: nil,
l1gpo: nil,
verifier: false, verifier: false,
gasLimit: 0, gasLimit: 0,
UsingOVM: true, UsingOVM: true,
......
...@@ -226,17 +226,17 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ...@@ -226,17 +226,17 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
log.Info("Backend Config", "max-calldata-size", config.Rollup.MaxCallDataSize, "gas-limit", config.Rollup.GasLimit, "is-verifier", config.Rollup.IsVerifier, "using-ovm", vm.UsingOVM, "l1-gasprice", config.Rollup.L1GasPrice) log.Info("Backend Config", "max-calldata-size", config.Rollup.MaxCallDataSize, "gas-limit", config.Rollup.GasLimit, "is-verifier", config.Rollup.IsVerifier, "using-ovm", vm.UsingOVM, "data-price", config.Rollup.DataPrice, "execution-price", config.Rollup.ExecutionPrice)
eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil, nil, config.Rollup.IsVerifier, config.Rollup.GasLimit, vm.UsingOVM, config.Rollup.MaxCallDataSize} eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil, nil, config.Rollup.IsVerifier, config.Rollup.GasLimit, vm.UsingOVM, config.Rollup.MaxCallDataSize}
gpoParams := config.GPO gpoParams := config.GPO
if gpoParams.Default == nil { if gpoParams.Default == nil {
gpoParams.Default = config.Miner.GasPrice gpoParams.Default = config.Miner.GasPrice
} }
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
// create the L1 GPO and allow the API backend and the sync service to access it // create the Rollup GPO and allow the API backend and the sync service to access it
l1Gpo := gasprice.NewL1Oracle(config.Rollup.L1GasPrice) rollupGpo := gasprice.NewRollupOracle(config.Rollup.DataPrice, config.Rollup.ExecutionPrice)
eth.APIBackend.l1gpo = l1Gpo eth.APIBackend.rollupGpo = rollupGpo
eth.syncService.L1gpo = l1Gpo eth.syncService.RollupGpo = rollupGpo
return eth, nil return eth, nil
} }
......
...@@ -81,7 +81,8 @@ var DefaultConfig = Config{ ...@@ -81,7 +81,8 @@ var DefaultConfig = Config{
// is additional overhead that is unaccounted. Round down to 127000 for // is additional overhead that is unaccounted. Round down to 127000 for
// safety. // safety.
MaxCallDataSize: 127000, MaxCallDataSize: 127000,
L1GasPrice: big.NewInt(100 * params.GWei), DataPrice: big.NewInt(100 * params.GWei),
ExecutionPrice: big.NewInt(0),
}, },
DiffDbCache: 256, DiffDbCache: 256,
} }
......
package gasprice
import (
"context"
"math/big"
)
type L1Oracle struct {
gasPrice *big.Int
}
func NewL1Oracle(gasPrice *big.Int) *L1Oracle {
return &L1Oracle{gasPrice}
}
/// SuggestDataPrice returns the gas price which should be charged per byte of published
/// data by the sequencer.
func (gpo *L1Oracle) SuggestDataPrice(ctx context.Context) (*big.Int, error) {
return gpo.gasPrice, nil
}
func (gpo *L1Oracle) SetL1GasPrice(gasPrice *big.Int) {
gpo.gasPrice = gasPrice
}
package gasprice
import (
"context"
"math/big"
"sync"
)
type RollupOracle struct {
dataPrice *big.Int
executionPrice *big.Int
dataPriceLock sync.RWMutex
executionPriceLock sync.RWMutex
}
func NewRollupOracle(dataPrice *big.Int, executionPrice *big.Int) *RollupOracle {
return &RollupOracle{
dataPrice: dataPrice,
executionPrice: executionPrice,
}
}
/// SuggestDataPrice returns the gas price which should be charged per byte of published
/// data by the sequencer.
func (gpo *RollupOracle) SuggestDataPrice(ctx context.Context) (*big.Int, error) {
gpo.dataPriceLock.RLock()
price := gpo.dataPrice
gpo.dataPriceLock.RUnlock()
return price, nil
}
func (gpo *RollupOracle) SetDataPrice(dataPrice *big.Int) {
gpo.dataPriceLock.Lock()
gpo.dataPrice = dataPrice
gpo.dataPriceLock.Unlock()
}
/// SuggestExecutionPrice returns the gas price which should be charged per unit of gas
/// set manually by the sequencer depending on congestion
func (gpo *RollupOracle) SuggestExecutionPrice(ctx context.Context) (*big.Int, error) {
gpo.executionPriceLock.RLock()
price := gpo.executionPrice
gpo.executionPriceLock.RUnlock()
return price, nil
}
func (gpo *RollupOracle) SetExecutionPrice(executionPrice *big.Int) {
gpo.executionPriceLock.Lock()
gpo.executionPrice = executionPrice
gpo.executionPriceLock.Unlock()
}
...@@ -1034,7 +1034,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash ...@@ -1034,7 +1034,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
} }
// 2b. fetch the execution gas price, by the typical mempool dynamics // 2b. fetch the execution gas price, by the typical mempool dynamics
executionPrice, err := b.SuggestPrice(ctx) executionPrice, err := b.SuggestExecutionPrice(ctx)
if err != nil { if err != nil {
return 0, err return 0, err
} }
...@@ -1982,10 +1982,15 @@ func NewPrivateRollupAPI(b Backend) *PrivateRollupAPI { ...@@ -1982,10 +1982,15 @@ func NewPrivateRollupAPI(b Backend) *PrivateRollupAPI {
return &PrivateRollupAPI{b: b} return &PrivateRollupAPI{b: b}
} }
// SetGasPrice sets the gas price to be used when quoting calldata publishing costs // SetDataPrice sets the gas price to be used when quoting calldata publishing costs
// to users // to users
func (api *PrivateRollupAPI) SetL1GasPrice(ctx context.Context, gasPrice hexutil.Big) { func (api *PrivateRollupAPI) SetDataPrice(ctx context.Context, gasPrice hexutil.Big) {
api.b.SetL1GasPrice(ctx, (*big.Int)(&gasPrice)) api.b.SetDataPrice(ctx, (*big.Int)(&gasPrice))
}
// SetExecutionPrice sets the gas price to be used when executing transactions on
func (api *PrivateRollupAPI) SetExecutionPrice(ctx context.Context, gasPrice hexutil.Big) {
api.b.SetExecutionPrice(ctx, (*big.Int)(&gasPrice))
} }
// PublicDebugAPI is the collection of Ethereum APIs exposed over the public // PublicDebugAPI is the collection of Ethereum APIs exposed over the public
......
...@@ -95,7 +95,9 @@ type Backend interface { ...@@ -95,7 +95,9 @@ type Backend interface {
GasLimit() uint64 GasLimit() uint64
GetDiff(*big.Int) (diffdb.Diff, error) GetDiff(*big.Int) (diffdb.Diff, error)
SuggestDataPrice(ctx context.Context) (*big.Int, error) SuggestDataPrice(ctx context.Context) (*big.Int, error)
SetL1GasPrice(context.Context, *big.Int) SetDataPrice(context.Context, *big.Int)
SuggestExecutionPrice(context.Context) (*big.Int, error)
SetExecutionPrice(context.Context, *big.Int)
} }
func GetAPIs(apiBackend Backend) []rpc.API { func GetAPIs(apiBackend Backend) []rpc.API {
......
...@@ -286,9 +286,19 @@ func (b *LesApiBackend) SuggestDataPrice(ctx context.Context) (*big.Int, error) ...@@ -286,9 +286,19 @@ func (b *LesApiBackend) SuggestDataPrice(ctx context.Context) (*big.Int, error)
panic("SuggestDataPrice not implemented") panic("SuggestDataPrice not implemented")
} }
// NB: Non sequencer nodes cannot suggest L2 execution gas prices.
func (b *LesApiBackend) SuggestExecutionPrice(ctx context.Context) (*big.Int, error) {
panic("SuggestExecutionPrice not implemented")
}
// NB: Non sequencer nodes cannot set L1 gas prices. // NB: Non sequencer nodes cannot set L1 gas prices.
func (b *LesApiBackend) SetL1GasPrice(ctx context.Context, gasPrice *big.Int) { func (b *LesApiBackend) SetDataPrice(ctx context.Context, gasPrice *big.Int) {
panic("SetL1GasPrice is not implemented") panic("SetDataPrice is not implemented")
}
// NB: Non sequencer nodes cannot set L2 execution prices.
func (b *LesApiBackend) SetExecutionPrice(ctx context.Context, gasPrice *big.Int) {
panic("SetExecutionPrice is not implemented")
} }
func (b *LesApiBackend) ChainDb() ethdb.Database { func (b *LesApiBackend) ChainDb() ethdb.Database {
......
...@@ -34,5 +34,7 @@ type Config struct { ...@@ -34,5 +34,7 @@ type Config struct {
// Interval for updating the timestamp // Interval for updating the timestamp
TimestampRefreshThreshold time.Duration TimestampRefreshThreshold time.Duration
// The gas price to use when estimating L1 calldata publishing costs // The gas price to use when estimating L1 calldata publishing costs
L1GasPrice *big.Int DataPrice *big.Int
// The gas price to use for L2 congestion costs
ExecutionPrice *big.Int
} }
...@@ -44,7 +44,7 @@ type SyncService struct { ...@@ -44,7 +44,7 @@ type SyncService struct {
eth1ChainId uint64 eth1ChainId uint64
bc *core.BlockChain bc *core.BlockChain
txpool *core.TxPool txpool *core.TxPool
L1gpo *gasprice.L1Oracle RollupGpo *gasprice.RollupOracle
client RollupClient client RollupClient
syncing atomic.Value syncing atomic.Value
OVMContext OVMContext OVMContext OVMContext
...@@ -449,7 +449,7 @@ func (s *SyncService) updateL1GasPrice() error { ...@@ -449,7 +449,7 @@ func (s *SyncService) updateL1GasPrice() error {
if err != nil { if err != nil {
return err return err
} }
s.L1gpo.SetL1GasPrice(l1GasPrice) s.RollupGpo.SetDataPrice(l1GasPrice)
log.Info("Adjusted L1 Gas Price", "gasprice", l1GasPrice) log.Info("Adjusted L1 Gas Price", "gasprice", l1GasPrice)
return nil return nil
} }
......
...@@ -154,13 +154,13 @@ func TestSyncServiceTransactionEnqueued(t *testing.T) { ...@@ -154,13 +154,13 @@ func TestSyncServiceTransactionEnqueued(t *testing.T) {
func TestSyncServiceL1GasPrice(t *testing.T) { func TestSyncServiceL1GasPrice(t *testing.T) {
service, _, _, err := newTestSyncService(true) service, _, _, err := newTestSyncService(true)
setupMockClient(service, map[string]interface{}{}) setupMockClient(service, map[string]interface{}{})
service.L1gpo = gasprice.NewL1Oracle(big.NewInt(0)) service.RollupGpo = gasprice.NewRollupOracle(big.NewInt(0), big.NewInt(0))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
gasBefore, err := service.L1gpo.SuggestDataPrice(context.Background()) gasBefore, err := service.RollupGpo.SuggestDataPrice(context.Background())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -172,7 +172,7 @@ func TestSyncServiceL1GasPrice(t *testing.T) { ...@@ -172,7 +172,7 @@ func TestSyncServiceL1GasPrice(t *testing.T) {
// Update the gas price // Update the gas price
service.updateL1GasPrice() service.updateL1GasPrice()
gasAfter, err := service.L1gpo.SuggestDataPrice(context.Background()) gasAfter, err := service.RollupGpo.SuggestDataPrice(context.Background())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -346,7 +346,7 @@ type mockClient struct { ...@@ -346,7 +346,7 @@ type mockClient struct {
func setupMockClient(service *SyncService, responses map[string]interface{}) { func setupMockClient(service *SyncService, responses map[string]interface{}) {
client := newMockClient(responses) client := newMockClient(responses)
service.client = client service.client = client
service.L1gpo = gasprice.NewL1Oracle(big.NewInt(0)) service.RollupGpo = gasprice.NewRollupOracle(big.NewInt(0), big.NewInt(0))
} }
func newMockClient(responses map[string]interface{}) *mockClient { func newMockClient(responses map[string]interface{}) *mockClient {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment