Commit 1d91ac6d authored by Mark Tyneway's avatar Mark Tyneway

Merge branch 'develop' into regenesis/0.4.0

parents 7f5936a8 8e2bfd07
---
'@eth-optimism/batch-submitter': patch
'@eth-optimism/data-transport-layer': patch
---
Remove dead imports from core-utils
---
'@eth-optimism/message-relayer': patch
---
Adds a README and cleans up the interface for generating messages and proofs
---
'@eth-optimism/core-utils': patch
---
Delete dead transaction coders. These are no longer used now that RLP encoded transactions are used
---
'@eth-optimism/data-transport-layer': patch
---
Logs the error stacktrace for a failed HTTP request
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/l2geth': patch
'@eth-optimism/core-utils': patch
---
Implement the latest fee spec such that the L2 gas limit is scaled and the tx.gasPrice/tx.gasLimit show correctly in metamask
---
'@eth-optimism/message-relayer': patch
---
Removes spreadsheet mode from the message relayer
...@@ -92,6 +92,14 @@ jobs: ...@@ -92,6 +92,14 @@ jobs:
push: true push: true
tags: ethereumoptimism/l2geth:${{ needs.release.outputs.l2geth }} tags: ethereumoptimism/l2geth:${{ needs.release.outputs.l2geth }}
- name: Publish rpc-proxy
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.rpc-proxy
push: true
tags: ethereumoptimism/rpc-proxy:${{ needs.release.outputs.l2geth }}
# pushes the base builder image to dockerhub # pushes the base builder image to dockerhub
builder: builder:
name: Prepare the base builder image for the services name: Prepare the base builder image for the services
......
...@@ -17,6 +17,7 @@ module.exports = { ...@@ -17,6 +17,7 @@ module.exports = {
host: '127.0.0.1', host: '127.0.0.1',
port: 8545, port: 8545,
gasPrice: 0, gasPrice: 0,
gas: 54180127,
} }
}, },
compilers: { compilers: {
...@@ -31,4 +32,4 @@ module.exports = { ...@@ -31,4 +32,4 @@ module.exports = {
} }
} }
} }
} }
\ No newline at end of file
...@@ -3,7 +3,7 @@ import chaiAsPromised from 'chai-as-promised' ...@@ -3,7 +3,7 @@ import chaiAsPromised from 'chai-as-promised'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
import { BigNumber, utils } from 'ethers' import { BigNumber, utils } from 'ethers'
import { OptimismEnv } from './shared/env' import { OptimismEnv } from './shared/env'
import { TxGasLimit } from '@eth-optimism/core-utils' import { TxGasLimit, TxGasPrice } from '@eth-optimism/core-utils'
describe('Fee Payment Integration Tests', async () => { describe('Fee Payment Integration Tests', async () => {
let env: OptimismEnv let env: OptimismEnv
...@@ -13,9 +13,9 @@ describe('Fee Payment Integration Tests', async () => { ...@@ -13,9 +13,9 @@ describe('Fee Payment Integration Tests', async () => {
env = await OptimismEnv.new() env = await OptimismEnv.new()
}) })
it('Should return a gasPrice of 1 wei', async () => { it(`Should return a gasPrice of ${TxGasPrice.toString()} wei`, async () => {
const gasPrice = await env.l2Wallet.getGasPrice() const gasPrice = await env.l2Wallet.getGasPrice()
expect(gasPrice.eq(1)) expect(gasPrice).to.deep.eq(TxGasPrice)
}) })
it('Should estimateGas with recoverable L2 gasLimit', async () => { it('Should estimateGas with recoverable L2 gasLimit', async () => {
...@@ -28,7 +28,7 @@ describe('Fee Payment Integration Tests', async () => { ...@@ -28,7 +28,7 @@ describe('Fee Payment Integration Tests', async () => {
utils.parseEther('0.5') utils.parseEther('0.5')
) )
const executionGas = await (env.ovmEth const executionGas = await (env.ovmEth
.provider as any).send('eth_estimateExecutionGas', [tx]) .provider as any).send('eth_estimateExecutionGas', [tx, true])
const decoded = TxGasLimit.decode(gas) const decoded = TxGasLimit.decode(gas)
expect(BigNumber.from(executionGas)).deep.eq(decoded) expect(BigNumber.from(executionGas)).deep.eq(decoded)
}) })
......
...@@ -50,13 +50,13 @@ describe('Native ETH Integration Tests', async () => { ...@@ -50,13 +50,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(0x0ef897216d)) expect(gas).to.be.deep.eq(BigNumber.from(6430020))
}) })
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, 0, '0xFFFF') const gas = await env.ovmEth.estimateGas.withdraw(amount, 0, '0xFFFF')
expect(gas).to.be.deep.eq(BigNumber.from(0x0f5203e9bf)) expect(gas).to.be.deep.eq(BigNumber.from(6140049))
}) })
}) })
......
...@@ -134,7 +134,7 @@ describe('Basic RPC tests', () => { ...@@ -134,7 +134,7 @@ describe('Basic RPC tests', () => {
gasPrice: TxGasPrice, gasPrice: TxGasPrice,
} }
const fee = tx.gasPrice.mul(tx.gasLimit) const fee = tx.gasPrice.mul(tx.gasLimit)
const gasLimit = 59300000001 const gasLimit = 5920001
await expect(env.l2Wallet.sendTransaction(tx)).to.be.rejectedWith( await expect(env.l2Wallet.sendTransaction(tx)).to.be.rejectedWith(
`fee too low: ${fee}, use at least tx.gasLimit = ${gasLimit} and tx.gasPrice = ${TxGasPrice.toString()}` `fee too low: ${fee}, use at least tx.gasLimit = ${gasLimit} and tx.gasPrice = ${TxGasPrice.toString()}`
...@@ -213,7 +213,7 @@ describe('Basic RPC tests', () => { ...@@ -213,7 +213,7 @@ describe('Basic RPC tests', () => {
it('correctly exposes revert data for contract calls', async () => { it('correctly exposes revert data for contract calls', async () => {
const req: TransactionRequest = { const req: TransactionRequest = {
...revertingTx, ...revertingTx,
gasLimit: 59808999999, // override gas estimation gasLimit: 5980899, // override gas estimation
} }
const tx = await wallet.sendTransaction(req) const tx = await wallet.sendTransaction(req)
...@@ -236,7 +236,7 @@ describe('Basic RPC tests', () => { ...@@ -236,7 +236,7 @@ describe('Basic RPC tests', () => {
it('correctly exposes revert data for contract creations', async () => { it('correctly exposes revert data for contract creations', async () => {
const req: TransactionRequest = { const req: TransactionRequest = {
...revertingDeployTx, ...revertingDeployTx,
gasLimit: 177008999999, // override gas estimation gasLimit: 17700899, // override gas estimation
} }
const tx = await wallet.sendTransaction(req) const tx = await wallet.sendTransaction(req)
...@@ -353,7 +353,7 @@ describe('Basic RPC tests', () => { ...@@ -353,7 +353,7 @@ describe('Basic RPC tests', () => {
to: DEFAULT_TRANSACTION.to, to: DEFAULT_TRANSACTION.to,
value: 0, value: 0,
}) })
expect(estimate).to.be.eq(0x0dce9004c7) expect(estimate).to.be.eq(5920012)
}) })
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 () => {
...@@ -371,6 +371,7 @@ describe('Basic RPC tests', () => { ...@@ -371,6 +371,7 @@ describe('Basic RPC tests', () => {
const estimate = await l2Provider.estimateGas(tx) const estimate = await l2Provider.estimateGas(tx)
const l2Gaslimit = await l2Provider.send('eth_estimateExecutionGas', [ const l2Gaslimit = await l2Provider.send('eth_estimateExecutionGas', [
tx, tx,
true,
]) ])
const decoded = TxGasLimit.decode(estimate) const decoded = TxGasLimit.decode(estimate)
......
...@@ -92,9 +92,8 @@ var ( ...@@ -92,9 +92,8 @@ var (
) )
var ( var (
evictionInterval = time.Minute // Time interval to check for evictable transactions evictionInterval = time.Minute // Time interval to check for evictable transactions
statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats
gwei = big.NewInt(params.GWei) // 1 gwei, used as a flag for "rollup" transactions
) )
var ( var (
...@@ -540,10 +539,14 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { ...@@ -540,10 +539,14 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
} }
// Ensure the transaction doesn't exceed the current block limit gas. // Ensure the transaction doesn't exceed the current block limit gas.
// We skip this condition check if the transaction's gasPrice is set to 1gwei, if vm.UsingOVM {
// which indicates a "rollup" transaction that's paying for its data. if pool.currentMaxGas < tx.L2Gas() {
if pool.currentMaxGas < tx.L2Gas() && tx.GasPrice().Cmp(gwei) != 0 { return ErrGasLimit
return ErrGasLimit }
} else {
if pool.currentMaxGas < tx.Gas() {
return ErrGasLimit
}
} }
// Make sure the transaction is signed properly // Make sure the transaction is signed properly
......
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
) )
//go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go //go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go
...@@ -215,7 +216,7 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { ...@@ -215,7 +216,7 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) } func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) }
func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit } func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit }
func (tx *Transaction) L2Gas() uint64 { return tx.data.GasLimit % 100_000_000 } func (tx *Transaction) L2Gas() uint64 { return fees.DecodeL2GasLimitU64(tx.data.GasLimit) }
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) } func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) }
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) }
func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce }
......
...@@ -1148,9 +1148,17 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h ...@@ -1148,9 +1148,17 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h
// EstimateExecutionGas returns an estimate of the amount of gas needed to execute the // EstimateExecutionGas returns an estimate of the amount of gas needed to execute the
// given transaction against the current pending block. // given transaction against the current pending block.
func (s *PublicBlockChainAPI) EstimateExecutionGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { func (s *PublicBlockChainAPI) EstimateExecutionGas(ctx context.Context, args CallArgs, round *bool) (hexutil.Uint64, error) {
blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
return legacyDoEstimateGas(ctx, s.b, args, blockNrOrHash, s.b.RPCGasCap()) estimate, err := legacyDoEstimateGas(ctx, s.b, args, blockNrOrHash, s.b.RPCGasCap())
if err != nil {
return estimate, err
}
if round != nil && *round {
rounded := fees.Ceilmod(new(big.Int).SetUint64(uint64(estimate)), fees.BigTenThousand)
estimate = (hexutil.Uint64)(rounded.Uint64())
}
return estimate, nil
} }
// ExecutionResult groups all structured logs emitted by the EVM // ExecutionResult groups all structured logs emitted by the EVM
......
...@@ -3,6 +3,7 @@ package fees ...@@ -3,6 +3,7 @@ package fees
import ( import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
...@@ -10,12 +11,9 @@ import ( ...@@ -10,12 +11,9 @@ import (
// transaction in gas. // transaction in gas.
const overhead uint64 = 4200 + 200*params.TxDataNonZeroGasEIP2028 const overhead uint64 = 4200 + 200*params.TxDataNonZeroGasEIP2028
// hundredMillion is a constant used in the gas encoding formula
const hundredMillion uint64 = 100_000_000
// feeScalar is used to scale the calculations in EncodeL2GasLimit // feeScalar is used to scale the calculations in EncodeL2GasLimit
// to prevent them from being too large // to prevent them from being too large
const feeScalar uint64 = 1000 const feeScalar uint64 = 10_000_000
// TxGasPrice is a constant that determines the result of `eth_gasPrice` // TxGasPrice is a constant that determines the result of `eth_gasPrice`
// It is scaled upwards by 50% // It is scaled upwards by 50%
...@@ -26,7 +24,10 @@ const TxGasPrice uint64 = feeScalar + (feeScalar / 2) ...@@ -26,7 +24,10 @@ const TxGasPrice uint64 = feeScalar + (feeScalar / 2)
// BigTxGasPrice is the L2GasPrice as type big.Int // BigTxGasPrice is the L2GasPrice as type big.Int
var BigTxGasPrice = new(big.Int).SetUint64(TxGasPrice) var BigTxGasPrice = new(big.Int).SetUint64(TxGasPrice)
var bigFeeScalar = new(big.Int).SetUint64(feeScalar) var bigFeeScalar = new(big.Int).SetUint64(feeScalar)
var bigHundredMillion = new(big.Int).SetUint64(hundredMillion)
const tenThousand = 10000
var BigTenThousand = new(big.Int).SetUint64(tenThousand)
// EncodeTxGasLimit computes the `tx.gasLimit` based on the L1/L2 gas prices and // EncodeTxGasLimit computes the `tx.gasLimit` based on the L1/L2 gas prices and
// the L2 gas limit. The L2 gas limit is encoded inside of the lower order bits // the L2 gas limit. The L2 gas limit is encoded inside of the lower order bits
...@@ -40,32 +41,50 @@ var bigHundredMillion = new(big.Int).SetUint64(hundredMillion) ...@@ -40,32 +41,50 @@ var bigHundredMillion = new(big.Int).SetUint64(hundredMillion)
// the fee, so increasing the L2 Gas limit will increase the fee paid. // the fee, so increasing the L2 Gas limit will increase the fee paid.
// The calculation is: // The calculation is:
// l1GasLimit = zero_count(data) * 4 + non_zero_count(data) * 16 + overhead // l1GasLimit = zero_count(data) * 4 + non_zero_count(data) * 16 + overhead
// roundedL2GasLimit = ceilmod(l2GasLimit, 10_000)
// l1Fee = l1GasPrice * l1GasLimit // l1Fee = l1GasPrice * l1GasLimit
// l2Fee = l2GasPrice * l2GasLimit // l2Fee = l2GasPrice * roundedL2GasLimit
// sum = l1Fee + l2Fee // sum = l1Fee + l2Fee
// scaled = sum / scalar // scaled = sum / scalar
// rounded = ceilmod(scaled, hundredMillion) // rounded = ceilmod(scaled, tenThousand)
// result = rounded + l2GasLimit // roundedScaledL2GasLimit = roundedL2GasLimit / tenThousand
// result = rounded + roundedScaledL2GasLimit
// Note that for simplicity purposes, only the calldata is passed into this // Note that for simplicity purposes, only the calldata is passed into this
// function when in reality the RLP encoded transaction should be. The // function when in reality the RLP encoded transaction should be. The
// additional cost is added to the overhead constant to prevent the need to RLP // additional cost is added to the overhead constant to prevent the need to RLP
// encode transactions during calls to `eth_estimateGas` // encode transactions during calls to `eth_estimateGas`
func EncodeTxGasLimit(data []byte, l1GasPrice, l2GasLimit, l2GasPrice *big.Int) *big.Int { func EncodeTxGasLimit(data []byte, l1GasPrice, l2GasLimit, l2GasPrice *big.Int) *big.Int {
l1GasLimit := calculateL1GasLimit(data, overhead) l1GasLimit := calculateL1GasLimit(data, overhead)
roundedL2GasLimit := Ceilmod(l2GasLimit, BigTenThousand)
l1Fee := new(big.Int).Mul(l1GasPrice, l1GasLimit) l1Fee := new(big.Int).Mul(l1GasPrice, l1GasLimit)
l2Fee := new(big.Int).Mul(l2GasPrice, l2GasLimit) l2Fee := new(big.Int).Mul(l2GasPrice, roundedL2GasLimit)
sum := new(big.Int).Add(l1Fee, l2Fee) sum := new(big.Int).Add(l1Fee, l2Fee)
scaled := new(big.Int).Div(sum, bigFeeScalar) scaled := new(big.Int).Div(sum, bigFeeScalar)
remainder := new(big.Int).Mod(scaled, bigHundredMillion) rounded := Ceilmod(scaled, BigTenThousand)
scaledSum := new(big.Int).Add(scaled, bigHundredMillion) roundedScaledL2GasLimit := new(big.Int).Div(roundedL2GasLimit, BigTenThousand)
rounded := new(big.Int).Sub(scaledSum, remainder) result := new(big.Int).Add(rounded, roundedScaledL2GasLimit)
result := new(big.Int).Add(rounded, l2GasLimit)
return result return result
} }
func Ceilmod(a, b *big.Int) *big.Int {
remainder := new(big.Int).Mod(a, b)
if remainder.Cmp(common.Big0) == 0 {
return a
}
sum := new(big.Int).Add(a, b)
rounded := new(big.Int).Sub(sum, remainder)
return rounded
}
// DecodeL2GasLimit decodes the L2 gas limit from an encoded L2 gas limit // DecodeL2GasLimit decodes the L2 gas limit from an encoded L2 gas limit
func DecodeL2GasLimit(gasLimit *big.Int) *big.Int { func DecodeL2GasLimit(gasLimit *big.Int) *big.Int {
return new(big.Int).Mod(gasLimit, bigHundredMillion) scaled := new(big.Int).Mod(gasLimit, BigTenThousand)
return new(big.Int).Mul(scaled, BigTenThousand)
}
func DecodeL2GasLimitU64(gasLimit uint64) uint64 {
scaled := gasLimit % tenThousand
return scaled * tenThousand
} }
// calculateL1GasLimit computes the L1 gasLimit based on the calldata and // calculateL1GasLimit computes the L1 gasLimit based on the calldata and
......
...@@ -74,7 +74,7 @@ var feeTests = map[string]struct { ...@@ -74,7 +74,7 @@ var feeTests = map[string]struct {
"max-gaslimit": { "max-gaslimit": {
dataLen: 10, dataLen: 10,
l1GasPrice: params.GWei, l1GasPrice: params.GWei,
l2GasLimit: 99999999, l2GasLimit: 99_970_000,
l2GasPrice: params.GWei, l2GasPrice: params.GWei,
}, },
"larger-divisor": { "larger-divisor": {
...@@ -95,7 +95,8 @@ func TestCalculateRollupFee(t *testing.T) { ...@@ -95,7 +95,8 @@ func TestCalculateRollupFee(t *testing.T) {
fee := EncodeTxGasLimit(data, l1GasPrice, l2GasLimit, l2GasPrice) fee := EncodeTxGasLimit(data, l1GasPrice, l2GasLimit, l2GasPrice)
decodedGasLimit := DecodeL2GasLimit(fee) decodedGasLimit := DecodeL2GasLimit(fee)
if l2GasLimit.Cmp(decodedGasLimit) != 0 { roundedL2GasLimit := Ceilmod(l2GasLimit, BigTenThousand)
if roundedL2GasLimit.Cmp(decodedGasLimit) != 0 {
t.Errorf("rollup fee check failed: expected %d, got %d", l2GasLimit.Uint64(), decodedGasLimit) t.Errorf("rollup fee check failed: expected %d, got %d", l2GasLimit.Uint64(), decodedGasLimit)
} }
}) })
......
...@@ -14,6 +14,8 @@ The base `docker-compose.yml` file will start the required components for a full ...@@ -14,6 +14,8 @@ The base `docker-compose.yml` file will start the required components for a full
Supplementing the base configuration is an additional metric enabling file, `docker-compose-metrics.yml`. Adding this configuration to the stack will enable metric emission for l2geth and start grafana (for metrics visualisation) and influxdb (for metric collection) instances. Supplementing the base configuration is an additional metric enabling file, `docker-compose-metrics.yml`. Adding this configuration to the stack will enable metric emission for l2geth and start grafana (for metrics visualisation) and influxdb (for metric collection) instances.
Also available for testing is the `rpc-proxy` service in the `docker-compose-rpc-proxy.yml` file. It can be used to restrict what RPC methods are allowed to the Sequencer.
The base stack can be started and stopped with a command like this (there is no need to specify the default docker-compose.yml) The base stack can be started and stopped with a command like this (there is no need to specify the default docker-compose.yml)
``` ```
docker-compose \ docker-compose \
......
version: "3"
services:
rpc-proxy:
depends_on:
- l1_chain
- deployer
- l2geth
image: rpc-proxy
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.rpc-proxy
environment:
SEQUENCER: l2geth:8545
ETH_CALLS_ALLOWED: eth_blockNumber,eth_sendRawTransaction
ports:
- 9546:8080
- 9145:9145
...@@ -80,6 +80,7 @@ services: ...@@ -80,6 +80,7 @@ services:
- ./envs/geth.env - ./envs/geth.env
environment: environment:
ETH1_HTTP: http://l1_chain:8545 ETH1_HTTP: http://l1_chain:8545
ROLLUP_TIMESTAMP_REFRESH: 5s
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
# used for getting the addresses # used for getting the addresses
URL: http://deployer:8081/addresses.json URL: http://deployer:8081/addresses.json
...@@ -172,3 +173,4 @@ services: ...@@ -172,3 +173,4 @@ services:
URL: http://deployer:8081/addresses.json URL: http://deployer:8081/addresses.json
ENABLE_GAS_REPORT: 1 ENABLE_GAS_REPORT: 1
NO_NETWORK: 1 NO_NETWORK: 1
FROM openresty/openresty:buster
LABEL maintainer="Optimistic Systems <systems@optiomism.io>"
ARG GOTEMPLATE_VERSION=v3.9.0
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
openresty-opm \
&& opm get knyar/nginx-lua-prometheus
RUN curl -o /usr/local/bin/gomplate \
-sSL https://github.com/hairyhenderson/gomplate/releases/download/$GOTEMPLATE_VERSION/gomplate_linux-amd64-slim \
&& chmod +x /usr/local/bin/gomplate
RUN mkdir -p /var/log/nginx/ \
&& ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
COPY ./ops/docker/rpc-proxy/eth-jsonrpc-access.lua /usr/local/openresty/nginx/eth-jsonrpc-access.lua
COPY ./ops/docker/rpc-proxy/nginx.template.conf /docker-entrypoint.d/nginx.template.conf
COPY ./ops/docker/rpc-proxy/docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
#!/bin/bash
set -eo pipefail
if [ -z "$SEQUENCER" ];then
echo "SEQUENCER env must be set, exiting"
exit 1
fi
if [ -z "$ETH_CALLS_ALLOWED" ];then
echo "ETH_CALLS_ALLOWED env must be set, exiting"
exit 1
fi
gomplate -f /docker-entrypoint.d/nginx.template.conf > /usr/local/openresty/nginx/conf/nginx.conf
cat /usr/local/openresty/nginx/conf/nginx.conf
exec openresty "$@"
-- Source: https://github.com/adetante/ethereum-nginx-proxy
local cjson = require('cjson')
local function empty(s)
return s == nil or s == ''
end
local function split(s)
local res = {}
local i = 1
for v in string.gmatch(s, "([^,]+)") do
res[i] = v
i = i + 1
end
return res
end
local function contains(arr, val)
for i, v in ipairs (arr) do
if v == val then
return true
end
end
return false
end
-- parse conf
local blacklist, whitelist = nil
if not empty(ngx.var.jsonrpc_blacklist) then
blacklist = split(ngx.var.jsonrpc_blacklist)
end
if not empty(ngx.var.jsonrpc_whitelist) then
whitelist = split(ngx.var.jsonrpc_whitelist)
end
-- check conf
if blacklist ~= nil and whitelist ~= nil then
ngx.log(ngx.ERR, 'invalid conf: jsonrpc_blacklist and jsonrpc_whitelist are both set')
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
return
end
-- get request content
ngx.req.read_body()
-- try to parse the body as JSON
local success, body = pcall(cjson.decode, ngx.var.request_body);
if not success then
ngx.log(ngx.ERR, 'invalid JSON request')
ngx.exit(ngx.HTTP_BAD_REQUEST)
return
end
local method = body['method']
local version = body['jsonrpc']
-- check we have a method and a version
if empty(method) or empty(version) then
ngx.log(ngx.ERR, 'no method and/or jsonrpc attribute')
ngx.exit(ngx.HTTP_BAD_REQUEST)
return
end
metric_sequencer_requests:inc(1, {method, ngx.var.server_name, ngx.var.status})
-- check the version is supported
if version ~= "2.0" then
ngx.log(ngx.ERR, 'jsonrpc version not supported: ' .. version)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
return
end
-- if whitelist is configured, check that the method is whitelisted
if whitelist ~= nil then
if not contains(whitelist, method) then
ngx.log(ngx.ERR, 'jsonrpc method is not whitelisted: ' .. method)
ngx.exit(ngx.HTTP_FORBIDDEN)
return
end
end
-- if blacklist is configured, check that the method is not blacklisted
if blacklist ~= nil then
if contains(blacklist, method) then
ngx.log(ngx.ERR, 'jsonrpc method is blacklisted: ' .. method)
ngx.exit(ngx.HTTP_FORBIDDEN)
return
end
end
return
worker_processes 5;
daemon off;
error_log /var/log/nginx/error.log;
worker_rlimit_nofile 8192;
pcre_jit on;
events {
worker_connections 4096;
}
http {
include mime.types;
index index.html;
# See Move default writable paths to a dedicated directory (#119)
# https://github.com/openresty/docker-openresty/issues/119
client_body_temp_path /var/run/openresty/nginx-client-body;
proxy_temp_path /var/run/openresty/nginx-proxy;
fastcgi_temp_path /var/run/openresty/nginx-fastcgi;
uwsgi_temp_path /var/run/openresty/nginx-uwsgi;
scgi_temp_path /var/run/openresty/nginx-scgi;
keepalive_timeout 0;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] $status '
'"$request" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
lua_shared_dict prometheus_metrics 10M;
init_worker_by_lua_block {
prometheus = require("prometheus").init("prometheus_metrics")
metric_requests = prometheus:counter(
"nginx_http_requests_total", "Number of HTTP requests", {"host", "status"})
metric_sequencer_requests = prometheus:counter(
"nginx_eth_sequencer_requests", "Number of requests going to the sequencer", {"method", "host", "status"})
metric_replica_requests = prometheus:counter(
"nginx_eth_replica_requests", "Number of requests going to the replicas", {"host", "status"})
metric_latency = prometheus:histogram(
"nginx_http_request_duration_seconds", "HTTP request latency", {"host"})
metric_connections = prometheus:gauge(
"nginx_http_connections", "Number of HTTP connections", {"state"})
}
log_by_lua_block {
metric_requests:inc(1, {ngx.var.server_name, ngx.var.status})
metric_latency:observe(tonumber(ngx.var.request_time), {ngx.var.server_name})
}
upstream sequencer {
server {{env.Getenv "SEQUENCER"}};
}
server { # RPC proxy server
listen 8080;
location = /healthz {
return 200 'healthz';
}
location / {
set $jsonrpc_whitelist {{env.Getenv "ETH_CALLS_ALLOWED"}};
access_by_lua_file 'eth-jsonrpc-access.lua';
proxy_pass http://sequencer;
}
}
server { # Metrics server
listen 9145;
location /metrics {
content_by_lua_block {
metric_connections:set(ngx.var.connections_reading, {"reading"})
metric_connections:set(ngx.var.connections_waiting, {"waiting"})
metric_connections:set(ngx.var.connections_writing, {"writing"})
prometheus:collect()
}
}
}
}
\ No newline at end of file
...@@ -33,7 +33,6 @@ import { ...@@ -33,7 +33,6 @@ import {
QueueOrigin, QueueOrigin,
Batch, Batch,
Signature, Signature,
TxType,
remove0x, remove0x,
} from '@eth-optimism/core-utils' } from '@eth-optimism/core-utils'
import { Logger, Metrics } from '@eth-optimism/common-ts' import { Logger, Metrics } from '@eth-optimism/common-ts'
...@@ -252,7 +251,7 @@ describe('BatchSubmitter', () => { ...@@ -252,7 +251,7 @@ describe('BatchSubmitter', () => {
{ {
rawTransaction: '0x1234', rawTransaction: '0x1234',
l1BlockNumber: nextQueueElement.blockNumber - 1, l1BlockNumber: nextQueueElement.blockNumber - 1,
txType: TxType.EIP155, txType: 0,
queueOrigin: QueueOrigin.Sequencer, queueOrigin: QueueOrigin.Sequencer,
l1TxOrigin: null, l1TxOrigin: null,
} as any, } as any,
...@@ -301,7 +300,7 @@ describe('BatchSubmitter', () => { ...@@ -301,7 +300,7 @@ describe('BatchSubmitter', () => {
{ {
rawTransaction: '0x1234', rawTransaction: '0x1234',
l1BlockNumber: nextQueueElement.blockNumber - 1, l1BlockNumber: nextQueueElement.blockNumber - 1,
txType: TxType.EthSign, txType: 1,
queueOrigin: QueueOrigin.Sequencer, queueOrigin: QueueOrigin.Sequencer,
l1TxOrigin: null, l1TxOrigin: null,
} as any, } as any,
...@@ -405,7 +404,7 @@ describe('BatchSubmitter', () => { ...@@ -405,7 +404,7 @@ describe('BatchSubmitter', () => {
{ {
rawTransaction: '0x1234', rawTransaction: '0x1234',
l1BlockNumber: nextQueueElement.blockNumber - 1, l1BlockNumber: nextQueueElement.blockNumber - 1,
txType: TxType.EIP155, txType: 0,
queueOrigin: QueueOrigin.Sequencer, queueOrigin: QueueOrigin.Sequencer,
l1TxOrigin: null, l1TxOrigin: null,
} as any, } as any,
......
...@@ -6,9 +6,9 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; ...@@ -6,9 +6,9 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/** /**
* @title OVM_GasPriceOracle * @title OVM_GasPriceOracle
* @dev This contract exposes the current execution price, a measure of how congested the network * @dev This contract exposes the current l2 gas price, a measure of how congested the network
* currently is. This measure is used by the Sequencer to determine what fee to charge for * currently is. This measure is used by the Sequencer to determine what fee to charge for
* transactions. When the system is more congested, the execution price will increase and fees * transactions. When the system is more congested, the l2 gas price will increase and fees
* will also increase as a result. * will also increase as a result.
* *
* Compiler used: optimistic-solc * Compiler used: optimistic-solc
...@@ -20,8 +20,8 @@ contract OVM_GasPriceOracle is Ownable { ...@@ -20,8 +20,8 @@ contract OVM_GasPriceOracle is Ownable {
* Variables * * Variables *
*************/ *************/
// Current execution price // Current l2 gas price
uint256 internal executionPrice; uint256 public gasPrice;
/*************** /***************
* Constructor * * Constructor *
...@@ -31,10 +31,12 @@ contract OVM_GasPriceOracle is Ownable { ...@@ -31,10 +31,12 @@ contract OVM_GasPriceOracle is Ownable {
* @param _owner Address that will initially own this contract. * @param _owner Address that will initially own this contract.
*/ */
constructor( constructor(
address _owner address _owner,
uint256 _initialGasPrice
) )
Ownable() Ownable()
{ {
setGasPrice(_initialGasPrice);
transferOwnership(_owner); transferOwnership(_owner);
} }
...@@ -44,28 +46,15 @@ contract OVM_GasPriceOracle is Ownable { ...@@ -44,28 +46,15 @@ contract OVM_GasPriceOracle is Ownable {
********************/ ********************/
/** /**
* @return Current execution price. * Allows the owner to modify the l2 gas price.
* @param _gasPrice New l2 gas price.
*/ */
function getExecutionPrice() function setGasPrice(
public uint256 _gasPrice
view
returns (
uint256
)
{
return executionPrice;
}
/**
* Allows the owner to modify the execution price.
* @param _executionPrice New execution price.
*/
function setExecutionPrice(
uint256 _executionPrice
) )
public public
onlyOwner onlyOwner
{ {
executionPrice = _executionPrice; gasPrice = _gasPrice;
} }
} }
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
/* Imports: Internal */
import { getContractDefinition } from '../src'
const deployFn: DeployFunction = async (hre: any) => {
const { deployments, getNamedAccounts } = hre
const { deploy } = deployments
const { deployer } = await getNamedAccounts()
const gasPriceOracle = getContractDefinition('OVM_GasPriceOracle', true)
const gasOracleOwner = (hre as any).deployConfig.ovmSequencerAddress
const initialGasPrice = (hre as any).deployConfig.initialGasPriceOracleGasPrice
if (!gasOracleOwner || !initialGasPrice) {
throw new Error('initialGasPrice & ovmSequencerAddress required to deploy gas price oracle')
}
await deploy('OVM_GasPriceOracle', {
contract: gasPriceOracle,
from: deployer,
args: [gasOracleOwner, initialGasPrice],
log: true,
});
}
deployFn.tags = ['OVM_GasPriceOracle']
export default deployFn
# Optimism Regenesis Deployments # Optimism Regenesis Deployments
## LAYER 2 ## LAYER 2
## OPTIMISTIC-KOVAN
Network : __optimistic-kovan (chain id: 69)__
|Contract|Address|
|--|--|
|OVM_GasPriceOracle|[0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76](https://kovan-optimistic.etherscan.io/address/0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76)|
---
### Chain IDs: ### Chain IDs:
- Mainnet: 10 - Mainnet: 10
- Kovan: 69 - Kovan: 69
......
...@@ -99,6 +99,12 @@ task('deploy') ...@@ -99,6 +99,12 @@ task('deploy')
undefined, undefined,
types.string types.string
) )
.addOptionalParam(
'initialGasPriceOracleGasPrice',
'The initial execution price for the gas price oracle.',
undefined,
types.int
)
.setAction(async (args, hre: any, runSuper) => { .setAction(async (args, hre: any, runSuper) => {
// Necessary because hardhat doesn't let us attach non-optional parameters to existing tasks. // Necessary because hardhat doesn't let us attach non-optional parameters to existing tasks.
const validateAddressArg = (argName: string) => { const validateAddressArg = (argName: string) => {
......
...@@ -5,6 +5,7 @@ import { ethers } from 'hardhat' ...@@ -5,6 +5,7 @@ import { ethers } from 'hardhat'
import { ContractFactory, Contract, Signer } from 'ethers' import { ContractFactory, Contract, Signer } from 'ethers'
describe('OVM_GasPriceOracle', () => { describe('OVM_GasPriceOracle', () => {
const initialGasPrice = 0
let signer1: Signer let signer1: Signer
let signer2: Signer let signer2: Signer
before(async () => { before(async () => {
...@@ -21,7 +22,8 @@ describe('OVM_GasPriceOracle', () => { ...@@ -21,7 +22,8 @@ describe('OVM_GasPriceOracle', () => {
let OVM_GasPriceOracle: Contract let OVM_GasPriceOracle: Contract
beforeEach(async () => { beforeEach(async () => {
OVM_GasPriceOracle = await Factory__OVM_GasPriceOracle.deploy( OVM_GasPriceOracle = await Factory__OVM_GasPriceOracle.deploy(
await signer1.getAddress() await signer1.getAddress(),
initialGasPrice
) )
}) })
...@@ -33,50 +35,44 @@ describe('OVM_GasPriceOracle', () => { ...@@ -33,50 +35,44 @@ describe('OVM_GasPriceOracle', () => {
}) })
}) })
describe('setExecutionPrice', () => { describe('setGasPrice', () => {
it('should revert if called by someone other than the owner', async () => { it('should revert if called by someone other than the owner', async () => {
await expect(OVM_GasPriceOracle.connect(signer2).setExecutionPrice(1234)) await expect(OVM_GasPriceOracle.connect(signer2).setGasPrice(1234)).to.be
.to.be.reverted .reverted
}) })
it('should succeed if called by the owner', async () => { it('should succeed if called by the owner and is equal to `0`', async () => {
await expect(OVM_GasPriceOracle.connect(signer1).setExecutionPrice(1234)) await expect(OVM_GasPriceOracle.connect(signer1).setGasPrice(0)).to.not.be
.to.not.be.reverted .reverted
}) })
}) })
describe('getExecutionPrice', () => { describe('get gasPrice', () => {
it('should return zero at first', async () => { it('should return zero at first', async () => {
expect(await OVM_GasPriceOracle.getExecutionPrice()).to.equal(0) expect(await OVM_GasPriceOracle.gasPrice()).to.equal(initialGasPrice)
}) })
it('should change when setExecutionPrice is called', async () => { it('should change when setGasPrice is called', async () => {
const executionPrice = 1234 const gasPrice = 1234
await OVM_GasPriceOracle.connect(signer1).setExecutionPrice( await OVM_GasPriceOracle.connect(signer1).setGasPrice(gasPrice)
executionPrice
)
expect(await OVM_GasPriceOracle.getExecutionPrice()).to.equal( expect(await OVM_GasPriceOracle.gasPrice()).to.equal(gasPrice)
executionPrice
)
}) })
it('is the 1st storage slot', async () => { it('is the 1st storage slot', async () => {
const executionPrice = 1234 const gasPrice = 1234
const slot = 1 const slot = 1
// set the price // set the price
await OVM_GasPriceOracle.connect(signer1).setExecutionPrice( await OVM_GasPriceOracle.connect(signer1).setGasPrice(gasPrice)
executionPrice
)
// get the storage slot value // get the storage slot value
const priceAtSlot = await signer1.provider.getStorageAt( const priceAtSlot = await signer1.provider.getStorageAt(
OVM_GasPriceOracle.address, OVM_GasPriceOracle.address,
slot slot
) )
expect(await OVM_GasPriceOracle.getExecutionPrice()).to.equal( expect(await OVM_GasPriceOracle.gasPrice()).to.equal(
ethers.BigNumber.from(priceAtSlot) ethers.BigNumber.from(priceAtSlot)
) )
}) })
......
/* Internal Imports */
import { add0x, remove0x, toVerifiedBytes, encodeHex, getLen } from '../common'
import { Coder, Signature, Uint16, Uint8, Uint24, Address } from './types'
/***********************
* TxTypes and TxData *
**********************/
export enum TxType {
EIP155 = 0,
EthSign = 1,
EthSign2 = 2,
}
export const txTypePlainText = {
0: TxType.EIP155,
1: TxType.EthSign,
2: TxType.EthSign2,
EIP155: TxType.EIP155,
EthSign: TxType.EthSign,
}
export interface DefaultEcdsaTxData {
sig: Signature
gasLimit: Uint16
gasPrice: Uint8
nonce: Uint24
target: Address
data: string
type: TxType
}
export interface EIP155TxData extends DefaultEcdsaTxData {}
export interface EthSignTxData extends DefaultEcdsaTxData {}
/***********************
* Encoding Positions *
**********************/
/*
* The positions in the tx data for the different transaction types
*/
export const TX_TYPE_POSITION = { start: 0, end: 1 }
/*
* The positions in the tx data for the EIP155TxData and EthSignTxData
*/
export const SIGNATURE_FIELD_POSITIONS = {
r: { start: 1, end: 33 }, // 32 bytes
s: { start: 33, end: 65 }, // 32 bytes
v: { start: 65, end: 66 }, // 1 byte
}
export const DEFAULT_ECDSA_TX_FIELD_POSITIONS = {
txType: TX_TYPE_POSITION, // 1 byte
sig: SIGNATURE_FIELD_POSITIONS, // 65 bytes
gasLimit: { start: 66, end: 69 }, // 3 bytes
gasPrice: { start: 69, end: 72 }, // 3 byte
nonce: { start: 72, end: 75 }, // 3 bytes
target: { start: 75, end: 95 }, // 20 bytes
data: { start: 95 }, // byte 95 onward
}
export const EIP155_TX_FIELD_POSITIONS = DEFAULT_ECDSA_TX_FIELD_POSITIONS
export const ETH_SIGN_TX_FIELD_POSITIONS = DEFAULT_ECDSA_TX_FIELD_POSITIONS
export const CTC_TX_GAS_PRICE_MULT_FACTOR = 1_000_000
/***************
* EcdsaCoders *
**************/
class DefaultEcdsaTxCoder implements Coder {
constructor(readonly txType: TxType) {}
public encode(txData: DefaultEcdsaTxData): string {
const txType = encodeHex(
this.txType,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.txType)
)
const r = toVerifiedBytes(
txData.sig.r,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.sig.r)
)
const s = toVerifiedBytes(
txData.sig.s,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.sig.s)
)
const v = encodeHex(
txData.sig.v,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.sig.v)
)
const gasLimit = encodeHex(
txData.gasLimit,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.gasLimit)
)
if (txData.gasPrice % CTC_TX_GAS_PRICE_MULT_FACTOR !== 0) {
throw new Error(`Gas Price ${txData.gasPrice} cannot be encoded`)
}
const gasPrice = encodeHex(
txData.gasPrice / CTC_TX_GAS_PRICE_MULT_FACTOR,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.gasPrice)
)
const nonce = encodeHex(
txData.nonce,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.nonce)
)
const target = toVerifiedBytes(
txData.target,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.target)
)
// Make sure that the data is even
if (txData.data.length % 2 !== 0) {
throw new Error('Non-even hex string for tx data!')
}
const encoding =
'0x' +
txType +
r +
s +
v +
gasLimit +
gasPrice +
nonce +
target +
remove0x(txData.data)
return encoding
}
public decode(txData: string): DefaultEcdsaTxData {
txData = remove0x(txData)
const sliceBytes = (position: { start; end? }): string =>
txData.slice(position.start * 2, position.end * 2)
const pos = DEFAULT_ECDSA_TX_FIELD_POSITIONS
if (parseInt(sliceBytes(pos.txType), 16) !== this.txType) {
throw new Error('Invalid tx type')
}
return {
sig: {
r: add0x(sliceBytes(pos.sig.r)),
s: add0x(sliceBytes(pos.sig.s)),
v: parseInt(sliceBytes(pos.sig.v), 16),
},
gasLimit: parseInt(sliceBytes(pos.gasLimit), 16),
gasPrice:
parseInt(sliceBytes(pos.gasPrice), 16) * CTC_TX_GAS_PRICE_MULT_FACTOR,
nonce: parseInt(sliceBytes(pos.nonce), 16),
target: add0x(sliceBytes(pos.target)),
data: add0x(txData.slice(pos.data.start * 2)),
type: this.txType,
}
}
}
class EthSignTxCoder extends DefaultEcdsaTxCoder {
constructor() {
super(TxType.EthSign)
}
public encode(txData: EthSignTxData): string {
return super.encode(txData)
}
public decode(txData: string): EthSignTxData {
return super.decode(txData)
}
}
class EthSign2TxCoder extends DefaultEcdsaTxCoder {
constructor() {
super(TxType.EthSign2)
}
public encode(txData: EthSignTxData): string {
return super.encode(txData)
}
public decode(txData: string): EthSignTxData {
return super.decode(txData)
}
}
class Eip155TxCoder extends DefaultEcdsaTxCoder {
constructor() {
super(TxType.EIP155)
}
public encode(txData: EIP155TxData): string {
return super.encode(txData)
}
public decode(txData: string): EIP155TxData {
return super.decode(txData)
}
}
/*************
* ctcCoder *
************/
function encode(data: EIP155TxData): string {
if (data.type === TxType.EIP155) {
return new Eip155TxCoder().encode(data)
}
if (data.type === TxType.EthSign) {
return new EthSignTxCoder().encode(data)
}
return null
}
function decode(data: string | Buffer): EIP155TxData {
if (Buffer.isBuffer(data)) {
data = data.toString()
}
data = remove0x(data)
const type = parseInt(data.slice(0, 2), 16)
if (type === TxType.EIP155) {
return new Eip155TxCoder().decode(data)
}
if (type === TxType.EthSign) {
return new EthSignTxCoder().decode(data)
}
if (type === TxType.EthSign2) {
return new EthSign2TxCoder().decode(data)
}
return null
}
/*
* Encoding and decoding functions for all txData types.
*/
export const ctcCoder = {
eip155TxData: new Eip155TxCoder(),
ethSignTxData: new EthSignTxCoder(),
ethSign2TxData: new EthSign2TxCoder(),
encode,
decode,
}
export * from './ecdsa-coder'
export * from './types' export * from './types'
export * from './sequencer-batch' export * from './sequencer-batch'
...@@ -8,8 +8,3 @@ export type Uint16 = number ...@@ -8,8 +8,3 @@ export type Uint16 = number
export type Uint8 = number export type Uint8 = number
export type Uint24 = number export type Uint24 = number
export type Address = string export type Address = string
export interface Coder {
encode: Function
decode: Function
}
...@@ -6,11 +6,12 @@ import { BigNumber } from 'ethers' ...@@ -6,11 +6,12 @@ import { BigNumber } from 'ethers'
import { remove0x } from './common' import { remove0x } from './common'
const hundredMillion = BigNumber.from(100_000_000) const hundredMillion = BigNumber.from(100_000_000)
const feeScalar = 1000 const feeScalar = 10_000_000
export const TxGasPrice = BigNumber.from(feeScalar + feeScalar / 2) export const TxGasPrice = BigNumber.from(feeScalar + feeScalar / 2)
const txDataZeroGas = 4 const txDataZeroGas = 4
const txDataNonZeroGasEIP2028 = 16 const txDataNonZeroGasEIP2028 = 16
const overhead = 4200 + 200 * txDataNonZeroGasEIP2028 const overhead = 4200 + 200 * txDataNonZeroGasEIP2028
const tenThousand = BigNumber.from(10_000)
export interface EncodableL2GasLimit { export interface EncodableL2GasLimit {
data: Buffer | string data: Buffer | string
...@@ -32,21 +33,22 @@ function encode(input: EncodableL2GasLimit): BigNumber { ...@@ -32,21 +33,22 @@ function encode(input: EncodableL2GasLimit): BigNumber {
l2GasPrice = BigNumber.from(l2GasPrice) l2GasPrice = BigNumber.from(l2GasPrice)
} }
const l1GasLimit = calculateL1GasLimit(data) const l1GasLimit = calculateL1GasLimit(data)
const roundedL2GasLimit = ceilmod(l2GasLimit, tenThousand)
const l1Fee = l1GasLimit.mul(l1GasPrice) const l1Fee = l1GasLimit.mul(l1GasPrice)
const l2Fee = l2GasLimit.mul(l2GasPrice) const l2Fee = roundedL2GasLimit.mul(l2GasPrice)
const sum = l1Fee.add(l2Fee) const sum = l1Fee.add(l2Fee)
const scaled = sum.div(feeScalar) const scaled = sum.div(feeScalar)
const remainder = scaled.mod(hundredMillion) const rounded = ceilmod(scaled, tenThousand)
const scaledSum = scaled.add(hundredMillion) const roundedScaledL2GasLimit = roundedL2GasLimit.div(tenThousand)
const rounded = scaledSum.sub(remainder) return rounded.add(roundedScaledL2GasLimit)
return rounded.add(l2GasLimit)
} }
function decode(fee: BigNumber | number): BigNumber { function decode(fee: BigNumber | number): BigNumber {
if (typeof fee === 'number') { if (typeof fee === 'number') {
fee = BigNumber.from(fee) fee = BigNumber.from(fee)
} }
return fee.mod(hundredMillion) const scaled = fee.mod(tenThousand)
return scaled.mul(tenThousand)
} }
export const TxGasLimit = { export const TxGasLimit = {
...@@ -54,6 +56,22 @@ export const TxGasLimit = { ...@@ -54,6 +56,22 @@ export const TxGasLimit = {
decode, decode,
} }
export function ceilmod(a: BigNumber | number, b: BigNumber | number) {
if (typeof a === 'number') {
a = BigNumber.from(a)
}
if (typeof b === 'number') {
b = BigNumber.from(b)
}
const remainder = a.mod(b)
if (remainder.eq(0)) {
return a
}
const sum = a.add(b)
const rounded = sum.sub(remainder)
return rounded
}
export function calculateL1GasLimit(data: string | Buffer): BigNumber { export function calculateL1GasLimit(data: string | Buffer): BigNumber {
const [zeroes, ones] = zeroesAndOnes(data) const [zeroes, ones] = zeroesAndOnes(data)
const zeroesCost = zeroes * txDataZeroGas const zeroesCost = zeroes * txDataZeroGas
......
...@@ -2,63 +2,13 @@ import '../setup' ...@@ -2,63 +2,13 @@ import '../setup'
/* Internal Imports */ /* Internal Imports */
import { import {
ctcCoder,
encodeAppendSequencerBatch, encodeAppendSequencerBatch,
decodeAppendSequencerBatch, decodeAppendSequencerBatch,
TxType,
sequencerBatch, sequencerBatch,
} from '../../src' } from '../../src'
import { expect } from 'chai' import { expect } from 'chai'
describe('BatchEncoder', () => { describe('BatchEncoder', () => {
describe('eip155TxData', () => {
it('should encode & then decode to the correct value', () => {
const eip155TxData = {
sig: {
v: 1,
r: '0x' + '11'.repeat(32),
s: '0x' + '22'.repeat(32),
},
gasLimit: 500,
gasPrice: 1000000,
nonce: 100,
target: '0x' + '12'.repeat(20),
data: '0x' + '99'.repeat(10),
type: TxType.EIP155,
}
const encoded = ctcCoder.eip155TxData.encode(eip155TxData)
const decoded = ctcCoder.eip155TxData.decode(encoded)
expect(eip155TxData).to.deep.equal(decoded)
})
it('should fail encoding a bad gas price', () => {
const badGasPrice = 1000001
const eip155TxData = {
sig: {
v: 1,
r: '0x' + '11'.repeat(32),
s: '0x' + '22'.repeat(32),
},
gasLimit: 500,
gasPrice: badGasPrice,
nonce: 100,
target: '0x' + '12'.repeat(20),
data: '0x' + '99'.repeat(10),
type: TxType.EIP155,
}
let error
try {
ctcCoder.eip155TxData.encode(eip155TxData)
} catch (e) {
error = e
}
expect(error.message).to.equal(
`Gas Price ${badGasPrice} cannot be encoded`
)
})
})
describe('appendSequencerBatch', () => { describe('appendSequencerBatch', () => {
it('should work with the simple case', () => { it('should work with the simple case', () => {
const batch = { const batch = {
...@@ -100,43 +50,4 @@ describe('BatchEncoder', () => { ...@@ -100,43 +50,4 @@ describe('BatchEncoder', () => {
} }
}) })
}) })
describe('generic ctcCoder', () => {
it('should decode EIP155 txs to the correct value', () => {
const eip155TxData = {
sig: {
v: 1,
r: '0x' + '11'.repeat(32),
s: '0x' + '22'.repeat(32),
},
gasLimit: 500,
gasPrice: 1000000,
nonce: 100,
target: '0x' + '12'.repeat(20),
data: '0x' + '99'.repeat(10),
type: TxType.EIP155,
}
const encoded = ctcCoder.encode(eip155TxData)
const decoded = ctcCoder.decode(encoded)
expect(eip155TxData).to.deep.equal(decoded)
})
it('should return null when encoding an unknown type', () => {
const weirdTypeTxData = {
sig: {
v: 1,
r: '0x' + '11'.repeat(32),
s: '0x' + '22'.repeat(32),
},
gasLimit: 500,
gasPrice: 100,
nonce: 100,
target: '0x' + '12'.repeat(20),
data: '0x' + '99'.repeat(10),
type: 420,
}
const encoded = ctcCoder.encode(weirdTypeTxData)
expect(encoded).to.be.null
})
})
}) })
...@@ -56,7 +56,7 @@ describe('Fees', () => { ...@@ -56,7 +56,7 @@ describe('Fees', () => {
dataLen: 10, dataLen: 10,
l1GasPrice: utils.parseUnits('5', 'ether'), l1GasPrice: utils.parseUnits('5', 'ether'),
l2GasPrice: utils.parseUnits('5', 'ether'), l2GasPrice: utils.parseUnits('5', 'ether'),
l2GasLimit: 10 ** 8 - 1, l2GasLimit: 99_970_000,
}, },
{ {
name: 'zero-l2-gasprice', name: 'zero-l2-gasprice',
...@@ -113,7 +113,8 @@ describe('Fees', () => { ...@@ -113,7 +113,8 @@ describe('Fees', () => {
}) })
const decoded = fees.TxGasLimit.decode(got) const decoded = fees.TxGasLimit.decode(got)
expect(decoded).to.deep.eq(BigNumber.from(test.l2GasLimit)) const roundedL2GasLimit = fees.ceilmod(test.l2GasLimit, 10_000)
expect(decoded).to.deep.eq(roundedL2GasLimit)
}) })
} }
}) })
......
...@@ -2,11 +2,9 @@ ...@@ -2,11 +2,9 @@
import { BigNumber, ethers, constants } from 'ethers' import { BigNumber, ethers, constants } from 'ethers'
import { getContractFactory } from '@eth-optimism/contracts' import { getContractFactory } from '@eth-optimism/contracts'
import { import {
ctcCoder,
fromHexString, fromHexString,
toHexString, toHexString,
toRpcHexString, toRpcHexString,
TxType,
EventArgsSequencerBatchAppended, EventArgsSequencerBatchAppended,
} from '@eth-optimism/core-utils' } from '@eth-optimism/core-utils'
......
...@@ -198,6 +198,7 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> { ...@@ -198,6 +198,7 @@ export class L1TransportServer extends BaseService<L1TransportServerOptions> {
url: req.url, url: req.url,
elapsed, elapsed,
msg: e.toString(), msg: e.toString(),
stack: e.stack,
}) })
return res.status(400).json({ return res.status(400).json({
error: e.toString(), error: e.toString(),
......
# @eth-optimism/message-relayer
This package contains:
1. A service for relaying messages from L2 to L1.
2. Utilities for finding these messages and relaying them.
## Installation
```
yarn add @eth-optimism/message-relayer
```
## Relay Utilities
### getMessagesAndProofsForL2Transaction
Finds all L2 => L1 messages sent in a given L2 transaction and generates proof for each.
#### Usage
```typescript
import { getMessagesAndProofsForL2Transaction } from '@eth-optimism/message-relayer'
const main = async () => {
const l1RpcProviderUrl = 'https://layer1.endpoint'
const l2RpcProviderUrl = 'https://layer2.endpoint'
const l1StateCommitmentChainAddress = 'address of OVM_StateCommitmentChain from deployments page'
const l2CrossDomainMessengerAddress = 'address of OVM_L2CrossDomainMessenger from deployments page'
const l2TransactionHash = 'hash of the transaction with messages to relay'
const messagePairs = await getMessagesAndProofsForL2Transaction(
l1RpcProviderUrl,
l2RpcProviderUrl,
l1StateCommitmentChainAddress,
l2CrossDomainMessengerAddress,
l2TransactionHash
)
console.log(messagePairs)
// Will log something along the lines of:
// [
// {
// message: {
// target: '0x...',
// sender: '0x...',
// message: '0x...',
// messageNonce: 1234...
// },
// proof: {
// // complicated
// }
// }
// ]
// You can then do something along the lines of:
// for (const { message, proof } of messagePairs) {
// await l1CrossDomainMessenger.relayMessage(
// message.target,
// message.sender,
// message.message,
// message.messageNonce,
// proof
// )
// }
}
main()
```
{ {
"name": "@eth-optimism/message-relayer", "name": "@eth-optimism/message-relayer",
"version": "0.1.3", "version": "0.1.3",
"private": true,
"description": "[Optimism] Cross Domain Message Relayer service", "description": "[Optimism] Cross Domain Message Relayer service",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -36,7 +35,6 @@ ...@@ -36,7 +35,6 @@
"bcfg": "^0.1.6", "bcfg": "^0.1.6",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"ethers": "^5.1.0", "ethers": "^5.1.0",
"google-spreadsheet": "^3.1.15",
"merkletreejs": "^0.2.18", "merkletreejs": "^0.2.18",
"rlp": "^2.2.6" "rlp": "^2.2.6"
}, },
......
import { Wallet, providers } from 'ethers' import { Wallet, providers } from 'ethers'
import { MessageRelayerService } from '../service' import { MessageRelayerService } from '../service'
import { Bcfg } from '@eth-optimism/core-utils' import { Bcfg } from '@eth-optimism/core-utils'
import SpreadSheet from '../spreadsheet'
import * as dotenv from 'dotenv' import * as dotenv from 'dotenv'
import Config from 'bcfg' import Config from 'bcfg'
...@@ -49,18 +48,6 @@ const main = async () => { ...@@ -49,18 +48,6 @@ const main = async () => {
parseInt(env.FROM_L2_TRANSACTION_INDEX, 10) || 0 parseInt(env.FROM_L2_TRANSACTION_INDEX, 10) || 0
) )
// Spreadsheet configuration
const SPREADSHEET_MODE = config.bool(
'spreadsheet-mode',
!!env.SPREADSHEET_MODE || false
)
const SHEET_ID = config.str('sheet-id', env.SHEET_ID)
const CLIENT_EMAIL = config.str('client-email', env.CLIENT_EMAIL)
const CLIENT_PRIVATE_KEY = config.str(
'client-private-key',
env.CLIENT_PRIVATE_KEY
)
if (!ADDRESS_MANAGER_ADDRESS) { if (!ADDRESS_MANAGER_ADDRESS) {
throw new Error('Must pass ADDRESS_MANAGER_ADDRESS') throw new Error('Must pass ADDRESS_MANAGER_ADDRESS')
} }
...@@ -84,22 +71,6 @@ const main = async () => { ...@@ -84,22 +71,6 @@ const main = async () => {
throw new Error('Must pass one of L1_WALLET_KEY or MNEMONIC') throw new Error('Must pass one of L1_WALLET_KEY or MNEMONIC')
} }
let spreadsheet = null
if (SPREADSHEET_MODE) {
if (!SHEET_ID) {
throw new Error('Must pass SHEET_ID')
}
if (!CLIENT_EMAIL) {
throw new Error('Must pass CLIENT_EMAIL')
}
if (!CLIENT_PRIVATE_KEY) {
throw new Error('Must pass CLIENT_PRIVATE_KEY')
}
const privateKey = CLIENT_PRIVATE_KEY.replace(/\\n/g, '\n')
spreadsheet = new SpreadSheet(SHEET_ID)
await spreadsheet.init(CLIENT_EMAIL, privateKey)
}
const service = new MessageRelayerService({ const service = new MessageRelayerService({
l1RpcProvider: l1Provider, l1RpcProvider: l1Provider,
l2RpcProvider: l2Provider, l2RpcProvider: l2Provider,
...@@ -111,8 +82,6 @@ const main = async () => { ...@@ -111,8 +82,6 @@ const main = async () => {
l2BlockOffset: L2_BLOCK_OFFSET, l2BlockOffset: L2_BLOCK_OFFSET,
l1StartOffset: L1_START_OFFSET, l1StartOffset: L1_START_OFFSET,
getLogsInterval: GET_LOGS_INTERVAL, getLogsInterval: GET_LOGS_INTERVAL,
spreadsheetMode: !!SPREADSHEET_MODE,
spreadsheet,
}) })
await service.start() await service.start()
......
...@@ -323,12 +323,19 @@ const getStateTrieProof = async ( ...@@ -323,12 +323,19 @@ const getStateTrieProof = async (
* @returns An array of messages sent in the transaction and a proof of inclusion for each. * @returns An array of messages sent in the transaction and a proof of inclusion for each.
*/ */
export const getMessagesAndProofsForL2Transaction = async ( export const getMessagesAndProofsForL2Transaction = async (
l1RpcProvider: ethers.providers.JsonRpcProvider, l1RpcProvider: ethers.providers.JsonRpcProvider | string,
l2RpcProvider: ethers.providers.JsonRpcProvider, l2RpcProvider: ethers.providers.JsonRpcProvider | string,
l1StateCommitmentChainAddress: string, l1StateCommitmentChainAddress: string,
l2CrossDomainMessengerAddress: string, l2CrossDomainMessengerAddress: string,
l2TransactionHash: string l2TransactionHash: string
): Promise<CrossDomainMessagePair[]> => { ): Promise<CrossDomainMessagePair[]> => {
if (typeof l1RpcProvider === 'string') {
l1RpcProvider = new ethers.providers.JsonRpcProvider(l1RpcProvider)
}
if (typeof l2RpcProvider === 'string') {
l2RpcProvider = new ethers.providers.JsonRpcProvider(l2RpcProvider)
}
const l2Transaction = await l2RpcProvider.getTransaction(l2TransactionHash) const l2Transaction = await l2RpcProvider.getTransaction(l2TransactionHash)
if (l2Transaction === null) { if (l2Transaction === null) {
throw new Error(`unable to find tx with hash: ${l2TransactionHash}`) throw new Error(`unable to find tx with hash: ${l2TransactionHash}`)
......
...@@ -6,7 +6,6 @@ import { MerkleTree } from 'merkletreejs' ...@@ -6,7 +6,6 @@ import { MerkleTree } from 'merkletreejs'
/* Imports: Internal */ /* Imports: Internal */
import { fromHexString, sleep } from '@eth-optimism/core-utils' import { fromHexString, sleep } from '@eth-optimism/core-utils'
import { BaseService } from '@eth-optimism/common-ts' import { BaseService } from '@eth-optimism/common-ts'
import SpreadSheet from './spreadsheet'
import { import {
loadContract, loadContract,
...@@ -45,10 +44,6 @@ interface MessageRelayerOptions { ...@@ -45,10 +44,6 @@ interface MessageRelayerOptions {
// Number of blocks within each getLogs query - max is 2000 // Number of blocks within each getLogs query - max is 2000
getLogsInterval?: number getLogsInterval?: number
// Append txs to a spreadsheet instead of submitting transactions
spreadsheetMode?: boolean
spreadsheet?: SpreadSheet
} }
const optionSettings = { const optionSettings = {
...@@ -58,7 +53,6 @@ const optionSettings = { ...@@ -58,7 +53,6 @@ const optionSettings = {
l2BlockOffset: { default: 1 }, l2BlockOffset: { default: 1 },
l1StartOffset: { default: 0 }, l1StartOffset: { default: 0 },
getLogsInterval: { default: 2000 }, getLogsInterval: { default: 2000 },
spreadsheetMode: { default: false },
} }
export class MessageRelayerService extends BaseService<MessageRelayerOptions> { export class MessageRelayerService extends BaseService<MessageRelayerOptions> {
...@@ -66,9 +60,6 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> { ...@@ -66,9 +60,6 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> {
super('Message_Relayer', options, optionSettings) super('Message_Relayer', options, optionSettings)
} }
protected spreadsheetMode: boolean
protected spreadsheet: SpreadSheet
private state: { private state: {
lastFinalizedTxHeight: number lastFinalizedTxHeight: number
nextUnfinalizedTxHeight: number nextUnfinalizedTxHeight: number
...@@ -88,7 +79,6 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> { ...@@ -88,7 +79,6 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> {
pollingInterval: this.options.pollingInterval, pollingInterval: this.options.pollingInterval,
l2BlockOffset: this.options.l2BlockOffset, l2BlockOffset: this.options.l2BlockOffset,
getLogsInterval: this.options.getLogsInterval, getLogsInterval: this.options.getLogsInterval,
spreadSheetMode: this.options.spreadsheetMode,
}) })
// Need to improve this, sorry. // Need to improve this, sorry.
this.state = {} as any this.state = {} as any
...@@ -145,10 +135,6 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> { ...@@ -145,10 +135,6 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> {
this.logger.info('Connected to all contracts.') this.logger.info('Connected to all contracts.')
if (this.options.spreadsheetMode) {
this.logger.info('Running in spreadsheet mode')
}
this.state.lastQueriedL1Block = this.options.l1StartOffset this.state.lastQueriedL1Block = this.options.l1StartOffset
this.state.eventCache = [] this.state.eventCache = []
...@@ -498,67 +484,12 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> { ...@@ -498,67 +484,12 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> {
message: SentMessage, message: SentMessage,
proof: SentMessageProof proof: SentMessageProof
): Promise<void> { ): Promise<void> {
if (this.options.spreadsheetMode) { try {
try { this.logger.info('Dry-run, checking to make sure proof would succeed...')
await this.options.spreadsheet.addRow({
target: message.target,
sender: message.sender,
message: message.message,
messageNonce: message.messageNonce.toString(),
encodedMessage: message.encodedMessage,
encodedMessageHash: message.encodedMessageHash,
parentTransactionIndex: message.parentTransactionIndex,
parentTransactionHash: message.parentTransactionIndex,
stateRoot: proof.stateRoot,
batchIndex: proof.stateRootBatchHeader.batchIndex.toString(),
batchRoot: proof.stateRootBatchHeader.batchRoot,
batchSize: proof.stateRootBatchHeader.batchSize.toString(),
prevTotalElements: proof.stateRootBatchHeader.prevTotalElements.toString(),
extraData: proof.stateRootBatchHeader.extraData,
index: proof.stateRootProof.index,
siblings: proof.stateRootProof.siblings.join(','),
stateTrieWitness: proof.stateTrieWitness.toString('hex'),
storageTrieWitness: proof.storageTrieWitness.toString('hex'),
})
this.logger.info('Submitted relay message to spreadsheet')
} catch (e) {
this.logger.error('Cannot submit message to spreadsheet')
this.logger.error(e.message)
}
} else {
try {
this.logger.info(
'Dry-run, checking to make sure proof would succeed...'
)
await this.state.OVM_L1CrossDomainMessenger.connect(
this.options.l1Wallet
).callStatic.relayMessage(
message.target,
message.sender,
message.message,
message.messageNonce,
proof,
{
gasLimit: this.options.relayGasLimit,
}
)
this.logger.info( await this.state.OVM_L1CrossDomainMessenger.connect(
'Proof should succeed. Submitting for real this time...'
)
} catch (err) {
this.logger.error('Proof would fail, skipping', {
message: err.toString(),
stack: err.stack,
code: err.code,
})
return
}
const result = await this.state.OVM_L1CrossDomainMessenger.connect(
this.options.l1Wallet this.options.l1Wallet
).relayMessage( ).callStatic.relayMessage(
message.target, message.target,
message.sender, message.sender,
message.message, message.message,
...@@ -569,29 +500,51 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> { ...@@ -569,29 +500,51 @@ export class MessageRelayerService extends BaseService<MessageRelayerOptions> {
} }
) )
this.logger.info('Relay message transaction sent', { this.logger.info('Proof should succeed. Submitting for real this time...')
transactionHash: result, } catch (err) {
this.logger.error('Proof would fail, skipping', {
message: err.toString(),
stack: err.stack,
code: err.code,
}) })
return
}
try { const result = await this.state.OVM_L1CrossDomainMessenger.connect(
const receipt = await result.wait() this.options.l1Wallet
).relayMessage(
this.logger.info('Relay message included in block', { message.target,
transactionHash: receipt.transactionHash, message.sender,
blockNumber: receipt.blockNumber, message.message,
gasUsed: receipt.gasUsed.toString(), message.messageNonce,
confirmations: receipt.confirmations, proof,
status: receipt.status, {
}) gasLimit: this.options.relayGasLimit,
} catch (err) {
this.logger.error('Real relay attempt failed, skipping.', {
message: err.toString(),
stack: err.stack,
code: err.code,
})
return
} }
this.logger.info('Message successfully relayed to Layer 1!') )
this.logger.info('Relay message transaction sent', {
transactionHash: result,
})
try {
const receipt = await result.wait()
this.logger.info('Relay message included in block', {
transactionHash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed.toString(),
confirmations: receipt.confirmations,
status: receipt.status,
})
} catch (err) {
this.logger.error('Real relay attempt failed, skipping.', {
message: err.toString(),
stack: err.stack,
code: err.code,
})
return
} }
this.logger.info('Message successfully relayed to Layer 1!')
} }
} }
import { GoogleSpreadsheet } from 'google-spreadsheet'
export default class SpreadSheet {
public doc
public sheet
constructor(id) {
this.doc = new GoogleSpreadsheet(id)
this.sheet = null
}
async init(email, privateKey) {
await this.doc.useServiceAccountAuth({
client_email: email,
private_key: privateKey,
})
await this.doc.loadInfo()
this.sheet = this.doc.sheetsByIndex[0]
}
async addRow(row) {
return this.sheet.addRow(row)
}
}
...@@ -2960,7 +2960,7 @@ arrify@^1.0.0, arrify@^1.0.1: ...@@ -2960,7 +2960,7 @@ arrify@^1.0.0, arrify@^1.0.1:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
arrify@^2.0.0, arrify@^2.0.1: arrify@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
...@@ -3608,7 +3608,7 @@ base-x@^3.0.2, base-x@^3.0.8: ...@@ -3608,7 +3608,7 @@ base-x@^3.0.2, base-x@^3.0.8:
dependencies: dependencies:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
base64-js@^1.3.0, base64-js@^1.3.1: base64-js@^1.3.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
...@@ -3901,11 +3901,6 @@ bsert@~0.0.10: ...@@ -3901,11 +3901,6 @@ bsert@~0.0.10:
resolved "https://registry.yarnpkg.com/bsert/-/bsert-0.0.10.tgz#231ac82873a1418c6ade301ab5cd9ae385895597" resolved "https://registry.yarnpkg.com/bsert/-/bsert-0.0.10.tgz#231ac82873a1418c6ade301ab5cd9ae385895597"
integrity sha512-NHNwlac+WPy4t2LoNh8pXk8uaIGH3NSaIUbTTRXGpE2WEbq0te/tDykYHkFK57YKLPjv/aGHmbqvnGeVWDz57Q== integrity sha512-NHNwlac+WPy4t2LoNh8pXk8uaIGH3NSaIUbTTRXGpE2WEbq0te/tDykYHkFK57YKLPjv/aGHmbqvnGeVWDz57Q==
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer-from@^1.0.0, buffer-from@^1.1.0: buffer-from@^1.0.0, buffer-from@^1.1.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
...@@ -5241,13 +5236,6 @@ ecc-jsbn@~0.1.1: ...@@ -5241,13 +5236,6 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0" jsbn "~0.1.0"
safer-buffer "^2.1.0" safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1: ee-first@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
...@@ -6213,7 +6201,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: ...@@ -6213,7 +6201,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0" assign-symbols "^1.0.0"
is-extendable "^1.0.1" is-extendable "^1.0.1"
extend@^3.0.2, extend@~3.0.2: extend@~3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
...@@ -6305,11 +6293,6 @@ fast-safe-stringify@^2.0.7: ...@@ -6305,11 +6293,6 @@ fast-safe-stringify@^2.0.7:
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
fast-text-encoding@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53"
integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==
fastq@^1.6.0: fastq@^1.6.0:
version "1.11.0" version "1.11.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858"
...@@ -6707,25 +6690,6 @@ gauge@~2.7.3: ...@@ -6707,25 +6690,6 @@ gauge@~2.7.3:
strip-ansi "^3.0.1" strip-ansi "^3.0.1"
wide-align "^1.1.0" wide-align "^1.1.0"
gaxios@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.2.0.tgz#33bdc4fc241fc33b8915a4b8c07cfb368b932e46"
integrity sha512-Ms7fNifGv0XVU+6eIyL9LB7RVESeML9+cMvkwGS70xyD6w2Z80wl6RiqiJ9k1KFlJCUTQqFFc8tXmPQfSKUe8g==
dependencies:
abort-controller "^3.0.0"
extend "^3.0.2"
https-proxy-agent "^5.0.0"
is-stream "^2.0.0"
node-fetch "^2.3.0"
gcp-metadata@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-4.2.1.tgz#31849fbcf9025ef34c2297c32a89a1e7e9f2cd62"
integrity sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==
dependencies:
gaxios "^4.0.0"
json-bigint "^1.0.0"
get-caller-file@^1.0.1: get-caller-file@^1.0.1:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
...@@ -6978,37 +6942,6 @@ globby@^11.0.0, globby@^11.0.2: ...@@ -6978,37 +6942,6 @@ globby@^11.0.0, globby@^11.0.2:
merge2 "^1.3.0" merge2 "^1.3.0"
slash "^3.0.0" slash "^3.0.0"
google-auth-library@^6.1.3:
version "6.1.6"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-6.1.6.tgz#deacdcdb883d9ed6bac78bb5d79a078877fdf572"
integrity sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==
dependencies:
arrify "^2.0.0"
base64-js "^1.3.0"
ecdsa-sig-formatter "^1.0.11"
fast-text-encoding "^1.0.0"
gaxios "^4.0.0"
gcp-metadata "^4.2.0"
gtoken "^5.0.4"
jws "^4.0.0"
lru-cache "^6.0.0"
google-p12-pem@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.0.3.tgz#673ac3a75d3903a87f05878f3c75e06fc151669e"
integrity sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==
dependencies:
node-forge "^0.10.0"
google-spreadsheet@^3.1.15:
version "3.1.15"
resolved "https://registry.yarnpkg.com/google-spreadsheet/-/google-spreadsheet-3.1.15.tgz#e7a86f750d8166faaa3e16929561baceb807bf5a"
integrity sha512-S5477f3Gf3Mz6AXgCw7dbaYnzu5aHou1AX4sDqrGboQWnAytkxqJGKQiXN+zzRTTcYzSTJCe0g7KqCPZO9xiOw==
dependencies:
axios "^0.21.1"
google-auth-library "^6.1.3"
lodash "^4.17.20"
got@9.6.0: got@9.6.0:
version "9.6.0" version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
...@@ -7061,15 +6994,6 @@ growl@1.10.5: ...@@ -7061,15 +6994,6 @@ growl@1.10.5:
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
gtoken@^5.0.4:
version "5.2.1"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.2.1.tgz#4dae1fea17270f457954b4a45234bba5fc796d16"
integrity sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==
dependencies:
gaxios "^4.0.0"
google-p12-pem "^3.0.3"
jws "^4.0.0"
handlebars@^4.0.1, handlebars@^4.7.6: handlebars@^4.0.1, handlebars@^4.7.6:
version "4.7.7" version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
...@@ -8114,13 +8038,6 @@ jsesc@~0.5.0: ...@@ -8114,13 +8038,6 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"
json-buffer@3.0.0: json-buffer@3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
...@@ -8254,23 +8171,6 @@ just-extend@^4.0.2: ...@@ -8254,23 +8171,6 @@ just-extend@^4.0.2:
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744"
integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==
jwa@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
dependencies:
jwa "^2.0.0"
safe-buffer "^5.0.1"
keccak@3.0.1, keccak@^3.0.0: keccak@3.0.1, keccak@^3.0.0:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff"
...@@ -9745,7 +9645,7 @@ node-fetch@2.1.2: ...@@ -9745,7 +9645,7 @@ node-fetch@2.1.2:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: node-fetch@^2.6.0, node-fetch@^2.6.1:
version "2.6.1" version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
...@@ -9758,11 +9658,6 @@ node-fetch@~1.7.1: ...@@ -9758,11 +9658,6 @@ node-fetch@~1.7.1:
encoding "^0.1.11" encoding "^0.1.11"
is-stream "^1.0.1" is-stream "^1.0.1"
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
node-gyp-build@^4.2.0: node-gyp-build@^4.2.0:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
......
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