Commit 390fd8a6 authored by Mark Tyneway's avatar Mark Tyneway

dtl: configurable gas price backend

Adds a new config option `--l1-gas-price-backend` or
`DATA_TRANSPORT_LAYER_L1_GAS_PRICE_BACKEND` that can be set to `l1` or
`l2`. This impacts the behavior of the HTTP endpoint `GET /eth/gasprice`
by changing what is queried to return the L1 gas price. The L1 gas price
is required to compute the L2 fee since the L2 fee consists of
`L1 gas price * L1 gas used + L1 gas price * L2 gas limit`. If the L1
gas price differs too much between different L2 providers, then users
using `eth_estimateGas` may submit transactions with too low of a fee
and be unable to submit transactions to the sequencer.

By configuring the DTL to use L2 as the L1 gas price backend, it will
call the Sequencer's RPC endpoint `rollup_gasPrices` which returns the
L1 and L2 gas prices from the point of view of the sequencer. The L2 gas
price exists in the state, so that will always be the same between the
sequencer and any replicas. The L1 gas price does not live on chain, so
querying for it from the sequencer directly will ensure that users send
transactions with a fee that is large enough.

Also adds eth/gasprice info to README.
parent 886a1507
---
'@eth-optimism/data-transport-layer': patch
---
Allow the L1 gas price to be fetched from either the sequencer or a L1 provider based on the config `--l1-gas-price-backend` as well as overriding the config by using a query param. Valid values are `l1` or `l2` and it defaults to `l1`
...@@ -62,6 +62,8 @@ Here's the list of environment variables you can change: ...@@ -62,6 +62,8 @@ Here's the list of environment variables you can change:
| DATA_TRANSPORT_LAYER__LEGACY_SEQUENCER_COMPATIBILITY | false | Whether or not to enable "legacy" sequencer sync (without the custom `eth_getBlockRange` endpoint) | | DATA_TRANSPORT_LAYER__LEGACY_SEQUENCER_COMPATIBILITY | false | Whether or not to enable "legacy" sequencer sync (without the custom `eth_getBlockRange` endpoint) |
| DATA_TRANSPORT_LAYER__NODE_ENV | development | Environment the service is running in: production, development, or test. | | DATA_TRANSPORT_LAYER__NODE_ENV | development | Environment the service is running in: production, development, or test. |
| DATA_TRANSPORT_LAYER__ETH_NETWORK_NAME | - | L1 Ethereum network the service is deployed to: mainnet, kovan, goerli. | | DATA_TRANSPORT_LAYER__ETH_NETWORK_NAME | - | L1 Ethereum network the service is deployed to: mainnet, kovan, goerli. |
| DATA_TRANSPORT_LAYER__L1_GAS_PRICE_BACKEND | l1 | Where to pull the l1 gas price from (l1 or l2) |
| DATA_TRANSPORT_LAYER__DEFAULT_BACKEND | l1 | Where to sync transactions from (l1 or l2) |
To enable proper error tracking via Sentry on deployed instances, make sure `NODE_ENV` and `ETH_NETWORK_NAME` are set in addition to [`SENTRY_DSN`](https://docs.sentry.io/platforms/node/). To enable proper error tracking via Sentry on deployed instances, make sure `NODE_ENV` and `ETH_NETWORK_NAME` are set in addition to [`SENTRY_DSN`](https://docs.sentry.io/platforms/node/).
...@@ -86,6 +88,24 @@ GET /eth/context/latest ...@@ -86,6 +88,24 @@ GET /eth/context/latest
} }
``` ```
### Latest Ethereum L1 Gas Price
#### Request
```
GET /eth/gasprice
```
Defaults to pulling L1 gas price from config option `DATA_TRANSPORT_LAYER__L1_GAS_PRICE_BACKEND`. Can be overridden with query parameter `backend` (`/eth/gasprice?backend=l1`).
#### Response
```ts
{
"gasPrice": string
}
```
### Enqueue by Index ### Enqueue by Index
#### Request #### Request
...@@ -145,7 +165,7 @@ GET /transaction/index/{index: number} ...@@ -145,7 +165,7 @@ GET /transaction/index/{index: number}
} | null, } | null,
"queueIndex": number | null, "queueIndex": number | null,
}, },
"batch": { "batch": {
"index": number, "index": number,
"blockNumber": number, "blockNumber": number,
...@@ -181,7 +201,7 @@ GET /batch/transaction/index/{index: number} ...@@ -181,7 +201,7 @@ GET /batch/transaction/index/{index: number}
"prevTotalElements": number, "prevTotalElements": number,
"extraData": string "extraData": string
}, },
"transactions": [ "transactions": [
{ {
"index": number, "index": number,
...@@ -266,7 +286,7 @@ GET /batch/stateroot/index/{index: number} ...@@ -266,7 +286,7 @@ GET /batch/stateroot/index/{index: number}
"prevTotalElements": number, "prevTotalElements": number,
"extraData": string "extraData": string
}, },
"stateRoots": [ "stateRoots": [
{ {
"index": number, "index": number,
......
...@@ -34,6 +34,7 @@ export interface L1DataTransportServiceOptions { ...@@ -34,6 +34,7 @@ export interface L1DataTransportServiceOptions {
sentryDsn?: string sentryDsn?: string
sentryTraceRate?: number sentryTraceRate?: number
defaultBackend: string defaultBackend: string
l1GasPriceBackend: string
} }
const optionSettings = { const optionSettings = {
......
...@@ -46,6 +46,7 @@ type ethNetwork = 'mainnet' | 'kovan' | 'goerli' ...@@ -46,6 +46,7 @@ type ethNetwork = 'mainnet' | 'kovan' | 'goerli'
false false
), ),
defaultBackend: config.str('default-backend', 'l1'), defaultBackend: config.str('default-backend', 'l1'),
l1GasPriceBackend: config.str('l1-gas-price-backend', 'l1'),
useSentry: config.bool('use-sentry', false), useSentry: config.bool('use-sentry', false),
sentryDsn: config.str('sentry-dsn'), sentryDsn: config.str('sentry-dsn'),
sentryTraceRate: config.ufloat('sentry-trace-rate', 0.05), sentryTraceRate: config.ufloat('sentry-trace-rate', 0.05),
......
...@@ -51,12 +51,23 @@ const optionSettings = { ...@@ -51,12 +51,23 @@ const optionSettings = {
return validators.isUrl(val) || validators.isJsonRpcProvider(val) return validators.isUrl(val) || validators.isJsonRpcProvider(val)
}, },
}, },
l2RpcProvider: {
validate: (val: unknown) => {
return validators.isUrl(val) || validators.isJsonRpcProvider(val)
},
},
defaultBackend: { defaultBackend: {
default: 'l1', default: 'l1',
validate: (val: string) => { validate: (val: string) => {
return val === 'l1' || val === 'l2' return val === 'l1' || val === 'l2'
}, },
}, },
l1GasPriceBackend: {
default: 'l1',
validate: (val: string) => {
return val === 'l1' || val === 'l2'
},
},
} }
export class L1TransportServer extends BaseService<L1TransportServerOptions> { export class L1TransportServer extends BaseService<L1TransportServerOptions> {
...@@ -69,6 +80,7 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> { ...@@ -69,6 +80,7 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> {
server: any server: any
db: TransportDB db: TransportDB
l1RpcProvider: JsonRpcProvider l1RpcProvider: JsonRpcProvider
l2RpcProvider: JsonRpcProvider
} = {} as any } = {} as any
protected async _init(): Promise<void> { protected async _init(): Promise<void> {
...@@ -82,6 +94,11 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> { ...@@ -82,6 +94,11 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> {
? new JsonRpcProvider(this.options.l1RpcProvider) ? new JsonRpcProvider(this.options.l1RpcProvider)
: this.options.l1RpcProvider : this.options.l1RpcProvider
this.state.l2RpcProvider =
typeof this.options.l2RpcProvider === 'string'
? new JsonRpcProvider(this.options.l2RpcProvider)
: this.options.l2RpcProvider
this._initializeApp() this._initializeApp()
} }
...@@ -133,6 +150,26 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> { ...@@ -133,6 +150,26 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> {
if (this.options.useSentry) { if (this.options.useSentry) {
this.state.app.use(Sentry.Handlers.errorHandler()) this.state.app.use(Sentry.Handlers.errorHandler())
} }
this.logger.info('HTTP Server Options', {
defaultBackend: this.options.defaultBackend,
l1GasPriceBackend: this.options.l1GasPriceBackend,
})
if (this.state.l1RpcProvider) {
this.logger.info('HTTP Server L1 RPC Provider initialized', {
url: this.state.l1RpcProvider.connection.url,
})
} else {
this.logger.warn('HTTP Server L1 RPC Provider not initialized')
}
if (this.state.l2RpcProvider) {
this.logger.info('HTTP Server L2 RPC Provider initialized', {
url: this.state.l2RpcProvider.connection.url,
})
} else {
this.logger.warn('HTTP Server L2 RPC Provider not initialized')
}
} }
/** /**
...@@ -271,8 +308,21 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> { ...@@ -271,8 +308,21 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> {
this._registerRoute( this._registerRoute(
'get', 'get',
'/eth/gasprice', '/eth/gasprice',
async (): Promise<GasPriceResponse> => { async (req): Promise<GasPriceResponse> => {
const gasPrice = await this.state.l1RpcProvider.getGasPrice() const backend = req.query.backend || this.options.l1GasPriceBackend
let gasPrice: BigNumber
if (backend === 'l1') {
gasPrice = await this.state.l1RpcProvider.getGasPrice()
} else if (backend === 'l2') {
const response = await this.state.l2RpcProvider.send(
'rollup_gasPrices',
[]
)
gasPrice = BigNumber.from(response.l1GasPrice)
} else {
throw new Error(`Unknown L1 gas price backend: ${backend}`)
}
return { return {
gasPrice: gasPrice.toString(), gasPrice: gasPrice.toString(),
......
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