Commit 1efebf14 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge pull request #2254 from cfromknecht/remove-ts-batch-submitter

feat: remove packages/batch-submitter
parents 66df4cd4 6b0d57aa
......@@ -20,9 +20,6 @@ jobs:
- 5000:5000
strategy:
matrix:
batch-submitter:
- ts-batch-submitter
- go-batch-submitter
batch-type:
- zlib
- legacy
......@@ -53,7 +50,7 @@ jobs:
working-directory: ./ops
run: |
./scripts/stats.sh &
docker-compose -f docker-compose.yml -f docker-compose.${{ matrix.batch-submitter }}.yml up -d
docker-compose -f docker-compose.yml up -d
- name: Wait for the Sequencer node
working-directory: ./ops
......
......@@ -113,12 +113,6 @@ jobs:
fail_ci_if_error: false
verbose: true
flags: core-utils
- uses: codecov/codecov-action@v1
with:
files: ./packages/batch-submitter/coverage.json
fail_ci_if_error: false
verbose: true
flags: batch-submitter
- uses: codecov/codecov-action@v1
with:
files: ./packages/data-transport-layer/coverage.json
......@@ -170,10 +164,6 @@ jobs:
# if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install
- name: Check packages/batch-submitter
working-directory: ./packages/batch-submitter
run: npx depcheck
- name: Check packages/contracts
working-directory: ./packages/contracts
run: npx depcheck
......
......@@ -116,7 +116,7 @@ This will build the following containers:
* [`l2geth`](https://hub.docker.com/r/ethereumoptimism/l2geth): L2 geth node running in Sequencer mode
* [`verifier`](https://hub.docker.com/r/ethereumoptimism/go-ethereum): L2 geth node running in Verifier mode
* [`relayer`](https://hub.docker.com/r/ethereumoptimism/message-relayer): helper process that relays messages between L1 and L2
* [`batch_submitter`](https://hub.docker.com/r/ethereumoptimism/batch-submitter): service that submits batches of Sequencer transactions to the L1 chain
* [`batch_submitter`](https://hub.docker.com/r/ethereumoptimism/batch-submitter-service): service that submits batches of Sequencer transactions to the L1 chain
* [`integration_tests`](https://hub.docker.com/r/ethereumoptimism/integration-tests): integration tests in a box
If you want to make a change to a container, you'll need to take it down and rebuild it.
......
......@@ -35,7 +35,6 @@ root
│ ├── <a href="./packages/core-utils">core-utils</a>: Low-level utilities that make building Optimism easier
│ ├── <a href="./packages/common-ts">common-ts</a>: Common tools for building apps in TypeScript
│ ├── <a href="./packages/data-transport-layer">data-transport-layer</a>: Service for indexing Optimism-related L1 data
│ ├── <a href="./packages/batch-submitter">batch-submitter</a>: Service for submitting batches of transactions and results to L1
│ ├── <a href="./packages/message-relayer">message-relayer</a>: Tool for automatically relaying L1<>L2 messages in development
│ └── <a href="./packages/replica-healthcheck">replica-healthcheck</a>: Service for monitoring the health of a replica node
├── <a href="./go">go</a>
......
BATCH_SUBMITTER ?= docker-compose.ts-batch-submitter.yml
DOCKER_COMPOSE_CMD := docker-compose \
-f docker-compose.yml \
-f $(BATCH_SUBMITTER)
-f docker-compose.yml
build:
DOCKER_BUILDKIT=1 \
......
......@@ -26,7 +26,6 @@ The base stack can be started and stopped with a command like this:
```
docker-compose \
-f docker-compose.yml \
-f docker-compose.ts-batch-submitter.yml \
up --build --detach
```
......@@ -40,7 +39,6 @@ To start the stack with monitoring enabled, just add the metric composition file
```
docker-compose \
-f docker-compose.yml \
-f docker-compose.ts-batch-submitter.yml \
-f docker-compose-metrics.yml \
up --build --detach
```
......@@ -50,7 +48,6 @@ Optionally, run a verifier along the rest of the stack. Run a replica with the s
```
docker-compose
-f docker-compose.yml \
-f docker-compose.ts-batch-submitter.yml \
up --scale \
verifier=1 \
--build --detach
......@@ -71,24 +68,6 @@ Fees can be turned off at runtime by setting the environment variable
ROLLUP_ENFORCE_FEES=false docker-compose up
```
## Using the Go Batch Submitter
The existing Typescript batch submitter is in the process of being reimplemented
in Go. During this transition, the user is required to specify which batch
submitter to use with docker-compose.
The commands above all use the Typescript batch submitter, by specifying
`-f docker-compose.ts-batch-submitter.yml`. This can be swapped out for the go
batch submitter by supplying `-f docker-compose.go-batch-submitter.yml` instead.
Additionally, the `make` targets assume the use of the Typescript batch
submitter. This can be overridden by setting the `BATCH_SUBMITTER` environment
variable, e.g. `BATCH_SUBMITTER=docker-compose.go-batch-submitter.yml make up`.
Once the transition is complete, specifying the desired batch submitter will be
obsolete, and the Go batch submitter will be selected by default from the
`docker-compose.yml` file and `Makefile`.
## Cross domain communication
By default, the `message-relayer` service is turned off. This means that
......
......@@ -85,16 +85,17 @@ services:
- l1_chain
- deployer
- l2geth
image: ethereumoptimism/batch-submitter:${DOCKER_TAG:-prerelease-0.5.0-rc-7-ee217ce}
entrypoint: ./batches.sh
image: ethereumoptimism/batch-submitter-service:${DOCKER_TAG:-prerelease-0.5.0-rc-7-ee217ce}
entrypoint: ./batch-submitter.sh
env_file:
- ./envs/batches.env
- ./envs/batch-submitter.env
environment:
L1_NODE_WEB3_URL: http://l1_chain:8545
L2_NODE_WEB3_URL: http://l2geth:8545
URL: http://deployer:8081/addresses.json
SEQUENCER_PRIVATE_KEY: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
PROPOSER_PRIVATE_KEY: "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
L1_ETH_RPC: http://l1_chain:8545
L2_ETH_RPC: http://l2geth:8545
URL: http://deployer:8081/addresses.json
BATCH_SUBMITTER_SEQUENCER_PRIVATE_KEY: '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'
BATCH_SUBMITTER_PROPOSER_PRIVATE_KEY: '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a'
BATCH_SUBMITTER_SEQUENCER_BATCH_TYPE: ${BATCH_SUBMITTER_SEQUENCER_BATCH_TYPE:-zlib}
verifier:
depends_on:
......
services:
batch_submitter:
depends_on:
- l1_chain
- deployer
- l2geth
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.batch-submitter-service
entrypoint: ./batch-submitter.sh
env_file:
- ./envs/batch-submitter.env
environment:
L1_ETH_RPC: http://l1_chain:8545
L2_ETH_RPC: http://l2geth:8545
URL: http://deployer:8081/addresses.json
BATCH_SUBMITTER_SEQUENCER_PRIVATE_KEY: '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'
BATCH_SUBMITTER_PROPOSER_PRIVATE_KEY: '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a'
BATCH_SUBMITTER_SEQUENCER_BATCH_TYPE: ${BATCH_SUBMITTER_SEQUENCER_BATCH_TYPE:-zlib}
services:
batch_submitter:
depends_on:
- l1_chain
- deployer
- l2geth
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.packages
target: batch-submitter
entrypoint: ./batches.sh
env_file:
- ./envs/batches.env
environment:
L1_NODE_WEB3_URL: http://l1_chain:8545
L2_NODE_WEB3_URL: http://l2geth:8545
URL: http://deployer:8081/addresses.json
SEQUENCER_PRIVATE_KEY: '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'
PROPOSER_PRIVATE_KEY: '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a'
BATCH_SUBMITTER_SEQUENCER_BATCH_TYPE: ${BATCH_SUBMITTER_SEQUENCER_BATCH_TYPE:-zlib}
......@@ -212,3 +212,22 @@ services:
GAS_PRICE_ORACLE_ETHEREUM_HTTP_URL: http://l2geth:8545
# Default hardhat account 5
GAS_PRICE_ORACLE_PRIVATE_KEY: '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba'
batch_submitter:
depends_on:
- l1_chain
- deployer
- l2geth
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.batch-submitter-service
entrypoint: ./batch-submitter.sh
env_file:
- ./envs/batch-submitter.env
environment:
L1_ETH_RPC: http://l1_chain:8545
L2_ETH_RPC: http://l2geth:8545
URL: http://deployer:8081/addresses.json
BATCH_SUBMITTER_SEQUENCER_PRIVATE_KEY: '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'
BATCH_SUBMITTER_PROPOSER_PRIVATE_KEY: '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a'
BATCH_SUBMITTER_SEQUENCER_BATCH_TYPE: ${BATCH_SUBMITTER_SEQUENCER_BATCH_TYPE:-zlib}
ARG LOCAL_REGISTRY=docker.io
ARG BUILDER_TAG=latest
FROM ${LOCAL_REGISTRY}/ethereumoptimism/builder:${BUILDER_TAG} AS builder
FROM node:16-alpine
RUN apk add --no-cache curl bash jq
WORKDIR /opt/optimism
# copy top level files
COPY --from=builder /optimism/*.json /optimism/yarn.lock ./
COPY --from=builder /optimism/node_modules ./node_modules
# copy deps (would have been nice if docker followed the symlinks required)
COPY --from=builder /optimism/packages/core-utils/package.json ./packages/core-utils/package.json
COPY --from=builder /optimism/packages/core-utils/dist ./packages/core-utils/dist
COPY --from=builder /optimism/packages/common-ts/package.json ./packages/common-ts/package.json
COPY --from=builder /optimism/packages/common-ts/dist ./packages/common-ts/dist
COPY --from=builder /optimism/packages/contracts/package.json ./packages/contracts/package.json
COPY --from=builder /optimism/packages/contracts/deployments ./packages/contracts/deployments
COPY --from=builder /optimism/packages/contracts/dist ./packages/contracts/dist
COPY --from=builder /optimism/packages/contracts/artifacts ./packages/contracts/artifacts
# copy the service
WORKDIR /opt/optimism/packages/batch-submitter
COPY --from=builder /optimism/packages/batch-submitter/package.json ./
COPY --from=builder /optimism/packages/batch-submitter/dist ./dist
COPY --from=builder /optimism/packages/batch-submitter/exec ./exec
COPY --from=builder /optimism/packages/batch-submitter/node_modules ./node_modules
# copy this over in case you want to run alongside other services
COPY ./ops/scripts/batches.sh .
ENTRYPOINT ["npm", "run", "start"]
......@@ -29,7 +29,6 @@ COPY packages/core-utils/package.json ./packages/core-utils/package.json
COPY packages/common-ts/package.json ./packages/common-ts/package.json
COPY packages/contracts/package.json ./packages/contracts/package.json
COPY packages/data-transport-layer/package.json ./packages/data-transport-layer/package.json
COPY packages/batch-submitter/package.json ./packages/batch-submitter/package.json
COPY packages/message-relayer/package.json ./packages/message-relayer/package.json
COPY packages/replica-healthcheck/package.json ./packages/replica-healthcheck/package.json
COPY integration-tests/package.json ./integration-tests/package.json
......
......@@ -18,7 +18,6 @@ COPY packages/core-utils/package.json ./packages/core-utils/package.json
COPY packages/common-ts/package.json ./packages/common-ts/package.json
COPY packages/contracts/package.json ./packages/contracts/package.json
COPY packages/data-transport-layer/package.json ./packages/data-transport-layer/package.json
COPY packages/batch-submitter/package.json ./packages/batch-submitter/package.json
COPY packages/message-relayer/package.json ./packages/message-relayer/package.json
COPY packages/replica-healthcheck/package.json ./packages/replica-healthcheck/package.json
COPY integration-tests/package.json ./integration-tests/package.json
......@@ -38,12 +37,6 @@ COPY ./ops/scripts/deployer.sh .
CMD ["yarn", "run", "deploy"]
FROM base as batch-submitter
WORKDIR /opt/optimism/packages/batch-submitter
COPY ./ops/scripts/batches.sh .
CMD ["npm", "run", "start"]
FROM base as data-transport-layer
WORKDIR /opt/optimism/packages/data-transport-layer
COPY ./ops/scripts/dtl.sh .
......
ADDRESS_MANAGER_ADDRESS=
DEBUG=info*,error*,warn*,debug*
MAX_L1_TX_SIZE=90000
MIN_L1_TX_SIZE=32
MAX_TX_BATCH_COUNT=50
MAX_STATE_BATCH_COUNT=50
POLL_INTERVAL=500
NUM_CONFIRMATIONS=0
RESUBMISSION_TIMEOUT=1
FINALITY_CONFIRMATIONS=0
RUN_TX_BATCH_SUBMITTER=true
RUN_STATE_BATCH_SUBMITTER=true
MAX_BATCH_SUBMISSION_TIME=0
SAFE_MINIMUM_ETHER_BALANCE=0
CLEAR_PENDING_TXS=false
RETRIES=80
#!/bin/bash
set -e
RETRIES=${RETRIES:-40}
if [[ ! -z "$URL" ]]; then
# get the addrs from the URL provided
ADDRESSES=$(curl --fail --show-error --silent --retry-connrefused --retry $RETRIES --retry-delay 5 $URL)
# set the env
export ADDRESS_MANAGER_ADDRESS=$(echo $ADDRESSES | jq -r '.AddressManager')
fi
# waits for l2geth to be up
curl --fail \
--show-error \
--silent \
--retry-connrefused \
--retry $RETRIES \
--retry-delay 1 \
--output /dev/null \
$L2_NODE_WEB3_URL
# go
exec node ./exec/run-batch-submitter.js
......@@ -43,7 +43,6 @@ docker tag localhost:5000/ethereumoptimism/builder:latest ethereumoptimism/build
build deployer "ethereumoptimism/deployer:latest" "./ops/docker/Dockerfile.deployer" .
build dtl "ethereumoptimism/data-transport-layer:latest" "./ops/docker/Dockerfile.data-transport-layer" .
build batch_submitter "ethereumoptimism/batch-submitter:latest" "./ops/docker/Dockerfile.batch-submitter" .
build relayer "ethereumoptimism/message-relayer:latest" "./ops/docker/Dockerfile.message-relayer" .
build integration-tests "ethereumoptimism/integration-tests:latest" "./ops/docker/Dockerfile.integration-tests" .
......
# Environment
NODE_ENV=development
# Leave blank during local development
ETH_NETWORK_NAME=
# Logging & monitoring
DEBUG=info*,error*,warn*,debug*
RUN_METRICS_SERVER=
METRICS_PORT=
METRICS_HOSTNAME=
# Leave the SENTRY_DSN variable unset during local development
SENTRY_DSN=
SENTRY_TRACE_RATE=
USE_SENTRY=
L1_NODE_WEB3_URL=http://localhost:9545
L2_NODE_WEB3_URL=http://localhost:8545
MAX_L1_TX_SIZE=90000
MIN_L1_TX_SIZE=32
MAX_TX_BATCH_SIZE=50
MAX_STATE_BATCH_COUNT=2000
MAX_TX_BATCH_COUNT=250
MAX_BATCH_SUBMISSION_TIME=0
POLL_INTERVAL=15000
NUM_CONFIRMATIONS=0
RESUBMISSION_TIMEOUT=300 # in seconds
FINALITY_CONFIRMATIONS=0
RUN_TX_BATCH_SUBMITTER=true
RUN_STATE_BATCH_SUBMITTER=true
SAFE_MINIMUM_ETHER_BALANCE=0
CLEAR_PENDING_TXS=false
ADDRESS_MANAGER_ADDRESS=
USE_HARDHAT=
DEBUG_IMPERSONATE_SEQUENCER_ADDRESS=
DEBUG_IMPERSONATE_PROPOSER_ADDRESS=
# Optional gas settings
MAX_GAS_PRICE_IN_GWEI=200
GAS_RETRY_INCREMENT=5
GAS_THRESHOLD_IN_GWEI=100
SEQUENCER_PRIVATE_KEY=0xd2ab07f7c10ac88d5f86f1b4c1035d5195e81f27dbe62ad65e59cbf88205629b
module.exports = {
extends: '../../.eslintrc.js',
}
module.exports = {
...require('../../.prettierrc.js'),
};
\ No newline at end of file
# Changelog
## 0.4.20
### Patch Changes
- d3d70291: Use asL2Provider instead of injectL2Context in bss and healthcheck service.
- Updated dependencies [f37c283c]
- Updated dependencies [3f4d3c13]
- Updated dependencies [0b4453f7]
- Updated dependencies [78298782]
- Updated dependencies [0c54e60e]
- @eth-optimism/sdk@0.2.3
- @eth-optimism/core-utils@0.8.0
- @eth-optimism/contracts@0.5.15
## 0.4.19
### Patch Changes
- Updated dependencies [b4165299]
- Updated dependencies [3c2acd91]
- @eth-optimism/core-utils@0.7.7
- @eth-optimism/contracts@0.5.14
## 0.4.18
### Patch Changes
- Updated dependencies [438bc78a]
- @eth-optimism/contracts@0.5.13
## 0.4.17
### Patch Changes
- fcce5b67: Updates batch submitter to also include separate timestamps for deposit transactions"
- ba14c59d: Updates various ethers dependencies to their latest versions
- Updated dependencies [ba14c59d]
- @eth-optimism/contracts@0.5.12
- @eth-optimism/core-utils@0.7.6
## 0.4.16
### Patch Changes
- Updated dependencies [e631c39c]
- @eth-optimism/contracts@0.5.11
## 0.4.15
### Patch Changes
- ae4a90d9: Adds a fix for the BSS to account for new timestamp logic in L2Geth
- ca547c4e: Import performance to not couple batch submitter to version of nodejs that has performance as a builtin
- Updated dependencies [ad94b9d1]
- @eth-optimism/core-utils@0.7.5
- @eth-optimism/contracts@0.5.10
## 0.4.14
### Patch Changes
- Updated dependencies [ba96a455]
- Updated dependencies [c3e85fef]
- @eth-optimism/core-utils@0.7.4
- @eth-optimism/contracts@0.5.9
## 0.4.13
### Patch Changes
- 9fe09f70: Properly clear state root batch txs on startup
- Updated dependencies [b3efb8b7]
- Updated dependencies [279603e5]
- Updated dependencies [b6040bb3]
- @eth-optimism/contracts@0.5.8
## 0.4.12
### Patch Changes
- 07f1ad01: Fix the numTxPerBatch metric
## 0.4.11
### Patch Changes
- 3eb5590b: adds batchTxBuildTime gauge
- Updated dependencies [b6f89fad]
- @eth-optimism/contracts@0.5.7
## 0.4.10
### Patch Changes
- Updated dependencies [bbd42e03]
- Updated dependencies [453f0774]
- @eth-optimism/contracts@0.5.6
## 0.4.9
### Patch Changes
- 57d5b8f9: Build docker images with node.js version 16
## 0.4.8
### Patch Changes
- 0ab37fc9: Update to node.js version 16
## 0.4.7
### Patch Changes
- Updated dependencies [584cbc25]
- @eth-optimism/core-utils@0.7.3
- @eth-optimism/contracts@0.5.5
## 0.4.6
### Patch Changes
- 39607e7c: Trigger release of the batch submitter with yatm retry fix
## 0.4.5
### Patch Changes
- 85f68bd3: Immediately reject on nonce errors to stop falling behind
## 0.4.4
### Patch Changes
- 8e634b49: Fix package JSON issues
- Updated dependencies [8e634b49]
- @eth-optimism/core-utils@0.7.2
- @eth-optimism/contracts@0.5.4
## 0.4.3
### Patch Changes
- Updated dependencies [b9049406]
- Updated dependencies [a8b14a7d]
- @eth-optimism/contracts@0.5.3
## 0.4.2
### Patch Changes
- 526d7e51: Throw an error when sequencer and proposer have the same address.
- 243f33e5: Standardize package json file format
- Updated dependencies [243f33e5]
- @eth-optimism/common-ts@0.2.1
- @eth-optimism/contracts@0.5.2
- @eth-optimism/core-utils@0.7.1
## 0.4.1
### Patch Changes
- Updated dependencies [c0fc7fee]
- @eth-optimism/contracts@0.5.1
## 0.4.0
### Minor Changes
- 3f590e33: Remove the "OVM" Prefix from contract names
- 81ccd6e4: `regenesis/0.5.0` release
### Patch Changes
- ac63235a: Default tx batch validation to false
- bfeb7fba: Add `VALIDATE_TX_BATCH` config option that can disable batch validation
- 222a3eef: Add 'User-Agent' to the http headers for ethers providers
- 71f8de9c: Handle error case more explicity when creating invalid batch
- 970f421e: Ensure empty batches are not submitted
- b70ee70c: upgraded to solidity 0.8.9
- a98a1884: Fixes dependencies instead of using caret constraints
- Updated dependencies [e4a1129c]
- Updated dependencies [64ea3ac9]
- Updated dependencies [3ce62c81]
- Updated dependencies [299a459e]
- Updated dependencies [8c8807c0]
- Updated dependencies [d7978cfc]
- Updated dependencies [e16d41c0]
- Updated dependencies [5db50b3d]
- Updated dependencies [cee2a464]
- Updated dependencies [66bf56a6]
- Updated dependencies [2c91ca00]
- Updated dependencies [d5036826]
- Updated dependencies [222a3eef]
- Updated dependencies [dfc784e8]
- Updated dependencies [896168e2]
- Updated dependencies [436c48fd]
- Updated dependencies [7c352b1e]
- Updated dependencies [2ade9a79]
- Updated dependencies [0272a536]
- Updated dependencies [6ee7423f]
- Updated dependencies [3f590e33]
- Updated dependencies [e20deca0]
- Updated dependencies [2a731e0d]
- Updated dependencies [199e895e]
- Updated dependencies [872f5976]
- Updated dependencies [9c1443a4]
- Updated dependencies [26906518]
- Updated dependencies [c53b3587]
- Updated dependencies [1b917041]
- Updated dependencies [483f561b]
- Updated dependencies [b70ee70c]
- Updated dependencies [c38e4b57]
- Updated dependencies [a98a1884]
- Updated dependencies [b744b6ea]
- Updated dependencies [20c8969b]
- Updated dependencies [d2eb8ae0]
- Updated dependencies [1e63ffa0]
- Updated dependencies [ff266e9c]
- Updated dependencies [b56dd079]
- Updated dependencies [56fe3793]
- Updated dependencies [3e2aa16a]
- Updated dependencies [d3cb1b86]
- Updated dependencies [3e41df63]
- Updated dependencies [973589da]
- Updated dependencies [83a449c4]
- Updated dependencies [9c63e9bd]
- Updated dependencies [81ccd6e4]
- Updated dependencies [f38b8000]
- Updated dependencies [d5f012ab]
- Updated dependencies [76c84f21]
- Updated dependencies [6d32d701]
- Updated dependencies [3605b963]
- Updated dependencies [3f28385a]
- Updated dependencies [280f348c]
- Updated dependencies [a0947c3f]
- Updated dependencies [51821d8f]
- Updated dependencies [29f1c228]
- Updated dependencies [8f4cb337]
- Updated dependencies [beb6c977]
- Updated dependencies [33abe73d]
- Updated dependencies [71de86d6]
- @eth-optimism/contracts@0.5.0
- @eth-optimism/core-utils@0.7.0
- @eth-optimism/common-ts@0.2.0
## 0.3.15
### Patch Changes
- 6d3e1d7f: Update dependencies
- Updated dependencies [6d3e1d7f]
- Updated dependencies [2e929aa9]
- @eth-optimism/common-ts@0.1.6
- @eth-optimism/contracts@0.4.14
- @eth-optimism/core-utils@0.6.1
## 0.3.14
### Patch Changes
- 39cea8fd: Removes the call to `appendQueueBatch` from the batch submitter
- Updated dependencies [e0be02e1]
- Updated dependencies [7f7f35c3]
- Updated dependencies [8da04505]
- @eth-optimism/core-utils@0.6.0
- @eth-optimism/contracts@0.4.13
## 0.3.13
### Patch Changes
- 7482d09c: Fixes a bug in the batch submitted that would cause it to submit transactions with increasing nonces
## 0.3.12
### Patch Changes
- 21b17edd: Added coverage for packages
- 78ca518b: Add loglines for eip1559 related fields before sending a transaction
- Updated dependencies [888dafca]
- Updated dependencies [eb0854e7]
- Updated dependencies [21b17edd]
- Updated dependencies [dfe3598f]
- @eth-optimism/contracts@0.4.11
- @eth-optimism/core-utils@0.5.5
## 0.3.11
### Patch Changes
- 918c08ca: Bump ethers dependency to 5.4.x to support eip1559
- Updated dependencies [918c08ca]
- @eth-optimism/contracts@0.4.10
- @eth-optimism/core-utils@0.5.2
## 0.3.10
### Patch Changes
- b5b9fd89: Migrate to using `ethers.StaticJsonRpcProvider`
- Updated dependencies [ecc2f8c1]
- @eth-optimism/contracts@0.4.9
## 0.3.9
### Patch Changes
- 3b132974: Fix tx resubmission estimateGas bug in batch submitter
- Updated dependencies [7f26667d]
- Updated dependencies [77511b68]
- @eth-optimism/contracts@0.4.7
## 0.3.8
### Patch Changes
- c73c3939: Update the typescript version to `4.3.5`
- Updated dependencies [c73c3939]
- @eth-optimism/common-ts@0.1.5
- @eth-optimism/contracts@0.4.5
- @eth-optimism/core-utils@0.5.1
## 0.3.7
### Patch Changes
- 8a1e63dd: Prevent batch submitter from submitting batches if low on ETH
- Updated dependencies [0313794b]
- Updated dependencies [049200f4]
- Updated dependencies [21e47e1f]
- @eth-optimism/contracts@0.4.2
- @eth-optimism/core-utils@0.5.0
## 0.3.6
### Patch Changes
- f87a2d00: Use dashes instead of colons in contract names
- 52d02b14: Add failure metrics to batch submitter
- 31f517a2: Improved logging of batch submission timeout logs
- 5c89c45f: Move the metric prefix string to a label #1047
- Updated dependencies [25f09abd]
- Updated dependencies [dd8edc7b]
- Updated dependencies [c87e4c74]
- Updated dependencies [db0dbfb2]
- Updated dependencies [7f5936a8]
- Updated dependencies [f87a2d00]
- Updated dependencies [85da4979]
- Updated dependencies [57ca21a2]
- Updated dependencies [5fc728da]
- Updated dependencies [2e72fd90]
- Updated dependencies [c43b33ec]
- Updated dependencies [26bc63ad]
- Updated dependencies [a0d9e565]
- Updated dependencies [2bd49730]
- Updated dependencies [38355a3b]
- Updated dependencies [3c2c32e1]
- Updated dependencies [d9644c34]
- Updated dependencies [48ece14c]
- Updated dependencies [e04de624]
- Updated dependencies [014dea71]
- Updated dependencies [fa29b03e]
- Updated dependencies [6b46c8ba]
- Updated dependencies [e045f582]
- Updated dependencies [5c89c45f]
- Updated dependencies [df5ff890]
- Updated dependencies [e29fab10]
- Updated dependencies [c2a04893]
- Updated dependencies [baacda34]
- @eth-optimism/contracts@0.4.0
- @eth-optimism/core-utils@0.4.6
- @eth-optimism/common-ts@0.1.4
## 0.3.5
### Patch Changes
- 7cce55a9: Add status to generic error log to disambiguate errors
## 0.3.4
### Patch Changes
- baa3b761: Improve Sentry support, initializing as needed and ensuring ERROR logs route to Sentry
- cc742715: Fix typo in USE_HARDHAT config
- 98b7839f: Change monotonicity band-aid code to log warnings not errors
- c520100d: Fix a bug in fixMonotonicity auto healer
- 85362d44: Log additional data in monotonicity violation
- Updated dependencies [baa3b761]
- @eth-optimism/common-ts@0.1.3
## 0.3.3
### Patch Changes
- 750a5021: Remove dead imports from core-utils
- Updated dependencies [a64f8161]
- Updated dependencies [4e03f8a9]
- Updated dependencies [8e2bfd07]
- Updated dependencies [750a5021]
- Updated dependencies [c2b6e14b]
- Updated dependencies [245136f1]
- @eth-optimism/core-utils@0.4.5
- @eth-optimism/contracts@0.3.5
## 0.3.2
### Patch Changes
- 4340bb1: Fix: correctly read Batch Submitter env var defaults
## 0.3.1
### Patch Changes
- c79dc8b: Add impersonate account debug config.
- 0c16805: add metrics server to common-ts and batch submitter
- fa4898a: Explicitly log error messages so that they do not show as empty objects
- 96a586e: Updates the configuration to use bcfg in a backwards compatible way
- c79dc8b: Make BLOCK_OFFSET configurable.
- Updated dependencies [96a586e]
- Updated dependencies [0c16805]
- Updated dependencies [775118a]
- @eth-optimism/core-utils@0.4.3
- @eth-optimism/common-ts@0.1.2
- @eth-optimism/contracts@0.3.1
## 0.3.0
### Minor Changes
- b799caa: Updates to use RLP encoded transactions in batches for the `v0.3.0` release
### Patch Changes
- 751e2be: Add the support for different sequencer & proposer keys in the batch submitter.
- Updated dependencies [b799caa]
- Updated dependencies [6132e7a]
- Updated dependencies [b799caa]
- Updated dependencies [b799caa]
- Updated dependencies [b799caa]
- Updated dependencies [20747fd]
- Updated dependencies [b799caa]
- Updated dependencies [b799caa]
- @eth-optimism/contracts@0.3.0
- @eth-optimism/core-utils@0.4.2
## 0.2.5
### Patch Changes
- 1d40586: Removed various unused dependencies
- Updated dependencies [1d40586]
- Updated dependencies [ce7fa52]
- Updated dependencies [575bcf6]
- Updated dependencies [6dc1877]
- @eth-optimism/common-ts@0.1.1
- @eth-optimism/contracts@0.2.10
- @eth-optimism/core-utils@0.4.1
## 0.2.4
### Patch Changes
- 12dbd81: add key metrics to batch submitter
- 28dc442: move metrics, logger, and base-service to new common-ts package
- 79df44e: Add skipped deposit auto heal
- Updated dependencies [28dc442]
- Updated dependencies [d2091d4]
- Updated dependencies [a0a0052]
- Updated dependencies [0ef3069]
- @eth-optimism/common-ts@0.1.0
- @eth-optimism/core-utils@0.4.0
- @eth-optimism/contracts@0.2.9
## 0.2.3
### Patch Changes
- 6daa408: update hardhat versions so that solc is resolved correctly
- dee74ef: migrate batch submitter types to core-utils
- d64b66d: reformat error context for Sentry
- Updated dependencies [6daa408]
- Updated dependencies [ea4041b]
- Updated dependencies [f1f5bf2]
- Updated dependencies [dee74ef]
- Updated dependencies [9ec3ec0]
- Updated dependencies [d64b66d]
- Updated dependencies [5f376ee]
- Updated dependencies [eef1df4]
- Updated dependencies [a76cde5]
- Updated dependencies [e713cd0]
- Updated dependencies [572dcbc]
- Updated dependencies [6014ec0]
- @eth-optimism/contracts@0.2.8
- @eth-optimism/core-utils@0.3.2
## 0.2.2
### Patch Changes
- 6d31324: Update release tag for Sentry compatability
- a2f6e83: add default metrics to all batch submitters
## 0.2.1
### Patch Changes
- ab285e4: properly start the batch submitter instead of instantly exiting
## 0.2.0
### Minor Changes
- 5077441: - Use raw transaction in batch submitter -- incompatible with L2Geth v0.1.2.1
- Pass through raw transaction in l2context
### Patch Changes
- a3dc553: Adds a release version to batch-submitter and data-transport-layer usage of Sentry
- b95dc22: log errors for monotonicity violations
- c7bc0ce: Correctly formatted error object to log exceptions
- Updated dependencies [ce5d596]
- Updated dependencies [1a55f64]
- Updated dependencies [6e8fe1b]
- Updated dependencies [8d4aae4]
- Updated dependencies [c75a0fc]
- Updated dependencies [d4ee2d7]
- Updated dependencies [edb4346]
- Updated dependencies [5077441]
- @eth-optimism/contracts@0.2.6
- @eth-optimism/core-utils@0.3.1
## 0.1.12
### Patch Changes
- a0a7956: initialize Sentry and streams in Logger, remove Sentry from Batch Submitter
- Updated dependencies [91460d9]
- Updated dependencies [a0a7956]
- Updated dependencies [0497d7d]
- @eth-optimism/core-utils@0.3.0
- @eth-optimism/contracts@0.2.5
## 0.1.11
### Patch Changes
- 35b99b0: add Sentry to TypeScript services for error tracking
- Updated dependencies [35b99b0]
- @eth-optimism/core-utils@0.2.3
## 0.1.10
### Patch Changes
- 962e31b: removed unused l1 block number logic, added debug logging to batch submitter
## 0.1.9
### Patch Changes
- 3b00b7c: bump private package versions to try triggering a tag
## 0.1.8
### Patch Changes
- 6cbc54d: allow injecting L2 transaction and block context via core-utils (this removes the need to import the now deprecated @eth-optimism/provider package)
- Updated dependencies [6cbc54d]
- @eth-optimism/core-utils@0.2.0
- @eth-optimism/contracts@0.2.2
## v0.1.3
- Add tx resubmission logic
- Log when the batch submitter runs low on ETH
## v0.1.2
Adds mnemonic config parsing
## v0.1.1
Final fixes before minnet release.
- Add batch submission timeout
- Log sequencer address
- remove ssh
## v0.1.0
The inital release
(The MIT License)
Copyright 2020-2021 Optimism
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[![codecov](https://codecov.io/gh/ethereum-optimism/optimism/branch/master/graph/badge.svg?token=0VTG7PG7YR&flag=batch-submitter)](https://codecov.io/gh/ethereum-optimism/optimism)
# Batch Submitter
Contains an executable batch submitter service which watches L1 and a local L2 node and submits batches to the
`CanonicalTransactionChain` & `StateCommitmentChain` based on its local information.
## Configuration
All configuration is done via environment variables. See all variables at [.env.example](.env.example); copy into a `.env` file before running.
## Building & Running
1. Make sure dependencies are installed just run `yarn` in the base directory
2. Build `yarn build`
3. Run `yarn start`
## Controlling log output verbosity
Before running, set the `DEBUG` environment variable to specify the verbosity level. It must be made up of comma-separated values of patterns to match in debug logs. Here's a few common options:
* `debug*` - Will match all debug statements -- very verbose
* `info*` - Will match all info statements -- less verbose, useful in most cases
* `warn*` - Will match all warnings -- recommended at a minimum
* `error*` - Will match all errors -- would not omit this
Examples:
* Everything but debug: `export DEBUG=info*,error*,warn*`
* Most verbose: `export DEBUG=info*,error*,warn*,debug*`
## Testing & linting
### Local
- Run unit tests with `yarn test`
- See lint errors with `yarn lint`; auto-fix with `yarn lint --fix`
### Submission
You may test a submission locally against a local Hardhat fork.
1. Follow the instructions [here](https://github.com/ethereum-optimism/hardhat) to run a Hardhat node.
2. Change the Batch Submitter `.env` field `L1_NODE_WEB3_URL` to the local Hardhat url. Depending on which network you are using, update `ADDRESS_MANAGER_ADDRESS` according to the [Regenesis repo](https://github.com/ethereum-optimism/regenesis).
3. Also check `L2_NODE_WEB3_URL` is correctly set and has transactions to submit.
3. Run `yarn build` to build your changes.
4. Start Batch Submitter with `yarn start`. It will automatically start submitting pending transactions from L2.
## Observability in production
When deploying Batch Submitter to production / a live ETH network, populate the environment variables `NODE_ENV` (`development`, `production`, or `test`) and `ETH_NETWORK_NAME` (`mainnet`, `kovan`, `goerli`). This enables Batch Submitter to capture more context in logs and metrics, and initializes [Sentry](https://docs.sentry.io/platforms/node/) to track errors.
#!/usr/bin/env node
const batchSubmitter = require('../dist/src/exec/run-batch-submitter')
batchSubmitter.run()
import '@nomiclabs/hardhat-waffle'
import { HardhatUserConfig } from 'hardhat/config'
import {
DEFAULT_ACCOUNTS_HARDHAT,
RUN_OVM_TEST_GAS,
} from './test/helpers/constants'
import '@nomiclabs/hardhat-ethers'
const config: HardhatUserConfig = {
networks: {
hardhat: {
accounts: DEFAULT_ACCOUNTS_HARDHAT,
blockGasLimit: RUN_OVM_TEST_GAS * 2,
},
},
mocha: {
timeout: 50000,
},
solidity: {
version: '0.7.0',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
metadata: {
bytecodeHash: 'none',
},
outputSelection: {
'*': {
'*': ['metadata', 'storageLayout'],
},
},
},
},
}
export default config
const rootPath = __dirname
export { rootPath }
{
"private": true,
"name": "@eth-optimism/batch-submitter",
"version": "0.4.20",
"description": "[Optimism] Service for submitting transactions and transaction results",
"main": "dist/index",
"types": "dist/index",
"files": [
"dist/*"
],
"scripts": {
"start": "node ./exec/run-batch-submitter.js",
"build": "tsc -p ./tsconfig.build.json",
"clean": "rimraf cache/ dist/ ./tsconfig.build.tsbuildinfo",
"lint": "yarn lint:fix && yarn lint:check",
"pre-commit": "lint-staged",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint . --max-warnings=0",
"test": "hardhat test --show-stack-traces",
"test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json"
},
"keywords": [
"optimism",
"ethereum",
"sequencer",
"aggregator"
],
"homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/packages/batch-submitter#readme",
"license": "MIT",
"author": "Optimism PBC",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.15",
"@eth-optimism/core-utils": "0.8.0",
"@eth-optimism/sdk": "^0.2.3",
"@eth-optimism/ynatm": "^0.2.2",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.5.3",
"@sentry/node": "^6.3.1",
"bcfg": "^0.1.6",
"bluebird": "^3.7.2",
"dotenv": "^10.0.0",
"ethers": "^5.5.4",
"old-contracts": "npm:@eth-optimism/contracts@^0.0.2-alpha.7",
"prom-client": "^13.1.0"
},
"devDependencies": {
"@eth-optimism/smock": "1.1.10",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@types/bluebird": "^3.5.34",
"@types/chai": "^4.2.18",
"@types/mocha": "^8.2.2",
"@types/node": "^15.12.2",
"@types/sinon": "^9.0.10",
"@types/sinon-chai": "^3.2.5",
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
"babel-eslint": "^10.1.0",
"chai": "^4.3.4",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.3.0",
"hardhat": "^2.3.0",
"lint-staged": "11.0.0",
"mocha": "^8.4.0",
"prettier": "^2.3.1",
"rimraf": "^3.0.2",
"sinon": "^9.2.4",
"sinon-chai": "^3.5.0",
"typescript": "^4.3.5"
},
"resolutions": {
"ganache-core": "^2.13.2",
"**/@sentry/node": "^6.2.5"
},
"publishConfig": {
"access": "public"
}
}
/* External Imports */
import {
Contract,
Signer,
utils,
providers,
PopulatedTransaction,
} from 'ethers'
import {
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { Gauge, Histogram, Counter } from 'prom-client'
import { RollupInfo, sleep } from '@eth-optimism/core-utils'
import { Logger, Metrics } from '@eth-optimism/common-ts'
import { getContractFactory } from 'old-contracts'
/* Internal Imports */
import { TxSubmissionHooks } from '..'
export interface BlockRange {
start: number
end: number
}
interface BatchSubmitterMetrics {
batchSubmitterETHBalance: Gauge<string>
batchSizeInBytes: Histogram<string>
numTxPerBatch: Histogram<string>
submissionTimestamp: Histogram<string>
submissionGasUsed: Histogram<string>
batchesSubmitted: Counter<string>
failedSubmissions: Counter<string>
malformedBatches: Counter<string>
batchTxBuildTime: Gauge<string>
}
export abstract class BatchSubmitter {
protected rollupInfo: RollupInfo
protected chainContract: Contract
protected l2ChainId: number
protected syncing: boolean
protected lastBatchSubmissionTimestamp: number = 0
protected metrics: BatchSubmitterMetrics
constructor(
readonly signer: Signer,
readonly l2Provider: providers.StaticJsonRpcProvider,
readonly minTxSize: number,
readonly maxTxSize: number,
readonly maxBatchSize: number,
readonly maxBatchSubmissionTime: number,
readonly numConfirmations: number,
readonly resubmissionTimeout: number,
readonly finalityConfirmations: number,
readonly addressManagerAddress: string,
readonly minBalanceEther: number,
readonly blockOffset: number,
readonly logger: Logger,
readonly defaultMetrics: Metrics
) {
this.metrics = this._registerMetrics(defaultMetrics)
}
public abstract _submitBatch(
startBlock: number,
endBlock: number
): Promise<TransactionReceipt>
public abstract _onSync(): Promise<TransactionReceipt>
public abstract _getBatchStartAndEnd(): Promise<BlockRange>
public abstract _updateChainInfo(): Promise<void>
public async submitNextBatch(): Promise<TransactionReceipt> {
if (typeof this.l2ChainId === 'undefined') {
this.l2ChainId = await this._getL2ChainId()
}
await this._updateChainInfo()
if (!(await this._hasEnoughETHToCoverGasCosts())) {
await sleep(this.resubmissionTimeout)
return
}
this.logger.info('Readying to submit next batch...', {
l2ChainId: this.l2ChainId,
batchSubmitterAddress: await this.signer.getAddress(),
})
if (this.syncing === true) {
this.logger.info(
'Syncing mode enabled! Skipping batch submission and clearing queue...'
)
return this._onSync()
}
const range = await this._getBatchStartAndEnd()
if (!range) {
return
}
return this._submitBatch(range.start, range.end)
}
protected async _hasEnoughETHToCoverGasCosts(): Promise<boolean> {
const address = await this.signer.getAddress()
const balance = await this.signer.getBalance()
const ether = utils.formatEther(balance)
const num = parseFloat(ether)
this.logger.info('Checked balance', {
address,
ether,
})
this.metrics.batchSubmitterETHBalance.set(num)
if (num < this.minBalanceEther) {
this.logger.fatal('Current balance lower than min safe balance', {
current: num,
safeBalance: this.minBalanceEther,
})
return false
}
return true
}
protected async _getRollupInfo(): Promise<RollupInfo> {
return this.l2Provider.send('rollup_getInfo', [])
}
protected async _getL2ChainId(): Promise<number> {
return this.l2Provider.send('eth_chainId', [])
}
protected async _getChainAddresses(): Promise<{
ctcAddress: string
sccAddress: string
}> {
const addressManager = (
await getContractFactory('Lib_AddressManager', this.signer)
).attach(this.addressManagerAddress)
const sccAddress = await addressManager.getAddress('StateCommitmentChain')
const ctcAddress = await addressManager.getAddress(
'CanonicalTransactionChain'
)
return {
ctcAddress,
sccAddress,
}
}
protected _shouldSubmitBatch(batchSizeInBytes: number): boolean {
const currentTimestamp = Date.now()
if (batchSizeInBytes < this.minTxSize) {
const timeSinceLastSubmission =
currentTimestamp - this.lastBatchSubmissionTimestamp
if (timeSinceLastSubmission < this.maxBatchSubmissionTime) {
this.logger.info(
'Skipping batch submission. Batch too small & max submission timeout not reached.',
{
batchSizeInBytes,
timeSinceLastSubmission,
maxBatchSubmissionTime: this.maxBatchSubmissionTime,
minTxSize: this.minTxSize,
lastBatchSubmissionTimestamp: this.lastBatchSubmissionTimestamp,
currentTimestamp,
}
)
return false
}
this.logger.info('Timeout reached, proceeding with batch submission.', {
batchSizeInBytes,
timeSinceLastSubmission,
maxBatchSubmissionTime: this.maxBatchSubmissionTime,
lastBatchSubmissionTimestamp: this.lastBatchSubmissionTimestamp,
currentTimestamp,
})
this.metrics.batchSizeInBytes.observe(batchSizeInBytes)
return true
}
this.logger.info(
'Sufficient batch size, proceeding with batch submission.',
{
batchSizeInBytes,
lastBatchSubmissionTimestamp: this.lastBatchSubmissionTimestamp,
currentTimestamp,
}
)
this.metrics.batchSizeInBytes.observe(batchSizeInBytes)
return true
}
protected _makeHooks(txName: string): TxSubmissionHooks {
return {
beforeSendTransaction: (tx: PopulatedTransaction) => {
this.logger.info(`Submitting ${txName} transaction`, {
gasPrice: tx.gasPrice,
maxFeePerGas: tx.maxFeePerGas,
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
gasLimit: tx.gasLimit,
nonce: tx.nonce,
contractAddr: this.chainContract.address,
})
},
onTransactionResponse: (txResponse: TransactionResponse) => {
this.logger.info(`Submitted ${txName} transaction`, {
txHash: txResponse.hash,
from: txResponse.from,
})
this.logger.debug(`${txName} transaction data`, {
data: txResponse.data,
})
},
}
}
protected async _submitAndLogTx(
submitTransaction: () => Promise<TransactionReceipt>,
successMessage: string
): Promise<TransactionReceipt> {
this.lastBatchSubmissionTimestamp = Date.now()
this.logger.debug('Submitting transaction & waiting for receipt...')
let receipt: TransactionReceipt
try {
receipt = await submitTransaction()
} catch (err) {
this.metrics.failedSubmissions.inc()
if (err.reason) {
this.logger.error(`Transaction invalid: ${err.reason}, aborting`, {
message: err.toString(),
stack: err.stack,
code: err.code,
})
return
}
this.logger.error('Encountered error at submission, aborting', {
message: err.toString(),
stack: err.stack,
code: err.code,
})
return
}
this.logger.info('Received transaction receipt', { receipt })
this.logger.info(successMessage)
this.metrics.batchesSubmitted.inc()
this.metrics.submissionGasUsed.observe(receipt.gasUsed.toNumber())
this.metrics.submissionTimestamp.observe(Date.now())
return receipt
}
private _registerMetrics(metrics: Metrics): BatchSubmitterMetrics {
metrics.registry.clear()
return {
batchSubmitterETHBalance: new metrics.client.Gauge({
name: 'batch_submitter_eth_balance',
help: 'ETH balance of the batch submitter',
registers: [metrics.registry],
}),
batchSizeInBytes: new metrics.client.Histogram({
name: 'batch_size_in_bytes',
help: 'Size of batches in bytes',
registers: [metrics.registry],
}),
numTxPerBatch: new metrics.client.Histogram({
name: 'num_txs_per_batch',
help: 'Number of transactions in each batch',
registers: [metrics.registry],
}),
submissionTimestamp: new metrics.client.Histogram({
name: 'submission_timestamp',
help: 'Timestamp of each batch submitter submission',
registers: [metrics.registry],
}),
submissionGasUsed: new metrics.client.Histogram({
name: 'submission_gash_used',
help: 'Gas used to submit each batch',
registers: [metrics.registry],
}),
batchesSubmitted: new metrics.client.Counter({
name: 'batches_submitted',
help: 'Count of batches submitted',
registers: [metrics.registry],
}),
failedSubmissions: new metrics.client.Counter({
name: 'failed_submissions',
help: 'Count of failed batch submissions',
registers: [metrics.registry],
}),
malformedBatches: new metrics.client.Counter({
name: 'malformed_batches',
help: 'Count of malformed batches',
registers: [metrics.registry],
}),
batchTxBuildTime: new metrics.client.Gauge({
name: 'batch_tx_build_time',
help: 'Time to construct batch transaction',
registers: [metrics.registry],
}),
}
}
}
export * from './batch-submitter'
export * from './tx-batch-submitter'
export * from './state-batch-submitter'
export const TX_BATCH_SUBMITTER_LOG_TAG = 'oe:batch_submitter:tx_chain'
export const STATE_BATCH_SUBMITTER_LOG_TAG = 'oe:batch_submitter:state_chain'
/* External Imports */
import { performance } from 'perf_hooks'
import { Promise as bPromise } from 'bluebird'
import { Contract, Signer, providers } from 'ethers'
import { TransactionReceipt } from '@ethersproject/abstract-provider'
import { getContractFactory } from 'old-contracts'
import {
L2Block,
RollupInfo,
Bytes32,
remove0x,
} from '@eth-optimism/core-utils'
import { Logger, Metrics } from '@eth-optimism/common-ts'
/* Internal Imports */
import { TransactionSubmitter } from '../utils'
import { BlockRange, BatchSubmitter } from '.'
export class StateBatchSubmitter extends BatchSubmitter {
// TODO: Change this so that we calculate start = scc.totalElements() and end = ctc.totalElements()!
// Not based on the length of the L2 chain -- that is only used in the batch submitter
// Note this means we've got to change the state / end calc logic
protected l2ChainId: number
protected syncing: boolean
protected ctcContract: Contract
private fraudSubmissionAddress: string
private transactionSubmitter: TransactionSubmitter
constructor(
signer: Signer,
l2Provider: providers.StaticJsonRpcProvider,
minTxSize: number,
maxTxSize: number,
maxBatchSize: number,
maxBatchSubmissionTime: number,
numConfirmations: number,
resubmissionTimeout: number,
finalityConfirmations: number,
addressManagerAddress: string,
minBalanceEther: number,
transactionSubmitter: TransactionSubmitter,
blockOffset: number,
logger: Logger,
metrics: Metrics,
fraudSubmissionAddress: string
) {
super(
signer,
l2Provider,
minTxSize,
maxTxSize,
maxBatchSize,
maxBatchSubmissionTime,
numConfirmations,
resubmissionTimeout,
finalityConfirmations,
addressManagerAddress,
minBalanceEther,
blockOffset,
logger,
metrics
)
this.fraudSubmissionAddress = fraudSubmissionAddress
this.transactionSubmitter = transactionSubmitter
}
/*****************************
* Batch Submitter Overrides *
****************************/
public async _updateChainInfo(): Promise<void> {
const info: RollupInfo = await this._getRollupInfo()
if (info.mode === 'verifier') {
this.logger.error(
'Verifier mode enabled! Batch submitter only compatible with sequencer mode'
)
process.exit(1)
}
this.syncing = info.syncing
const addrs = await this._getChainAddresses()
const sccAddress = addrs.sccAddress
const ctcAddress = addrs.ctcAddress
if (
typeof this.chainContract !== 'undefined' &&
sccAddress === this.chainContract.address &&
ctcAddress === this.ctcContract.address
) {
this.logger.debug('Chain contract already initialized', {
sccAddress,
ctcAddress,
})
return
}
this.chainContract = (
await getContractFactory('OVM_StateCommitmentChain', this.signer)
).attach(sccAddress)
this.ctcContract = (
await getContractFactory('OVM_CanonicalTransactionChain', this.signer)
).attach(ctcAddress)
this.logger.info('Connected Optimism contracts', {
stateCommitmentChain: this.chainContract.address,
canonicalTransactionChain: this.ctcContract.address,
})
return
}
public async _onSync(): Promise<TransactionReceipt> {
this.logger.info('Syncing mode enabled! Skipping state batch submission...')
return
}
public async _getBatchStartAndEnd(): Promise<BlockRange> {
this.logger.info('Getting batch start and end for state batch submitter...')
const startBlock: number =
(await this.chainContract.getTotalElements()).toNumber() +
this.blockOffset
this.logger.info('Retrieved start block number from SCC', {
startBlock,
})
// We will submit state roots for txs which have been in the tx chain for a while.
const totalElements: number =
(await this.ctcContract.getTotalElements()).toNumber() + this.blockOffset
this.logger.info('Retrieved total elements from CTC', {
totalElements,
})
const endBlock: number = Math.min(
startBlock + this.maxBatchSize,
totalElements
)
if (startBlock >= endBlock) {
if (startBlock > endBlock) {
this.logger.error(
'State commitment chain is larger than transaction chain. This should never happen!'
)
}
this.logger.info(
'No state commitments to submit. Skipping batch submission...'
)
return
}
return {
start: startBlock,
end: endBlock,
}
}
public async _submitBatch(
startBlock: number,
endBlock: number
): Promise<TransactionReceipt> {
const batchTxBuildStart = performance.now()
const batch = await this._generateStateCommitmentBatch(startBlock, endBlock)
const calldata = this.chainContract.interface.encodeFunctionData(
'appendStateBatch',
[batch, startBlock]
)
const batchSizeInBytes = remove0x(calldata).length / 2
this.logger.debug('State batch generated', {
batchSizeInBytes,
calldata,
})
if (!this._shouldSubmitBatch(batchSizeInBytes)) {
return
}
const batchTxBuildEnd = performance.now()
this.metrics.batchTxBuildTime.set(batchTxBuildEnd - batchTxBuildStart)
const offsetStartsAtIndex = startBlock - this.blockOffset
this.logger.debug('Submitting batch.', { calldata })
// Generate the transaction we will repeatedly submit
const nonce = await this.signer.getTransactionCount()
const tx = await this.chainContract.populateTransaction.appendStateBatch(
batch,
offsetStartsAtIndex,
{ nonce }
)
const submitTransaction = (): Promise<TransactionReceipt> => {
return this.transactionSubmitter.submitTransaction(
tx,
this._makeHooks('appendStateBatch')
)
}
return this._submitAndLogTx(
submitTransaction,
'Submitted state root batch!'
)
}
/*********************
* Private Functions *
********************/
private async _generateStateCommitmentBatch(
startBlock: number,
endBlock: number
): Promise<Bytes32[]> {
const blockRange = endBlock - startBlock
const batch: Bytes32[] = await bPromise.map(
[...Array(blockRange).keys()],
async (i: number) => {
this.logger.debug('Fetching L2BatchElement', {
blockNo: startBlock + i,
})
const block = (await this.l2Provider.getBlockWithTransactions(
startBlock + i
)) as L2Block
const blockTx = block.transactions[0]
if (blockTx.from === this.fraudSubmissionAddress) {
this.logger.warn('Found transaction from fraud submission address', {
txHash: blockTx.hash,
fraudSubmissionAddress: this.fraudSubmissionAddress,
})
this.fraudSubmissionAddress = 'no fraud'
return '0xbad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1bad1'
}
return block.stateRoot
},
{ concurrency: 100 }
)
let tx = this.chainContract.interface.encodeFunctionData(
'appendStateBatch',
[batch, startBlock]
)
while (remove0x(tx).length / 2 > this.maxTxSize) {
batch.splice(Math.ceil((batch.length * 2) / 3)) // Delete 1/3rd of all of the batch elements
this.logger.debug('Splicing batch...', {
batchSizeInBytes: tx.length / 2,
})
tx = this.chainContract.interface.encodeFunctionData('appendStateBatch', [
batch,
startBlock,
])
}
this.logger.info('Generated state commitment batch', {
batch, // list of stateRoots
})
return batch
}
}
/* External Imports */
import { performance } from 'perf_hooks'
import { Promise as bPromise } from 'bluebird'
import { Signer, ethers, Contract, providers } from 'ethers'
import { TransactionReceipt } from '@ethersproject/abstract-provider'
import { getContractInterface, getContractFactory } from 'old-contracts'
import { getContractInterface as getNewContractInterface } from '@eth-optimism/contracts'
import {
L2Block,
RollupInfo,
BatchElement,
Batch,
QueueOrigin,
BatchType,
} from '@eth-optimism/core-utils'
import { Logger, Metrics } from '@eth-optimism/common-ts'
/* Internal Imports */
import {
CanonicalTransactionChainContract,
encodeAppendSequencerBatch,
BatchContext,
AppendSequencerBatchParams,
} from '../transaction-chain-contract'
import { TransactionSubmitter } from '../utils'
import { BlockRange, BatchSubmitter } from '.'
export interface AutoFixBatchOptions {
fixDoublePlayedDeposits: boolean
fixMonotonicity: boolean
fixSkippedDeposits: boolean
}
export class TransactionBatchSubmitter extends BatchSubmitter {
protected chainContract: CanonicalTransactionChainContract
protected l2ChainId: number
protected syncing: boolean
private autoFixBatchOptions: AutoFixBatchOptions
private validateBatch: boolean
private transactionSubmitter: TransactionSubmitter
private gasThresholdInGwei: number
private batchType: BatchType
constructor(
signer: Signer,
l2Provider: providers.StaticJsonRpcProvider,
minTxSize: number,
maxTxSize: number,
maxBatchSize: number,
maxBatchSubmissionTime: number,
numConfirmations: number,
resubmissionTimeout: number,
addressManagerAddress: string,
minBalanceEther: number,
gasThresholdInGwei: number,
transactionSubmitter: TransactionSubmitter,
blockOffset: number,
validateBatch: boolean,
logger: Logger,
metrics: Metrics,
autoFixBatchOptions: AutoFixBatchOptions = {
fixDoublePlayedDeposits: false,
fixMonotonicity: false,
fixSkippedDeposits: false,
}, // TODO: Remove this
batchType: string
) {
super(
signer,
l2Provider,
minTxSize,
maxTxSize,
maxBatchSize,
maxBatchSubmissionTime,
numConfirmations,
resubmissionTimeout,
0, // Supply dummy value because it is not used.
addressManagerAddress,
minBalanceEther,
blockOffset,
logger,
metrics
)
this.validateBatch = validateBatch
this.autoFixBatchOptions = autoFixBatchOptions
this.gasThresholdInGwei = gasThresholdInGwei
this.transactionSubmitter = transactionSubmitter
if (batchType === 'legacy') {
this.batchType = BatchType.LEGACY
} else if (batchType === 'zlib') {
this.batchType = BatchType.ZLIB
} else {
throw new Error(`Invalid batch type: ${batchType}`)
}
this.logger.info('Batch options', {
autoFixBatchOptions,
validateBatch,
batchType: BatchType[this.batchType],
})
}
/*****************************
* Batch Submitter Overrides *
****************************/
public async _updateChainInfo(): Promise<void> {
const info: RollupInfo = await this._getRollupInfo()
if (info.mode === 'verifier') {
this.logger.error(
'Verifier mode enabled! Batch submitter only compatible with sequencer mode'
)
process.exit(1)
}
this.syncing = info.syncing
const addrs = await this._getChainAddresses()
const ctcAddress = addrs.ctcAddress
if (
typeof this.chainContract !== 'undefined' &&
ctcAddress === this.chainContract.address
) {
this.logger.debug('Chain contract already initialized', {
ctcAddress,
})
return
}
const unwrapped_OVM_CanonicalTransactionChain = (
await getContractFactory('OVM_CanonicalTransactionChain', this.signer)
).attach(ctcAddress)
this.chainContract = new CanonicalTransactionChainContract(
unwrapped_OVM_CanonicalTransactionChain.address,
getContractInterface('OVM_CanonicalTransactionChain'),
this.signer
)
this.logger.info('Initialized new CTC', {
address: this.chainContract.address,
})
return
}
public async _onSync(): Promise<TransactionReceipt> {
const pendingQueueElements =
await this.chainContract.getNumPendingQueueElements()
this.logger.debug('Got number of pending queue elements', {
pendingQueueElements,
})
if (pendingQueueElements !== 0) {
this.logger.info(
'Syncing mode enabled! Skipping batch submission and clearing queue elements',
{ pendingQueueElements }
)
}
this.logger.info('Syncing mode enabled but queue is empty. Skipping...')
return
}
public async _getBatchStartAndEnd(): Promise<BlockRange> {
this.logger.info(
'Getting batch start and end for transaction batch submitter...'
)
const startBlock =
(await this.chainContract.getTotalElements()).toNumber() +
this.blockOffset
this.logger.info('Retrieved start block number from CTC', {
startBlock,
})
const endBlock =
Math.min(
startBlock + this.maxBatchSize,
await this.l2Provider.getBlockNumber()
) + 1 // +1 because the `endBlock` is *exclusive*
this.logger.info('Retrieved end block number from L2 sequencer', {
endBlock,
})
if (startBlock >= endBlock) {
if (startBlock > endBlock) {
this.logger
.error(`More chain elements in L1 (${startBlock}) than in the L2 node (${endBlock}).
This shouldn't happen because we don't submit batches if the sequencer is syncing.`)
}
this.logger.info('No txs to submit. Skipping batch submission...')
return
}
return {
start: startBlock,
end: endBlock,
}
}
public async _submitBatch(
startBlock: number,
endBlock: number
): Promise<TransactionReceipt> {
// Do not submit batch if gas price above threshold
const gasPriceInGwei = parseInt(
ethers.utils.formatUnits(await this.signer.getGasPrice(), 'gwei'),
10
)
if (gasPriceInGwei > this.gasThresholdInGwei) {
this.logger.warn(
'Gas price is higher than gas price threshold; aborting batch submission',
{
gasPriceInGwei,
gasThresholdInGwei: this.gasThresholdInGwei,
}
)
return
}
const batchTxBuildStart = performance.now()
const params = await this._generateSequencerBatchParams(
startBlock,
endBlock
)
if (!params) {
throw new Error(
`Cannot create sequencer batch with params start ${startBlock} and end ${endBlock}`
)
}
const [batchParams, wasBatchTruncated] = params
const batchSizeInBytes = encodeAppendSequencerBatch(batchParams).length / 2
this.logger.debug('Sequencer batch generated', {
batchSizeInBytes,
})
// Only submit batch if one of the following is true:
// 1. it was truncated
// 2. it is large enough
// 3. enough time has passed since last submission
if (!wasBatchTruncated && !this._shouldSubmitBatch(batchSizeInBytes)) {
return
}
const batchTxBuildEnd = performance.now()
this.metrics.batchTxBuildTime.set(batchTxBuildEnd - batchTxBuildStart)
this.metrics.numTxPerBatch.observe(batchParams.totalElementsToAppend)
const l1tipHeight = await this.signer.provider.getBlockNumber()
this.logger.debug('Submitting batch.', {
calldata: batchParams,
l1tipHeight,
})
return this.submitAppendSequencerBatch(batchParams)
}
/*********************
* Private Functions *
********************/
private async submitAppendSequencerBatch(
batchParams: AppendSequencerBatchParams
): Promise<TransactionReceipt> {
const tx =
await this.chainContract.customPopulateTransaction.appendSequencerBatch(
batchParams
)
const submitTransaction = (): Promise<TransactionReceipt> => {
return this.transactionSubmitter.submitTransaction(
tx,
this._makeHooks('appendSequencerBatch')
)
}
return this._submitAndLogTx(submitTransaction, 'Submitted batch!')
}
private async _generateSequencerBatchParams(
startBlock: number,
endBlock: number
): Promise<[AppendSequencerBatchParams, boolean]> {
// Get all L2 BatchElements for the given range
const blockRange = endBlock - startBlock
let batch: Batch = await bPromise.map(
[...Array(blockRange).keys()],
(i) => {
this.logger.debug('Fetching L2BatchElement', {
blockNo: startBlock + i,
})
return this._getL2BatchElement(startBlock + i)
},
{ concurrency: 100 }
)
// Fix our batches if we are configured to. This will not
// modify the batch unless an autoFixBatchOption is set
batch = await this._fixBatch(batch)
if (this.validateBatch) {
this.logger.info('Validating batch')
if (!(await this._validateBatch(batch))) {
this.metrics.malformedBatches.inc()
return
}
}
let sequencerBatchParams = await this._getSequencerBatchParams(
startBlock,
batch
)
let wasBatchTruncated = false
let encoded = encodeAppendSequencerBatch(sequencerBatchParams)
while (encoded.length / 2 > this.maxTxSize) {
this.logger.debug('Splicing batch...', {
batchSizeInBytes: encoded.length / 2,
})
batch.splice(Math.ceil((batch.length * 2) / 3)) // Delete 1/3rd of all of the batch elements
sequencerBatchParams = await this._getSequencerBatchParams(
startBlock,
batch
)
encoded = encodeAppendSequencerBatch(sequencerBatchParams)
// This is to prevent against the case where a batch is oversized,
// but then gets truncated to the point where it is under the minimum size.
// In this case, we want to submit regardless of the batch's size.
wasBatchTruncated = true
}
// Set the batch type so that it is serialized correctly
sequencerBatchParams.type = this.batchType
this.logger.info('Generated sequencer batch params', {
contexts: sequencerBatchParams.contexts,
transactions: sequencerBatchParams.transactions,
wasBatchTruncated,
type: BatchType[sequencerBatchParams.type],
})
return [sequencerBatchParams, wasBatchTruncated]
}
/**
* Returns true if the batch is valid.
*/
protected async _validateBatch(batch: Batch): Promise<boolean> {
// Verify all of the queue elements are what we expect
let nextQueueIndex = await this.chainContract.getNextQueueIndex()
for (const ele of batch) {
this.logger.debug('Verifying batch element', { ele })
if (!ele.isSequencerTx) {
this.logger.debug('Checking queue equality against L1 queue index', {
nextQueueIndex,
})
if (!(await this._doesQueueElementMatchL1(nextQueueIndex, ele))) {
return false
}
nextQueueIndex++
}
}
// Verify all of the batch elements are monotonic
let lastTimestamp: number
let lastBlockNumber: number
for (const [idx, ele] of batch.entries()) {
if (ele.timestamp < lastTimestamp) {
this.logger.error('Timestamp monotonicity violated! Element', {
idx,
ele,
})
return false
}
if (ele.blockNumber < lastBlockNumber) {
this.logger.error('Block Number monotonicity violated! Element', {
idx,
ele,
})
return false
}
lastTimestamp = ele.timestamp
lastBlockNumber = ele.blockNumber
}
return true
}
private async _doesQueueElementMatchL1(
queueIndex: number,
queueElement: BatchElement
): Promise<boolean> {
const logEqualityError = (name, index, expected, got) => {
this.logger.error('Observed mismatched values', {
index,
expected,
got,
})
}
let isEqual = true
const [, timestamp, blockNumber] = await this.chainContract.getQueueElement(
queueIndex
)
// TODO: Verify queue element hash equality. The queue element hash can be computed with:
// keccak256( abi.encode( msg.sender, _target, _gasLimit, _data))
// Check timestamp & blockNumber equality
if (timestamp !== queueElement.timestamp) {
isEqual = false
logEqualityError(
'Timestamp',
queueIndex,
timestamp,
queueElement.timestamp
)
}
if (blockNumber !== queueElement.blockNumber) {
isEqual = false
logEqualityError(
'Block Number',
queueIndex,
blockNumber,
queueElement.blockNumber
)
}
return isEqual
}
/**
* Takes in a batch which is potentially malformed & returns corrected version.
* Current fixes that are supported:
* - Double played deposits.
*/
private async _fixBatch(batch: Batch): Promise<Batch> {
const fixDoublePlayedDeposits = async (b: Batch): Promise<Batch> => {
let nextQueueIndex = await this.chainContract.getNextQueueIndex()
const fixedBatch: Batch = []
for (const ele of b) {
if (!ele.isSequencerTx) {
if (!(await this._doesQueueElementMatchL1(nextQueueIndex, ele))) {
this.logger.warn('Fixing double played queue element.', {
nextQueueIndex,
})
fixedBatch.push(
await this._fixDoublePlayedDepositQueueElement(
nextQueueIndex,
ele
)
)
continue
}
nextQueueIndex++
}
fixedBatch.push(ele)
}
return fixedBatch
}
const fixSkippedDeposits = async (b: Batch): Promise<Batch> => {
this.logger.debug('Fixing skipped deposits...')
let nextQueueIndex = await this.chainContract.getNextQueueIndex()
const fixedBatch: Batch = []
for (const ele of b) {
// Look for skipped deposits
while (true) {
const pendingQueueElements =
await this.chainContract.getNumPendingQueueElements()
const nextRemoteQueueElements =
await this.chainContract.getNextQueueIndex()
const totalQueueElements =
pendingQueueElements + nextRemoteQueueElements
// No more queue elements so we clearly haven't skipped anything
if (nextQueueIndex >= totalQueueElements) {
break
}
const [, timestamp, blockNumber] =
await this.chainContract.getQueueElement(nextQueueIndex)
if (timestamp < ele.timestamp || blockNumber < ele.blockNumber) {
this.logger.warn('Fixing skipped deposit', {
badTimestamp: ele.timestamp,
skippedQueueTimestamp: timestamp,
badBlockNumber: ele.blockNumber,
skippedQueueBlockNumber: blockNumber,
})
// Push a dummy queue element
fixedBatch.push({
stateRoot: ele.stateRoot,
isSequencerTx: false,
rawTransaction: undefined,
timestamp,
blockNumber,
})
nextQueueIndex++
} else {
// The next queue element's timestamp is after this batch element so
// we must not have skipped anything.
break
}
}
fixedBatch.push(ele)
if (!ele.isSequencerTx) {
nextQueueIndex++
}
}
return fixedBatch
}
// TODO: Remove this super complex logic and rely on Geth to actually supply correct block data.
const fixMonotonicity = async (b: Batch): Promise<Batch> => {
this.logger.debug('Fixing monotonicity...')
// The earliest allowed timestamp/blockNumber is the last timestamp submitted on chain.
const { lastTimestamp, lastBlockNumber } =
await this._getLastTimestampAndBlockNumber()
let earliestTimestamp = lastTimestamp
let earliestBlockNumber = lastBlockNumber
this.logger.debug('Determined earliest timestamp and blockNumber', {
earliestTimestamp,
earliestBlockNumber,
})
// The latest allowed timestamp/blockNumber is the next queue element!
let nextQueueIndex = await this.chainContract.getNextQueueIndex()
let latestTimestamp: number
let latestBlockNumber: number
// updateLatestTimestampAndBlockNumber is a helper which updates
// the latest timestamp and block number based on the pending queue elements.
const updateLatestTimestampAndBlockNumber = async () => {
const pendingQueueElements =
await this.chainContract.getNumPendingQueueElements()
const nextRemoteQueueElements =
await this.chainContract.getNextQueueIndex()
const totalQueueElements =
pendingQueueElements + nextRemoteQueueElements
if (nextQueueIndex < totalQueueElements) {
const [, queueTimestamp, queueBlockNumber] =
await this.chainContract.getQueueElement(nextQueueIndex)
latestTimestamp = queueTimestamp
latestBlockNumber = queueBlockNumber
} else {
// If there are no queue elements left then just allow any timestamp/blocknumber
latestTimestamp = Number.MAX_SAFE_INTEGER
latestBlockNumber = Number.MAX_SAFE_INTEGER
}
}
// Actually update the latest timestamp and block number
await updateLatestTimestampAndBlockNumber()
this.logger.debug('Determined latest timestamp and blockNumber', {
latestTimestamp,
latestBlockNumber,
})
// Now go through our batch and fix the timestamps and block numbers
// to automatically enforce monotonicity.
const fixedBatch: Batch = []
for (const ele of b) {
if (!ele.isSequencerTx) {
// Set the earliest allowed timestamp to the old latest and set the new latest
// to the next queue element's timestamp / blockNumber
earliestTimestamp = latestTimestamp
earliestBlockNumber = latestBlockNumber
nextQueueIndex++
await updateLatestTimestampAndBlockNumber()
}
// Fix the element if its timestammp/blockNumber is too small
if (
ele.timestamp < earliestTimestamp ||
ele.blockNumber < earliestBlockNumber
) {
this.logger.warn('Fixing timestamp/blockNumber too small', {
oldTimestamp: ele.timestamp,
newTimestamp: earliestTimestamp,
oldBlockNumber: ele.blockNumber,
newBlockNumber: earliestBlockNumber,
})
ele.timestamp = earliestTimestamp
ele.blockNumber = earliestBlockNumber
}
// Fix the element if its timestammp/blockNumber is too large
if (
ele.timestamp > latestTimestamp ||
ele.blockNumber > latestBlockNumber
) {
this.logger.warn('Fixing timestamp/blockNumber too large.', {
oldTimestamp: ele.timestamp,
newTimestamp: latestTimestamp,
oldBlockNumber: ele.blockNumber,
newBlockNumber: latestBlockNumber,
})
ele.timestamp = latestTimestamp
ele.blockNumber = latestBlockNumber
}
earliestTimestamp = ele.timestamp
earliestBlockNumber = ele.blockNumber
fixedBatch.push(ele)
}
return fixedBatch
}
// NOTE: It is unsafe to combine multiple autoFix options.
// If you must combine them, manually verify the output before proceeding.
if (this.autoFixBatchOptions.fixDoublePlayedDeposits) {
this.logger.info('Fixing double played deposits')
batch = await fixDoublePlayedDeposits(batch)
}
if (this.autoFixBatchOptions.fixMonotonicity) {
this.logger.info('Fixing monotonicity')
batch = await fixMonotonicity(batch)
}
if (this.autoFixBatchOptions.fixSkippedDeposits) {
this.logger.info('Fixing skipped deposits')
batch = await fixSkippedDeposits(batch)
}
return batch
}
private async _getLastTimestampAndBlockNumber(): Promise<{
lastTimestamp: number
lastBlockNumber: number
}> {
const manager = new Contract(
this.addressManagerAddress,
getNewContractInterface('Lib_AddressManager'),
this.signer.provider
)
const addr = await manager.getAddress('ChainStorageContainer-CTC-batches')
const container = new Contract(
addr,
getNewContractInterface('IChainStorageContainer'),
this.signer.provider
)
let meta = await container.getGlobalMetadata()
// remove 0x
meta = meta.slice(2)
// convert to bytes27
meta = meta.slice(10)
const lastTimestamp = parseInt(meta.slice(-30, -20), 16)
const lastBlockNumber = parseInt(meta.slice(-40, -30), 16)
this.logger.debug('Retrieved timestamp and block number from CTC', {
lastTimestamp,
lastBlockNumber,
})
return { lastTimestamp, lastBlockNumber }
}
private async _fixDoublePlayedDepositQueueElement(
queueIndex: number,
queueElement: BatchElement
): Promise<BatchElement> {
const [, timestamp, blockNumber] = await this.chainContract.getQueueElement(
queueIndex
)
if (
timestamp > queueElement.timestamp &&
blockNumber > queueElement.blockNumber
) {
this.logger.warn(
'Double deposit detected. Fixing by skipping the deposit & replacing with a dummy tx.',
{
timestamp,
blockNumber,
queueElementTimestamp: queueElement.timestamp,
queueElementBlockNumber: queueElement.blockNumber,
}
)
const dummyTx: string = '0x1234'
return {
stateRoot: queueElement.stateRoot,
isSequencerTx: true,
rawTransaction: dummyTx,
timestamp: queueElement.timestamp,
blockNumber: queueElement.blockNumber,
}
}
if (
timestamp < queueElement.timestamp &&
blockNumber < queueElement.blockNumber
) {
this.logger.error('A deposit seems to have been skipped!')
throw new Error('Skipped deposit?!')
}
throw new Error('Unable to fix queue element!')
}
private async _getSequencerBatchParams(
shouldStartAtIndex: number,
blocks: Batch
): Promise<AppendSequencerBatchParams> {
const totalElementsToAppend = blocks.length
// Generate contexts
const contexts: BatchContext[] = []
let lastBlockIsSequencerTx = false
let lastTimestamp = 0
let lastBlockNumber = 0
const groupedBlocks: Array<{
sequenced: BatchElement[]
queued: BatchElement[]
}> = []
for (const block of blocks) {
// Create a new context in certain situations
if (
// If there are no contexts yet, create a new context.
groupedBlocks.length === 0 ||
// If the last block was an L1 to L2 transaction, but the next block is a Sequencer
// transaction, create a new context.
(lastBlockIsSequencerTx === false && block.isSequencerTx === true) ||
// If the timestamp of the last block differs from the timestamp of the current block,
// create a new context. Applies to both L1 to L2 transactions and Sequencer transactions.
block.timestamp !== lastTimestamp ||
// If the block number of the last block differs from the block number of the current block,
// create a new context. ONLY applies to Sequencer transactions.
(block.blockNumber !== lastBlockNumber && block.isSequencerTx === true)
) {
groupedBlocks.push({
sequenced: [],
queued: [],
})
}
const cur = groupedBlocks.length - 1
block.isSequencerTx
? groupedBlocks[cur].sequenced.push(block)
: groupedBlocks[cur].queued.push(block)
lastBlockIsSequencerTx = block.isSequencerTx
lastTimestamp = block.timestamp
lastBlockNumber = block.blockNumber
}
for (const groupedBlock of groupedBlocks) {
if (
groupedBlock.sequenced.length === 0 &&
groupedBlock.queued.length === 0
) {
throw new Error(
'Attempted to generate batch context with 0 queued and 0 sequenced txs!'
)
}
contexts.push({
numSequencedTransactions: groupedBlock.sequenced.length,
numSubsequentQueueTransactions: groupedBlock.queued.length,
timestamp:
groupedBlock.sequenced.length > 0
? groupedBlock.sequenced[0].timestamp
: groupedBlock.queued[0].timestamp,
blockNumber:
groupedBlock.sequenced.length > 0
? groupedBlock.sequenced[0].blockNumber
: groupedBlock.queued[0].blockNumber,
})
}
// Generate sequencer transactions
const transactions: string[] = []
for (const block of blocks) {
if (!block.isSequencerTx) {
continue
}
transactions.push(block.rawTransaction)
}
return {
shouldStartAtElement: shouldStartAtIndex - this.blockOffset,
totalElementsToAppend,
contexts,
transactions,
}
}
private async _getL2BatchElement(blockNumber: number): Promise<BatchElement> {
const block = await this._getBlock(blockNumber)
this.logger.debug('Fetched L2 block', {
block,
})
const batchElement = {
stateRoot: block.stateRoot,
timestamp: block.timestamp,
blockNumber: block.transactions[0].l1BlockNumber,
isSequencerTx: false,
rawTransaction: undefined,
}
if (this._isSequencerTx(block)) {
batchElement.isSequencerTx = true
batchElement.rawTransaction = block.transactions[0].rawTransaction
}
return batchElement
}
private async _getBlock(blockNumber: number): Promise<L2Block> {
const p = this.l2Provider.getBlockWithTransactions(blockNumber)
return p as Promise<L2Block>
}
private _isSequencerTx(block: L2Block): boolean {
return block.transactions[0].queueOrigin === QueueOrigin.Sequencer
}
}
/* External Imports */
import { exit } from 'process'
import { Bcfg } from '@eth-optimism/core-utils'
import { asL2Provider } from '@eth-optimism/sdk'
import * as Sentry from '@sentry/node'
import { Logger, Metrics, createMetricsServer } from '@eth-optimism/common-ts'
import { Signer, Wallet } from 'ethers'
import {
StaticJsonRpcProvider,
TransactionReceipt,
} from '@ethersproject/providers'
import * as dotenv from 'dotenv'
import Config from 'bcfg'
/* Internal Imports */
import {
TransactionBatchSubmitter,
AutoFixBatchOptions,
StateBatchSubmitter,
STATE_BATCH_SUBMITTER_LOG_TAG,
TX_BATCH_SUBMITTER_LOG_TAG,
} from '..'
import {
TransactionSubmitter,
YnatmTransactionSubmitter,
ResubmissionConfig,
} from '../utils'
interface RequiredEnvVars {
// The HTTP provider URL for L1.
L1_NODE_WEB3_URL: string
// The HTTP provider URL for L2.
L2_NODE_WEB3_URL: string
// The layer one address manager address
ADDRESS_MANAGER_ADDRESS: string
// The minimum size in bytes of any L1 transactions generated by the batch submitter.
MIN_L1_TX_SIZE: number
// The maximum size in bytes of any L1 transactions generated by the batch submitter.
MAX_L1_TX_SIZE: number
// The maximum number of L2 transactions that can ever be in a batch.
MAX_TX_BATCH_COUNT: number
// The maximum number of L2 state roots that can ever be in a batch.
MAX_STATE_BATCH_COUNT: number
// The maximum amount of time (seconds) that we will wait before submitting an under-sized batch.
MAX_BATCH_SUBMISSION_TIME: number
// The delay in milliseconds between querying L2 for more transactions / to create a new batch.
POLL_INTERVAL: number
// The number of confirmations which we will wait after appending new batches.
NUM_CONFIRMATIONS: number
// The number of seconds to wait before resubmitting a transaction.
RESUBMISSION_TIMEOUT: number
// The number of confirmations that we should wait before submitting state roots for CTC elements.
FINALITY_CONFIRMATIONS: number
// Whether or not to run the tx batch submitter.
RUN_TX_BATCH_SUBMITTER: boolean
// Whether or not to run the state batch submitter.
RUN_STATE_BATCH_SUBMITTER: boolean
// The safe minimum amount of ether the batch submitter key should
// hold before it starts to log errors.
SAFE_MINIMUM_ETHER_BALANCE: number
// A boolean to clear the pending transactions in the mempool
// on start up.
CLEAR_PENDING_TXS: boolean
}
/* Optional Env Vars
* FRAUD_SUBMISSION_ADDRESS
* SEQUENCER_PRIVATE_KEY
* PROPOSER_PRIVATE_KEY
* MNEMONIC
* SEQUENCER_MNEMONIC
* PROPOSER_MNEMONIC
* SEQUENCER_HD_PATH
* PROPOSER_HD_PATH
* BLOCK_OFFSET
* VALIDATE_TX_BATCH
* USE_HARDHAT
* DEBUG_IMPERSONATE_SEQUENCER_ADDRESS
* DEBUG_IMPERSONATE_PROPOSER_ADDRESS
* RUN_METRICS_SERVER
*/
export const run = async () => {
dotenv.config()
const config: Bcfg = new Config('batch-submitter')
config.load({
env: true,
argv: true,
})
// Parse config
const env = process.env
const environment = config.str('node-env', env.NODE_ENV)
const network = config.str('eth-network-name', env.ETH_NETWORK_NAME)
const service = `batch-submitter`
const release = `batch-submitter@${env.npm_package_version}`
const sentryDsn = config.str('sentry-dsn', env.SENTRY_DSN)
const sentryTraceRate = config.ufloat(
'sentry-trace-rate',
parseFloat(env.SENTRY_TRACE_RATE) || 0.05
)
// Default is 1 because Geth normally has 1 more block than L1
const BLOCK_OFFSET = config.uint(
'block-offset',
parseInt(env.BLOCK_OFFSET, 10) || 1
)
/* Logger */
const name = 'oe:batch_submitter:init'
let logger
if (config.bool('use-sentry', env.USE_SENTRY === 'true')) {
const sentryOptions = {
release,
dsn: sentryDsn,
tracesSampleRate: sentryTraceRate,
environment: network,
}
Sentry.init(sentryOptions)
// Initialize Sentry for Batch Submitter deployed to a network
logger = new Logger({
name,
sentryOptions,
})
} else {
// Skip initializing Sentry
logger = new Logger({ name })
}
const useHardhat = config.bool('use-hardhat', !!env.USE_HARDHAT)
const DEBUG_IMPERSONATE_SEQUENCER_ADDRESS = config.str(
'debug-impersonate-sequencer-address',
env.DEBUG_IMPERSONATE_SEQUENCER_ADDRESS
)
const DEBUG_IMPERSONATE_PROPOSER_ADDRESS = config.str(
'debug-impersonate-proposer-address',
env.DEBUG_IMPERSONATE_PROPOSER_ADDRESS
)
const getSequencerSigner = async (): Promise<Signer> => {
const l1Provider = new StaticJsonRpcProvider({
url: requiredEnvVars.L1_NODE_WEB3_URL,
headers: { 'User-Agent': 'batch-submitter' },
})
if (useHardhat) {
if (!DEBUG_IMPERSONATE_SEQUENCER_ADDRESS) {
throw new Error('Must pass DEBUG_IMPERSONATE_SEQUENCER_ADDRESS')
}
await l1Provider.send('hardhat_impersonateAccount', [
DEBUG_IMPERSONATE_SEQUENCER_ADDRESS,
])
return l1Provider.getSigner(DEBUG_IMPERSONATE_SEQUENCER_ADDRESS)
}
if (SEQUENCER_PRIVATE_KEY) {
return new Wallet(SEQUENCER_PRIVATE_KEY, l1Provider)
} else if (SEQUENCER_MNEMONIC) {
return Wallet.fromMnemonic(SEQUENCER_MNEMONIC, SEQUENCER_HD_PATH).connect(
l1Provider
)
}
throw new Error(
'Must pass one of SEQUENCER_PRIVATE_KEY, MNEMONIC, or SEQUENCER_MNEMONIC'
)
}
const getProposerSigner = async (): Promise<Signer> => {
const l1Provider = new StaticJsonRpcProvider({
url: requiredEnvVars.L1_NODE_WEB3_URL,
headers: { 'User-Agent': 'batch-submitter' },
})
if (useHardhat) {
if (!DEBUG_IMPERSONATE_PROPOSER_ADDRESS) {
throw new Error('Must pass DEBUG_IMPERSONATE_PROPOSER_ADDRESS')
}
await l1Provider.send('hardhat_impersonateAccount', [
DEBUG_IMPERSONATE_PROPOSER_ADDRESS,
])
return l1Provider.getSigner(DEBUG_IMPERSONATE_PROPOSER_ADDRESS)
}
if (PROPOSER_PRIVATE_KEY) {
return new Wallet(PROPOSER_PRIVATE_KEY, l1Provider)
} else if (PROPOSER_MNEMONIC) {
return Wallet.fromMnemonic(PROPOSER_MNEMONIC, PROPOSER_HD_PATH).connect(
l1Provider
)
}
throw new Error(
'Must pass one of PROPOSER_PRIVATE_KEY, MNEMONIC, or PROPOSER_MNEMONIC'
)
}
/* Metrics */
const metrics = new Metrics({
labels: { environment, release, network, service },
})
const FRAUD_SUBMISSION_ADDRESS = config.str(
'fraud-submisison-address',
env.FRAUD_SUBMISSION_ADDRESS || 'no fraud'
)
const MIN_GAS_PRICE_IN_GWEI = config.uint(
'min-gas-price-in-gwei',
parseInt(env.MIN_GAS_PRICE_IN_GWEI, 10) || 0
)
const GAS_RETRY_INCREMENT = config.uint(
'gas-retry-increment',
parseInt(env.GAS_RETRY_INCREMENT, 10) || 5
)
const GAS_THRESHOLD_IN_GWEI = config.uint(
'gas-threshold-in-gwei',
parseInt(env.GAS_THRESHOLD_IN_GWEI, 10) || 100
)
// Private keys & mnemonics
const SEQUENCER_PRIVATE_KEY = config.str(
'sequencer-private-key',
env.SEQUENCER_PRIVATE_KEY
)
// Kept for backwards compatibility
const PROPOSER_PRIVATE_KEY = config.str(
'proposer-private-key',
env.PROPOSER_PRIVATE_KEY || env.SEQUENCER_PRIVATE_KEY
)
const SEQUENCER_MNEMONIC = config.str(
'sequencer-mnemonic',
env.SEQUENCER_MNEMONIC || env.MNEMONIC
)
const PROPOSER_MNEMONIC = config.str(
'proposer-mnemonic',
env.PROPOSER_MNEMONIC || env.MNEMONIC
)
const SEQUENCER_HD_PATH = config.str(
'sequencer-hd-path',
env.SEQUENCER_HD_PATH || env.HD_PATH
)
const PROPOSER_HD_PATH = config.str(
'proposer-hd-path',
env.PROPOSER_HD_PATH || env.HD_PATH
)
const VALIDATE_TX_BATCH = config.bool(
'validate-tx-batch',
env.VALIDATE_TX_BATCH ? env.VALIDATE_TX_BATCH === 'true' : false
)
const SEQUENCER_BATCH_TYPE = config.str(
'sequencer-batch-type',
env.SEQUENCER_BATCH_TYPE || 'legacy'
)
// Auto fix batch options -- TODO: Remove this very hacky config
const AUTO_FIX_BATCH_OPTIONS_CONF = config.str(
'auto-fix-batch-conf',
env.AUTO_FIX_BATCH_OPTIONS_CONF || ''
)
const autoFixBatchOptions: AutoFixBatchOptions = {
fixDoublePlayedDeposits: AUTO_FIX_BATCH_OPTIONS_CONF
? AUTO_FIX_BATCH_OPTIONS_CONF.includes('fixDoublePlayedDeposits')
: false,
fixMonotonicity: AUTO_FIX_BATCH_OPTIONS_CONF
? AUTO_FIX_BATCH_OPTIONS_CONF.includes('fixMonotonicity')
: false,
fixSkippedDeposits: AUTO_FIX_BATCH_OPTIONS_CONF
? AUTO_FIX_BATCH_OPTIONS_CONF.includes('fixSkippedDeposits')
: false,
}
logger.info('Starting batch submitter...')
const requiredEnvVars: RequiredEnvVars = {
L1_NODE_WEB3_URL: config.str('l1-node-web3-url', env.L1_NODE_WEB3_URL),
L2_NODE_WEB3_URL: config.str('l2-node-web3-url', env.L2_NODE_WEB3_URL),
ADDRESS_MANAGER_ADDRESS: config.str(
'address-manager-address',
env.ADDRESS_MANAGER_ADDRESS
),
MIN_L1_TX_SIZE: config.uint(
'min-l1-tx-size',
parseInt(env.MIN_L1_TX_SIZE, 10)
),
MAX_L1_TX_SIZE: config.uint(
'max-l1-tx-size',
parseInt(env.MAX_L1_TX_SIZE, 10)
),
MAX_TX_BATCH_COUNT: config.uint(
'max-tx-batch-count',
parseInt(env.MAX_TX_BATCH_COUNT, 10)
),
MAX_STATE_BATCH_COUNT: config.uint(
'max-state-batch-count',
parseInt(env.MAX_STATE_BATCH_COUNT, 10)
),
MAX_BATCH_SUBMISSION_TIME: config.uint(
'max-batch-submisison-time',
parseInt(env.MAX_BATCH_SUBMISSION_TIME, 10)
),
POLL_INTERVAL: config.uint(
'poll-interval',
parseInt(env.POLL_INTERVAL, 10)
),
NUM_CONFIRMATIONS: config.uint(
'num-confirmations',
parseInt(env.NUM_CONFIRMATIONS, 10)
),
RESUBMISSION_TIMEOUT: config.uint(
'resubmission-timeout',
parseInt(env.RESUBMISSION_TIMEOUT, 10)
),
FINALITY_CONFIRMATIONS: config.uint(
'finality-confirmations',
parseInt(env.FINALITY_CONFIRMATIONS, 10)
),
RUN_TX_BATCH_SUBMITTER: config.bool(
'run-tx-batch-submitter',
env.RUN_TX_BATCH_SUBMITTER === 'true'
),
RUN_STATE_BATCH_SUBMITTER: config.bool(
'run-state-batch-submitter',
env.RUN_STATE_BATCH_SUBMITTER === 'true'
),
SAFE_MINIMUM_ETHER_BALANCE: config.ufloat(
'safe-minimum-ether-balance',
parseFloat(env.SAFE_MINIMUM_ETHER_BALANCE)
),
CLEAR_PENDING_TXS: config.bool(
'clear-pending-txs',
env.CLEAR_PENDING_TXS === 'true'
),
}
for (const [key, val] of Object.entries(requiredEnvVars)) {
if (val === null || val === undefined) {
logger.warn('Missing environment variable', {
key,
value: val,
})
exit(1)
}
}
if (requiredEnvVars.MIN_L1_TX_SIZE === 0) {
logger.error('Must configure a MIN_L1_TX_SIZE greater than 0')
process.exit(1)
}
const clearPendingTxs = requiredEnvVars.CLEAR_PENDING_TXS
const l2Provider = asL2Provider(
new StaticJsonRpcProvider({
url: requiredEnvVars.L2_NODE_WEB3_URL,
headers: { 'User-Agent': 'batch-submitter' },
})
)
const sequencerSigner: Signer = await getSequencerSigner()
let proposerSigner: Signer = await getProposerSigner()
const sequencerAddress = await sequencerSigner.getAddress()
const proposerAddress = await proposerSigner.getAddress()
// If the sequencer & proposer are the same, use a single wallet
if (sequencerAddress === proposerAddress) {
proposerSigner = sequencerSigner
throw new Error(
'Sequencer and proposer have the same address, multiple transactions may be sent with the same nonce.'
)
}
logger.info('Configured batch submitter addresses', {
sequencerAddress,
proposerAddress,
addressManagerAddress: requiredEnvVars.ADDRESS_MANAGER_ADDRESS,
})
const resubmissionConfig: ResubmissionConfig = {
resubmissionTimeout: requiredEnvVars.RESUBMISSION_TIMEOUT * 1_000,
minGasPriceInGwei: MIN_GAS_PRICE_IN_GWEI,
maxGasPriceInGwei: GAS_THRESHOLD_IN_GWEI,
gasRetryIncrement: GAS_RETRY_INCREMENT,
}
const txBatchTxSubmitter: TransactionSubmitter =
new YnatmTransactionSubmitter(
sequencerSigner,
resubmissionConfig,
requiredEnvVars.NUM_CONFIRMATIONS
)
const txBatchSubmitter = new TransactionBatchSubmitter(
sequencerSigner,
l2Provider,
requiredEnvVars.MIN_L1_TX_SIZE,
requiredEnvVars.MAX_L1_TX_SIZE,
requiredEnvVars.MAX_TX_BATCH_COUNT,
requiredEnvVars.MAX_BATCH_SUBMISSION_TIME * 1_000,
requiredEnvVars.NUM_CONFIRMATIONS,
requiredEnvVars.RESUBMISSION_TIMEOUT * 1_000,
requiredEnvVars.ADDRESS_MANAGER_ADDRESS,
requiredEnvVars.SAFE_MINIMUM_ETHER_BALANCE,
GAS_THRESHOLD_IN_GWEI,
txBatchTxSubmitter,
BLOCK_OFFSET,
VALIDATE_TX_BATCH,
logger.child({ name: TX_BATCH_SUBMITTER_LOG_TAG }),
metrics,
autoFixBatchOptions,
SEQUENCER_BATCH_TYPE
)
const stateBatchTxSubmitter: TransactionSubmitter =
new YnatmTransactionSubmitter(
proposerSigner,
resubmissionConfig,
requiredEnvVars.NUM_CONFIRMATIONS
)
const stateBatchSubmitter = new StateBatchSubmitter(
proposerSigner,
l2Provider,
requiredEnvVars.MIN_L1_TX_SIZE,
requiredEnvVars.MAX_L1_TX_SIZE,
requiredEnvVars.MAX_STATE_BATCH_COUNT,
requiredEnvVars.MAX_BATCH_SUBMISSION_TIME * 1_000,
requiredEnvVars.NUM_CONFIRMATIONS,
requiredEnvVars.RESUBMISSION_TIMEOUT * 1_000,
requiredEnvVars.FINALITY_CONFIRMATIONS,
requiredEnvVars.ADDRESS_MANAGER_ADDRESS,
requiredEnvVars.SAFE_MINIMUM_ETHER_BALANCE,
stateBatchTxSubmitter,
BLOCK_OFFSET,
logger.child({ name: STATE_BATCH_SUBMITTER_LOG_TAG }),
metrics,
FRAUD_SUBMISSION_ADDRESS
)
// Loops infinitely!
const loop = async (
func: () => Promise<TransactionReceipt>,
signer: Signer
): Promise<void> => {
// Clear all pending transactions
if (clearPendingTxs) {
try {
const pendingTxs = await signer.getTransactionCount('pending')
const latestTxs = await signer.getTransactionCount('latest')
if (pendingTxs > latestTxs) {
logger.info(
'Detected pending transactions. Clearing all transactions!'
)
for (let i = latestTxs; i < pendingTxs; i++) {
const response = await signer.sendTransaction({
to: await signer.getAddress(),
value: 0,
nonce: i,
})
logger.info('Submitted empty transaction', {
nonce: i,
txHash: response.hash,
to: response.to,
from: response.from,
})
logger.debug('empty transaction data', {
data: response.data,
})
await signer.provider.waitForTransaction(
response.hash,
requiredEnvVars.NUM_CONFIRMATIONS
)
}
}
} catch (err) {
logger.error('Cannot clear transactions', {
message: err.toString(),
stack: err.stack,
code: err.code,
})
process.exit(1)
}
}
while (true) {
try {
await func()
} catch (err) {
switch (err.code) {
case 'SERVER_ERROR':
logger.error(`Encountered server error with status ${err.status}`, {
message: err.toString(),
stack: err.stack,
code: err.code,
})
break
case 'NETWORK_ERROR':
logger.error('Could not detect network', {
message: err.toString(),
stack: err.stack,
code: err.code,
})
break
default:
logger.error('Unhandled exception during batch submission', {
message: err.toString(),
stack: err.stack,
code: err.code,
})
break
}
logger.info('Retrying...')
}
// Sleep
await new Promise((r) => setTimeout(r, requiredEnvVars.POLL_INTERVAL))
}
}
// Run batch submitters in two seperate infinite loops!
if (requiredEnvVars.RUN_TX_BATCH_SUBMITTER) {
loop(() => txBatchSubmitter.submitNextBatch(), sequencerSigner)
}
if (requiredEnvVars.RUN_STATE_BATCH_SUBMITTER) {
loop(() => stateBatchSubmitter.submitNextBatch(), proposerSigner)
}
if (config.bool('run-metrics-server', env.RUN_METRICS_SERVER === 'true')) {
// Initialize metrics server
await createMetricsServer({
logger,
registry: metrics.registry,
port: config.uint('metrics-port', parseInt(env.METRICS_PORT, 10) || 7300),
hostname: config.str(
'metrics-hostname',
env.METRICS_HOSTNAME || '127.0.0.1'
),
})
}
}
export * from './batch-submitter'
export * from './utils'
export * from './transaction-chain-contract'
/* External Imports */
import { Contract, ethers } from 'ethers'
import {
TransactionResponse,
TransactionRequest,
} from '@ethersproject/abstract-provider'
import {
AppendSequencerBatchParams,
BatchContext,
encodeAppendSequencerBatch,
sequencerBatch,
} from '@eth-optimism/core-utils'
export { encodeAppendSequencerBatch, BatchContext, AppendSequencerBatchParams }
/*
* OVM_CanonicalTransactionChainContract is a wrapper around a normal Ethers contract
* where the `appendSequencerBatch(...)` function uses a specialized encoding for improved efficiency.
*/
export class CanonicalTransactionChainContract extends Contract {
public customPopulateTransaction = {
appendSequencerBatch: async (
batch: AppendSequencerBatchParams
): Promise<ethers.PopulatedTransaction> => {
const nonce = await this.signer.getTransactionCount()
const to = this.address
const data = getEncodedCalldata(batch)
const gasLimit = await this.signer.provider.estimateGas({
to,
from: await this.signer.getAddress(),
data,
})
return {
nonce,
to,
data,
gasLimit,
}
},
}
public async appendSequencerBatch(
batch: AppendSequencerBatchParams,
options?: TransactionRequest
): Promise<TransactionResponse> {
return appendSequencerBatch(this, batch, options)
}
}
/**********************
* Internal Functions *
*********************/
const appendSequencerBatch = async (
OVM_CanonicalTransactionChain: Contract,
batch: AppendSequencerBatchParams,
options?: TransactionRequest
): Promise<TransactionResponse> => {
return OVM_CanonicalTransactionChain.signer.sendTransaction({
to: OVM_CanonicalTransactionChain.address,
data: getEncodedCalldata(batch),
...options,
})
}
const getEncodedCalldata = (params: AppendSequencerBatchParams): string => {
return sequencerBatch.encode(params)
}
import { Signer, ethers, PopulatedTransaction } from 'ethers'
import {
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import * as ynatm from '@eth-optimism/ynatm'
export interface ResubmissionConfig {
resubmissionTimeout: number
minGasPriceInGwei: number
maxGasPriceInGwei: number
gasRetryIncrement: number
}
export type SubmitTransactionFn = (
tx: PopulatedTransaction
) => Promise<TransactionReceipt>
export interface TxSubmissionHooks {
beforeSendTransaction: (tx: PopulatedTransaction) => void
onTransactionResponse: (txResponse: TransactionResponse) => void
}
const getGasPriceInGwei = async (signer: Signer): Promise<number> => {
return parseInt(
ethers.utils.formatUnits(await signer.getGasPrice(), 'gwei'),
10
)
}
export const ynatmRejectOn = (e) => {
// taken almost verbatim from the readme,
// see https://github.com/ethereum-optimism/ynatm.
// immediately rejects on reverts and nonce errors
const errMsg = e.toString().toLowerCase()
const conditions = ['revert', 'nonce']
for (const cond of conditions) {
if (errMsg.includes(cond)) {
return true
}
}
return false
}
export const submitTransactionWithYNATM = async (
tx: PopulatedTransaction,
signer: Signer,
config: ResubmissionConfig,
numConfirmations: number,
hooks: TxSubmissionHooks
): Promise<TransactionReceipt> => {
const sendTxAndWaitForReceipt = async (
gasPrice
): Promise<TransactionReceipt> => {
const fullTx = {
...tx,
gasPrice,
}
hooks.beforeSendTransaction(fullTx)
const txResponse = await signer.sendTransaction(fullTx)
hooks.onTransactionResponse(txResponse)
return signer.provider.waitForTransaction(txResponse.hash, numConfirmations)
}
const minGasPrice = await getGasPriceInGwei(signer)
const receipt = await ynatm.send({
sendTransactionFunction: sendTxAndWaitForReceipt,
minGasPrice: ynatm.toGwei(minGasPrice),
maxGasPrice: ynatm.toGwei(config.maxGasPriceInGwei),
gasPriceScalingFunction: ynatm.LINEAR(config.gasRetryIncrement),
delay: config.resubmissionTimeout,
rejectImmediatelyOnCondition: ynatmRejectOn,
})
return receipt
}
export interface TransactionSubmitter {
submitTransaction(
tx: PopulatedTransaction,
hooks?: TxSubmissionHooks
): Promise<TransactionReceipt>
}
export class YnatmTransactionSubmitter implements TransactionSubmitter {
constructor(
readonly signer: Signer,
readonly ynatmConfig: ResubmissionConfig,
readonly numConfirmations: number
) {}
public async submitTransaction(
tx: PopulatedTransaction,
hooks?: TxSubmissionHooks
): Promise<TransactionReceipt> {
if (!hooks) {
hooks = {
beforeSendTransaction: () => undefined,
onTransactionResponse: () => undefined,
}
}
return submitTransactionWithYNATM(
tx,
this.signer,
this.ynatmConfig,
this.numConfirmations,
hooks
)
}
}
/* External Imports */
import { ethers } from 'hardhat'
import '@nomiclabs/hardhat-ethers'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import sinon from 'sinon'
import scc from '@eth-optimism/contracts/artifacts/contracts/L1/rollup/StateCommitmentChain.sol/StateCommitmentChain.json'
import { getContractInterface } from '@eth-optimism/contracts'
import { smockit, MockContract } from '@eth-optimism/smock'
import { getContractFactory } from 'old-contracts'
import { QueueOrigin, Batch, remove0x } from '@eth-optimism/core-utils'
import { Logger, Metrics } from '@eth-optimism/common-ts'
/* Internal Imports */
import { MockchainProvider } from './mockchain-provider'
import { expect } from '../setup'
import {
CanonicalTransactionChainContract,
TransactionBatchSubmitter as RealTransactionBatchSubmitter,
StateBatchSubmitter,
TX_BATCH_SUBMITTER_LOG_TAG,
STATE_BATCH_SUBMITTER_LOG_TAG,
YnatmTransactionSubmitter,
ResubmissionConfig,
} from '../../src'
import {
makeAddressManager,
setProxyTarget,
FORCE_INCLUSION_PERIOD_SECONDS,
} from '../helpers'
const EXAMPLE_STATE_ROOT =
'0x16b7f83f409c7195b1f4fde5652f1b54a4477eacb6db7927691becafba5f8801'
const MAX_GAS_LIMIT = 8_000_000
const MAX_TX_SIZE = 100_000
const MIN_TX_SIZE = 1_000
const MIN_GAS_PRICE_IN_GWEI = 1
const GAS_RETRY_INCREMENT = 5
const GAS_THRESHOLD_IN_GWEI = 120
// Helper functions
interface QueueElement {
queueRoot: string
timestamp: number
blockNumber: number
}
const getQueueElement = async (
ctcContract: Contract,
nextQueueIndex?: number
): Promise<QueueElement> => {
if (!nextQueueIndex) {
nextQueueIndex = await ctcContract.getNextQueueIndex()
}
const nextQueueElement = await ctcContract.getQueueElement(nextQueueIndex)
return nextQueueElement
}
// A transaction batch submitter which skips the validate batch check
class TransactionBatchSubmitter extends RealTransactionBatchSubmitter {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async _validateBatch(batch: Batch): Promise<boolean> {
return true
}
}
const testMetrics = new Metrics({ prefix: 'bs_test' })
describe('BatchSubmitter', () => {
let signer: Signer
let sequencer: Signer
before(async () => {
;[signer, sequencer] = await ethers.getSigners()
})
let AddressManager: Contract
let Mock__OVM_ExecutionManager: MockContract
let Mock__OVM_BondManager: MockContract
let Mock__OVM_StateCommitmentChain: MockContract
before(async () => {
AddressManager = await makeAddressManager()
await AddressManager.setAddress(
'OVM_Sequencer',
await sequencer.getAddress()
)
Mock__OVM_ExecutionManager = await smockit(
await getContractFactory('OVM_ExecutionManager')
)
Mock__OVM_BondManager = await smockit(
await getContractFactory('OVM_BondManager')
)
Mock__OVM_StateCommitmentChain = await smockit(
await getContractFactory('OVM_StateCommitmentChain')
)
await setProxyTarget(
AddressManager,
'OVM_ExecutionManager',
Mock__OVM_ExecutionManager
)
await setProxyTarget(
AddressManager,
'OVM_BondManager',
Mock__OVM_BondManager
)
await setProxyTarget(
AddressManager,
'OVM_StateCommitmentChain',
Mock__OVM_StateCommitmentChain
)
Mock__OVM_StateCommitmentChain.smocked.canOverwrite.will.return.with(false)
Mock__OVM_ExecutionManager.smocked.getMaxTransactionGasLimit.will.return.with(
MAX_GAS_LIMIT
)
Mock__OVM_BondManager.smocked.isCollateralized.will.return.with(true)
})
let Factory__OVM_CanonicalTransactionChain: ContractFactory
let Factory__OVM_StateCommitmentChain: ContractFactory
before(async () => {
Factory__OVM_CanonicalTransactionChain = await getContractFactory(
'OVM_CanonicalTransactionChain'
)
Factory__OVM_CanonicalTransactionChain =
Factory__OVM_CanonicalTransactionChain.connect(signer)
Factory__OVM_StateCommitmentChain = await getContractFactory(
'OVM_StateCommitmentChain'
)
Factory__OVM_StateCommitmentChain =
Factory__OVM_StateCommitmentChain.connect(signer)
})
let OVM_CanonicalTransactionChain: CanonicalTransactionChainContract
let OVM_StateCommitmentChain: Contract
let l2Provider: MockchainProvider
beforeEach(async () => {
const unwrapped_OVM_CanonicalTransactionChain =
await Factory__OVM_CanonicalTransactionChain.deploy(
AddressManager.address,
FORCE_INCLUSION_PERIOD_SECONDS
)
await unwrapped_OVM_CanonicalTransactionChain.init()
await AddressManager.setAddress(
'OVM_CanonicalTransactionChain',
unwrapped_OVM_CanonicalTransactionChain.address
)
await AddressManager.setAddress(
'CanonicalTransactionChain',
unwrapped_OVM_CanonicalTransactionChain.address
)
OVM_CanonicalTransactionChain = new CanonicalTransactionChainContract(
unwrapped_OVM_CanonicalTransactionChain.address,
getContractInterface('CanonicalTransactionChain'),
sequencer
)
const unwrapped_OVM_StateCommitmentChain =
await Factory__OVM_StateCommitmentChain.deploy(
AddressManager.address,
0, // fraudProofWindowSeconds
0 // sequencerPublishWindowSeconds
)
await unwrapped_OVM_StateCommitmentChain.init()
await AddressManager.setAddress(
'OVM_StateCommitmentChain',
unwrapped_OVM_StateCommitmentChain.address
)
await AddressManager.setAddress(
'StateCommitmentChain',
unwrapped_OVM_StateCommitmentChain.address
)
OVM_StateCommitmentChain = new Contract(
unwrapped_OVM_StateCommitmentChain.address,
getContractInterface('StateCommitmentChain'),
sequencer
)
l2Provider = new MockchainProvider(
OVM_CanonicalTransactionChain.address,
OVM_StateCommitmentChain.address
)
})
afterEach(() => {
sinon.restore()
})
const createBatchSubmitter = (timeout: number): TransactionBatchSubmitter => {
const resubmissionConfig: ResubmissionConfig = {
resubmissionTimeout: 100000,
minGasPriceInGwei: MIN_GAS_PRICE_IN_GWEI,
maxGasPriceInGwei: GAS_THRESHOLD_IN_GWEI,
gasRetryIncrement: GAS_RETRY_INCREMENT,
}
const txBatchTxSubmitter = new YnatmTransactionSubmitter(
sequencer,
resubmissionConfig,
1
)
return new TransactionBatchSubmitter(
sequencer,
l2Provider as any,
MIN_TX_SIZE,
MAX_TX_SIZE,
10,
timeout,
1,
100000,
AddressManager.address,
1,
GAS_THRESHOLD_IN_GWEI,
txBatchTxSubmitter,
1,
false,
new Logger({ name: TX_BATCH_SUBMITTER_LOG_TAG }),
testMetrics,
{
fixDoublePlayedDeposits: false,
fixMonotonicity: false,
fixSkippedDeposits: false,
},
'legacy'
)
}
describe('TransactionBatchSubmitter', () => {
describe('submitNextBatch', () => {
let batchSubmitter
beforeEach(async () => {
for (let i = 1; i < 15; i++) {
await OVM_CanonicalTransactionChain.enqueue(
'0x' + '01'.repeat(20),
50_000,
'0x' + i.toString().repeat(64),
{
gasLimit: 1_000_000,
}
)
}
batchSubmitter = createBatchSubmitter(0)
})
it('should submit a sequencer batch correctly', async () => {
l2Provider.setNumBlocksToReturn(5)
const nextQueueElement = await getQueueElement(
OVM_CanonicalTransactionChain
)
l2Provider.setL2BlockData(
{
rawTransaction: '0x1234',
l1BlockNumber: nextQueueElement.blockNumber - 1,
txType: 0,
queueOrigin: QueueOrigin.Sequencer,
l1TxOrigin: null,
} as any,
nextQueueElement.timestamp - 1
)
let receipt = await batchSubmitter.submitNextBatch()
let logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(0) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(0) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(6) // _totalElements
receipt = await batchSubmitter.submitNextBatch()
logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(0) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(0) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(11) // _totalElements
})
it('should submit a queue batch correctly', async () => {
l2Provider.setNumBlocksToReturn(5)
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
let receipt = await batchSubmitter.submitNextBatch()
let logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(0) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(6) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(6) // _totalElements
receipt = await batchSubmitter.submitNextBatch()
logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(6) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(5) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(11) // _totalElements
})
it('should submit a batch with both queue and sequencer chain elements', async () => {
l2Provider.setNumBlocksToReturn(10) // For this batch we'll return 10 elements!
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
// Turn blocks 3-5 into sequencer txs
const nextQueueElement = await getQueueElement(
OVM_CanonicalTransactionChain,
2
)
l2Provider.setL2BlockData(
{
rawTransaction: '0x1234',
l1BlockNumber: nextQueueElement.blockNumber - 1,
txType: 1,
queueOrigin: QueueOrigin.Sequencer,
l1TxOrigin: null,
} as any,
nextQueueElement.timestamp - 1,
'', // blank stateRoot
3,
6
)
const receipt = await batchSubmitter.submitNextBatch()
const logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(0) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(8) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(11) // _totalElements
})
it('should submit a small batch only after the timeout', async () => {
l2Provider.setNumBlocksToReturn(2)
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
// Create a batch submitter with a long timeout & make sure it doesn't submit the batches one after another
const longTimeout = 10_000
batchSubmitter = createBatchSubmitter(longTimeout)
let receipt = await batchSubmitter.submitNextBatch()
expect(receipt).to.not.be.undefined
receipt = await batchSubmitter.submitNextBatch()
// The receipt should be undefined because that means it didn't submit
expect(receipt).to.be.undefined
// This time create a batch submitter with a short timeout & it should submit batches after the timeout is reached
const shortTimeout = 5
batchSubmitter = createBatchSubmitter(shortTimeout)
receipt = await batchSubmitter.submitNextBatch()
expect(receipt).to.not.be.undefined
// Sleep for the short timeout
await new Promise((r) => setTimeout(r, shortTimeout))
receipt = await batchSubmitter.submitNextBatch()
// The receipt should NOT be undefined because that means it successfully submitted!
expect(receipt).to.not.be.undefined
})
it('should not submit if gas price is over threshold', async () => {
l2Provider.setNumBlocksToReturn(2)
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
const highGasPriceWei = BigNumber.from(200).mul(1_000_000_000)
sinon
.stub(sequencer, 'getGasPrice')
.callsFake(async () => highGasPriceWei)
const receipt = await batchSubmitter.submitNextBatch()
expect(sequencer.getGasPrice).to.have.been.calledOnce
expect(receipt).to.be.undefined
})
it('should submit if gas price is not over threshold', async () => {
l2Provider.setNumBlocksToReturn(2)
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
const lowGasPriceWei = BigNumber.from(2).mul(1_000_000_000)
sinon
.stub(sequencer, 'getGasPrice')
.callsFake(async () => lowGasPriceWei)
const receipt = await batchSubmitter.submitNextBatch()
expect(sequencer.getGasPrice).to.have.been.calledTwice
expect(receipt).to.not.be.undefined
})
})
})
describe('StateBatchSubmitter', () => {
let txBatchSubmitter
let stateBatchSubmitter
beforeEach(async () => {
for (let i = 1; i < 15; i++) {
await OVM_CanonicalTransactionChain.enqueue(
'0x' + '01'.repeat(20),
50_000,
'0x' + i.toString().repeat(64),
{
gasLimit: 1_000_000,
}
)
}
txBatchSubmitter = createBatchSubmitter(0)
l2Provider.setNumBlocksToReturn(5)
const nextQueueElement = await getQueueElement(
OVM_CanonicalTransactionChain
)
l2Provider.setL2BlockData(
{
rawTransaction: '0x1234',
l1BlockNumber: nextQueueElement.blockNumber - 1,
txType: 0,
queueOrigin: QueueOrigin.Sequencer,
l1TxOrigin: null,
} as any,
nextQueueElement.timestamp - 1,
EXAMPLE_STATE_ROOT // example stateRoot
)
// submit a batch of transactions to enable state batch submission
await txBatchSubmitter.submitNextBatch()
const resubmissionConfig: ResubmissionConfig = {
resubmissionTimeout: 100000,
minGasPriceInGwei: MIN_GAS_PRICE_IN_GWEI,
maxGasPriceInGwei: GAS_THRESHOLD_IN_GWEI,
gasRetryIncrement: GAS_RETRY_INCREMENT,
}
const stateBatchTxSubmitter = new YnatmTransactionSubmitter(
sequencer,
resubmissionConfig,
1
)
stateBatchSubmitter = new StateBatchSubmitter(
sequencer,
l2Provider as any,
MIN_TX_SIZE,
MAX_TX_SIZE,
10, // maxBatchSize
0,
1,
100000,
0, // finalityConfirmations
AddressManager.address,
1,
stateBatchTxSubmitter,
1,
new Logger({ name: STATE_BATCH_SUBMITTER_LOG_TAG }),
testMetrics,
'0x' + '01'.repeat(20) // placeholder for fraudSubmissionAddress
)
})
describe('submitNextBatch', () => {
it('should submit a state batch after a transaction batch', async () => {
const receipt = await stateBatchSubmitter.submitNextBatch()
expect(receipt).to.not.be.undefined
const iface = new ethers.utils.Interface(scc.abi)
const parsedLogs = iface.parseLog(receipt.logs[0])
expect(parsedLogs.eventFragment.name).to.eq('StateBatchAppended')
expect(parsedLogs.args._batchIndex.toNumber()).to.eq(0)
expect(parsedLogs.args._batchSize.toNumber()).to.eq(6)
expect(parsedLogs.args._prevTotalElements.toNumber()).to.eq(0)
})
})
})
})
/* External Imports */
import { providers, BigNumber } from 'ethers'
import {
BlockWithTransactions,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { L2Transaction, L2Block, RollupInfo } from '@eth-optimism/core-utils'
/**
* Unformatted Transaction & Blocks. This exists because Geth currently
* does not return the correct fields & so this code renames those
* poorly named fields
*/
interface UnformattedL2Transaction extends TransactionResponse {
l1BlockNumber: string
l1MessageSender: string
signatureHashType: string
queueOrigin: string
rawTransaction: string
}
interface UnformattedL2Block extends BlockWithTransactions {
stateRoot: string
transactions: [UnformattedL2Transaction]
}
export class MockchainProvider extends providers.JsonRpcProvider {
public mockBlockNumber: number = 1
public numBlocksToReturn: number = 2
public mockBlocks: L2Block[] = []
public ctcAddr: string
public sccAddr: string
constructor(ctcAddr: string, sccAddr: string) {
super('https://optimism.io')
for (const block of BLOCKS) {
if (block.number === 0) {
// No need to convert genesis to an L2Block because it has no txs
this.mockBlocks.push(block)
continue
}
this.mockBlocks.push(this._toL2Block(block))
this.ctcAddr = ctcAddr
this.sccAddr = sccAddr
}
}
public async getBlockNumber(): Promise<number> {
// Increment our mock block number every time
if (
this.mockBlockNumber + this.numBlocksToReturn <
this.mockBlocks.length
) {
this.mockBlockNumber += this.numBlocksToReturn
} else {
return this.mockBlocks.length - 1
}
return this.mockBlockNumber
}
public async send(endpoint: string, params: []): Promise<any> {
switch (endpoint) {
case 'eth_chainId':
return this.chainId()
case 'rollup_getInfo':
const info: RollupInfo = {
mode: 'sequencer',
syncing: false,
ethContext: {
timestamp: 0,
blockNumber: 0,
},
rollupContext: {
index: 0,
queueIndex: 0,
},
}
return info
case 'eth_getBlockByNumber':
if (params.length === 0) {
throw new Error(`Invalid params for ${endpoint}`)
}
const blockNumber = BigNumber.from((params as any)[0]).toNumber()
return this.mockBlocks[blockNumber]
default:
throw new Error('Unsupported endpoint!')
}
}
public setNumBlocksToReturn(numBlocks: number): void {
this.numBlocksToReturn = numBlocks
}
public setL2BlockData(
tx: L2Transaction,
timestamp?: number,
stateRoot?: string,
start: number = 1,
end: number = this.mockBlocks.length
) {
for (let i = start; i < end; i++) {
this.mockBlocks[i].timestamp = timestamp
? timestamp
: this.mockBlocks[i].timestamp
this.mockBlocks[i].transactions[0] = {
...this.mockBlocks[i].transactions[0],
...tx,
}
this.mockBlocks[i].stateRoot = stateRoot
}
}
public async getBlockWithTransactions(blockNumber: number): Promise<L2Block> {
return this.mockBlocks[blockNumber]
}
public chainId(): number {
// We know that mockBlocks will always have at least 1 value
return this.mockBlocks[1].transactions[0].chainId
}
private _toL2Block(block: UnformattedL2Block): L2Block {
const l1BlockNumber: number = parseInt(
block.transactions[0].l1BlockNumber,
10
)
const queueOrigin: string = block.transactions[0].queueOrigin
const l1TxOrigin: string = block.transactions[0].l1MessageSender
const l2Transaction: L2Transaction = {
...block.transactions[0],
// Rename the incorrectly named fields
l1TxOrigin,
queueOrigin,
l1BlockNumber,
}
// Add an interface here to fix the type casing into L2Block during Object.assign
interface PartialL2Block {
transactions: [L2Transaction]
}
const partialBlock: PartialL2Block = {
transactions: [l2Transaction],
}
const l2Block: L2Block = { ...block, ...partialBlock }
return l2Block
}
}
const BLOCKS = JSON.parse(`
[
{
"hash":"0xbc27fdbd1fee6e001438709ef57210bb7b2b1b8c23b65acb2d79161f4dc3cf05",
"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"number":0,
"timestamp":1603651804,
"nonce":"0x0000000000000042",
"difficulty":1,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0x0000000000000000000000000000000000000000",
"extraData":"0x1234",
"transactions":[
]
},
{
"hash":"0x05a7f5c5fce57346f59355184daa58822f97a32e4327fe6ef4a1c37dfd36f2f0",
"parentHash":"0x64e89492b3ea72b9f9f0f4566e5198e19d7bfa583619c54c33872c7112aec9cd",
"number":1,
"timestamp":1603404102,
"nonce":"0x0000000000000042",
"difficulty":131072,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x688ddd1acc3cbccd9112018eac6c78744f43140d408128b8ed0392c7ee28966e",
"blockHash":"0x05a7f5c5fce57346f59355184daa58822f97a32e4327fe6ef4a1c37dfd36f2f0",
"blockNumber":1,
"transactionIndex":0,
"confirmations":16,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0x05bc67"
},
"to":null,
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":0,
"data":"0x608060405234801561001057600080fd5b50600080546001600160a01b03191633178082556040516001600160a01b039190911691907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a361056a806100696000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063715018a61461005c5780638da5cb5b146100665780639b2ea4bd1461008a578063bf40fac11461013b578063f2fde38b146101e1575b600080fd5b610064610207565b005b61006e6102b0565b604080516001600160a01b039092168252519081900360200190f35b610064600480360360408110156100a057600080fd5b8101906020810181356401000000008111156100bb57600080fd5b8201836020820111156100cd57600080fd5b803590602001918460018302840111640100000000831117156100ef57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b031691506102bf9050565b61006e6004803603602081101561015157600080fd5b81019060208101813564010000000081111561016c57600080fd5b82018360208201111561017e57600080fd5b803590602001918460018302840111640100000000831117156101a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610362945050505050565b610064600480360360208110156101f757600080fd5b50356001600160a01b0316610391565b6000546001600160a01b03163314610266576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000546001600160a01b031681565b6000546001600160a01b0316331461031e576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b806001600061032c85610490565b815260200190815260200160002060006101000a8154816001600160a01b0302191690836001600160a01b031602179055505050565b60006001600061037184610490565b81526020810191909152604001600020546001600160a01b031692915050565b6000546001600160a01b031633146103f0576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b0381166104355760405162461bcd60e51b815260040180806020018281038252602d815260200180610508602d913960400191505060405180910390fd5b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000816040516020018082805190602001908083835b602083106104c55780518252601f1990920191602091820191016104a6565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120905091905056fe4f776e61626c653a206e6577206f776e65722063616e6e6f7420626520746865207a65726f2061646472657373a26469706673582212204367ffc2e6671623708150e2d0cff4c12cf566722a26b4748555d789953e2d2264736f6c63430007000033",
"r":"0x2babe370e2e422a38386a5a96cd3bf16772ddbbf8c9dab8aadf4416fff557756",
"s":"0x213ab994b50ed4a38e2de390f851d88cb66dd238a27d89246616b34eb8e859df",
"v":62710,
"creates":"0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA",
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0xb5903854b196abb9b7d1e3c2f5f8f9519b4fedefc21d72f3d92c74f128afcd46",
"parentHash":"0x05a7f5c5fce57346f59355184daa58822f97a32e4327fe6ef4a1c37dfd36f2f0",
"number":2,
"timestamp":1603404103,
"nonce":"0x0000000000000042",
"difficulty":131136,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0xeae52be001bfea0b3d8de2de5edff5c0c39d26f6e6ab0fc6623cd24913dbf150",
"blockHash":"0xb5903854b196abb9b7d1e3c2f5f8f9519b4fedefc21d72f3d92c74f128afcd46",
"blockNumber":2,
"transactionIndex":0,
"confirmations":15,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0xaeab"
},
"to":"0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":1,
"data":"0x9b2ea4bd000000000000000000000000000000000000000000000000000000000000004000000000000000000000000063fc2ad3d021a4d7e64323529a55a9442c444da0000000000000000000000000000000000000000000000000000000000000000d4f564d5f53657175656e63657200000000000000000000000000000000000000",
"r":"0x530be666add21a30fe9a0fadee8072d4d8ccb2b80a9fe07c7e0591c5a1e5f375",
"s":"0x5954b17cd0f6299ee54f20853bcfdfc870f2fc271f0433fd592b8b0fc0329aa4",
"v":62709,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x7504e7104a2d80e810758c1cc2736028ffe5632a2ec988da740c93b3cfa03945",
"parentHash":"0xb5903854b196abb9b7d1e3c2f5f8f9519b4fedefc21d72f3d92c74f128afcd46",
"number":3,
"timestamp":1603404104,
"nonce":"0x0000000000000042",
"difficulty":131200,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x79f4dc191018a37a8b71b4a5aff02ce501b0341fc72708c8095845e61be15c02",
"blockHash":"0x7504e7104a2d80e810758c1cc2736028ffe5632a2ec988da740c93b3cfa03945",
"blockNumber":3,
"transactionIndex":0,
"confirmations":14,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0xafb0"
},
"to":"0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":2,
"data":"0x9b2ea4bd0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000420000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000224f564d5f4465636f6d7072657373696f6e507265636f6d70696c6541646472657373000000000000000000000000000000000000000000000000000000000000",
"r":"0x388b6b7e4fe10128e92507042971b864edd535d8f3cb70c61247daed1dc734c1",
"s":"0x6ad11366b26fa59ce73bc73fa320560f46ffb6df3142adbd16ebce0bcc3ba9f5",
"v":62710,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x6587aa5da64944673741bdd3299c0d2471c2fdbfcd94713d511ab7240d127bbb",
"parentHash":"0x7504e7104a2d80e810758c1cc2736028ffe5632a2ec988da740c93b3cfa03945",
"number":4,
"timestamp":1603404105,
"nonce":"0x0000000000000042",
"difficulty":131264,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x0584fbd34096ffc0fc96dbdf1d8ee574c3eda9e07bf85aed9862542416b1a007",
"blockHash":"0x6587aa5da64944673741bdd3299c0d2471c2fdbfcd94713d511ab7240d127bbb",
"blockNumber":4,
"transactionIndex":0,
"confirmations":13,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0x02e294"
},
"to":null,
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":3,
"data":"0x608060405234801561001057600080fd5b50600080546001600160a01b03191633179055610213806100326000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063776d1a0114610077575b60015460408051602036601f8101829004820283018201909352828252610075936001600160a01b0316926000918190840183828082843760009201919091525061009d92505050565b005b6100756004803603602081101561008d57600080fd5b50356001600160a01b031661015d565b60006060836001600160a01b0316836040518082805190602001908083835b602083106100db5780518252601f1990920191602091820191016100bc565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461013d576040519150601f19603f3d011682016040523d82523d6000602084013e610142565b606091505b5091509150811561015557805160208201f35b805160208201fd5b6000546001600160a01b031633141561019057600180546001600160a01b0319166001600160a01b0383161790556101da565b60015460408051602036601f81018290048202830182019093528282526101da936001600160a01b0316926000918190840183828082843760009201919091525061009d92505050565b5056fea2646970667358221220293887d48c4c1c34de868edf3e9a6be82327946c76d71f7c2023e67f556c6ecb64736f6c63430007000033",
"r":"0x2e420851664bb81c0d5d0bd1a805661fc1f83922b92e1d9e0e57c9184eddec0e",
"s":"0x5e00184c9b50ed54e714231af904caed92cf47ee309fc3604793d7d32a9f4988",
"v":62709,
"creates":"0x94BA4d5Ebb0e05A50e977FFbF6e1a1Ee3D89299c",
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x7a215669ab018e508473ef76b42d19cc4228cc5cc855089dc04cb22555fcf555",
"parentHash":"0x6587aa5da64944673741bdd3299c0d2471c2fdbfcd94713d511ab7240d127bbb",
"number":5,
"timestamp":1603404106,
"nonce":"0x0000000000000042",
"difficulty":131328,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0xc8243d8b9bf79624f8930527a983be3581cb7d969ae76d3e5962f9db2be7b71a",
"blockHash":"0x7a215669ab018e508473ef76b42d19cc4228cc5cc855089dc04cb22555fcf555",
"blockNumber":5,
"transactionIndex":0,
"confirmations":12,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0xa93c"
},
"to":"0x94BA4d5Ebb0e05A50e977FFbF6e1a1Ee3D89299c",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":4,
"data":"0x776d1a01000000000000000000000000e9a9a643588daa154de182f88a5b04e8745909c2",
"r":"0x2de73fc5aec124cc9cf0fa54fc8492692a48b8921a32d31f46f5d431fdeea7a0",
"s":"0x4e813ae50bc7705fb023d79429ed6cc92367cb5209460e6ea3904c014b06cd4d",
"v":62709,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0xb4c01e6e867856dce9b451ba67273f86bdc584ff398ed5b8c46f70ea37b1002f",
"parentHash":"0x7a215669ab018e508473ef76b42d19cc4228cc5cc855089dc04cb22555fcf555",
"number":6,
"timestamp":1603404107,
"nonce":"0x0000000000000042",
"difficulty":131392,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0xa5bf13c2638f1d99a26c4c7d867f1bc64a34ebf141524b35840c879536e69d69",
"blockHash":"0xb4c01e6e867856dce9b451ba67273f86bdc584ff398ed5b8c46f70ea37b1002f",
"blockNumber":6,
"transactionIndex":0,
"confirmations":11,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0xaeff"
},
"to":"0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":5,
"data":"0x9b2ea4bd000000000000000000000000000000000000000000000000000000000000004000000000000000000000000094ba4d5ebb0e05a50e977ffbf6e1a1ee3d89299c00000000000000000000000000000000000000000000000000000000000000144f564d5f457865637574696f6e4d616e61676572000000000000000000000000",
"r":"0x42a7ca8603050e58d948df4573374746fabc8542c3fabc1d3b03391c2e50ae3b",
"s":"0x99b52df93bddbd15393ec9b6649c569ec9f65b1e7dda62b7a81baf32bd951a36",
"v":62709,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x9f8a3aae03d0f8f769d5af64583c1ee32458bd5b63bf6436147e624ef3297c69",
"parentHash":"0xb4c01e6e867856dce9b451ba67273f86bdc584ff398ed5b8c46f70ea37b1002f",
"number":7,
"timestamp":1603404108,
"nonce":"0x0000000000000042",
"difficulty":131456,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x967c90bfcb19090f0b73b0d0ca4d5f866160d7628d84412fada18abc50e3da69",
"blockHash":"0x9f8a3aae03d0f8f769d5af64583c1ee32458bd5b63bf6436147e624ef3297c69",
"blockNumber":7,
"transactionIndex":0,
"confirmations":10,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0x02e294"
},
"to":null,
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":6,
"data":"0x608060405234801561001057600080fd5b50600080546001600160a01b03191633179055610213806100326000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063776d1a0114610077575b60015460408051602036601f8101829004820283018201909352828252610075936001600160a01b0316926000918190840183828082843760009201919091525061009d92505050565b005b6100756004803603602081101561008d57600080fd5b50356001600160a01b031661015d565b60006060836001600160a01b0316836040518082805190602001908083835b602083106100db5780518252601f1990920191602091820191016100bc565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461013d576040519150601f19603f3d011682016040523d82523d6000602084013e610142565b606091505b5091509150811561015557805160208201f35b805160208201fd5b6000546001600160a01b031633141561019057600180546001600160a01b0319166001600160a01b0383161790556101da565b60015460408051602036601f81018290048202830182019093528282526101da936001600160a01b0316926000918190840183828082843760009201919091525061009d92505050565b5056fea2646970667358221220293887d48c4c1c34de868edf3e9a6be82327946c76d71f7c2023e67f556c6ecb64736f6c63430007000033",
"r":"0x16f49916bda30884d49df7f83c60ca49899fd21311e4cd4b464ac52bfa722b40",
"s":"0x3a2873207cbcba218ff71bb7e3916ea809c393c2407c03a70bbf4e393cbebfcb",
"v":62709,
"creates":"0x956dA338C1518a7FB213042b70c60c021aeBd554",
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x808315e40a80d00bf171b8ba924b451fb7936d446177c9e3545303b2ef830801",
"parentHash":"0x9f8a3aae03d0f8f769d5af64583c1ee32458bd5b63bf6436147e624ef3297c69",
"number":8,
"timestamp":1603404109,
"nonce":"0x0000000000000042",
"difficulty":131520,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x3dabdb84873e50c3b26df28ead1c053451a340b20d945a8cf8bddf5b5ff11775",
"blockHash":"0x808315e40a80d00bf171b8ba924b451fb7936d446177c9e3545303b2ef830801",
"blockNumber":8,
"transactionIndex":0,
"confirmations":9,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0xa93c"
},
"to":"0x956dA338C1518a7FB213042b70c60c021aeBd554",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":7,
"data":"0x776d1a01000000000000000000000000048b45c16e9631d3f630106c6086ec21a30cdf60",
"r":"0x553525e3656dfb41299a9f2a0d1a0058445e017bb98992de6d9b762203cf975a",
"s":"0x74b9f344513fa2881cfcec5a4c20027bb905de5c52a8e584da6767d946cd465d",
"v":62710,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0xed13ac7f6bf594309a325c1558e4a3032f745e66ee0ffc2f95a701562b53dd13",
"parentHash":"0x808315e40a80d00bf171b8ba924b451fb7936d446177c9e3545303b2ef830801",
"number":9,
"timestamp":1603404110,
"nonce":"0x0000000000000042",
"difficulty":131584,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x2b9e216b390804c731f3ffc0630e522865bff35d0c539f377d204e94a346eddd",
"blockHash":"0xed13ac7f6bf594309a325c1558e4a3032f745e66ee0ffc2f95a701562b53dd13",
"blockNumber":9,
"transactionIndex":0,
"confirmations":8,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0xaf2f"
},
"to":"0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":8,
"data":"0x9b2ea4bd0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000956da338c1518a7fb213042b70c60c021aebd55400000000000000000000000000000000000000000000000000000000000000184f564d5f5374617465436f6d6d69746d656e74436861696e0000000000000000",
"r":"0x6318ce7714d7aefc9add30ebcf9d657a01b50c308c689c6f7d5567d806b6914e",
"s":"0xfe4d90d895f4b18b89309d768a383334bf5d622d367051079ac0e401b23960e0",
"v":62709,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x65f3831f69d1e746f30d07a3def1eefdcc6650ee0714cbac795c2be3ce9430a2",
"parentHash":"0xed13ac7f6bf594309a325c1558e4a3032f745e66ee0ffc2f95a701562b53dd13",
"number":10,
"timestamp":1603404111,
"nonce":"0x0000000000000042",
"difficulty":131648,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0xa77aca829b5bc91a16c11efb4dee280345851fccbd83b1469f746349894a46ab",
"blockHash":"0x65f3831f69d1e746f30d07a3def1eefdcc6650ee0714cbac795c2be3ce9430a2",
"blockNumber":10,
"transactionIndex":0,
"confirmations":7,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0x24f27e"
},
"to":null,
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":9,
"data":"0x12345678",
"r":"0x06e79a060942823dc5b328a5b059b58cf42372c03617122139deb5b7844c043d",
"s":"0xfabd07fab3f36816397917ae8c048a4675d34d4ca3f7b06ca6595796a159d359",
"v":62710,
"creates":"0x6454C9d69a4721feBA60e26A367bD4D56196Ee7c",
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x709bb2a24ac6120a12bf56e806cfcf3ceab71a41408b0685892800eebc4e3a45",
"parentHash":"0x65f3831f69d1e746f30d07a3def1eefdcc6650ee0714cbac795c2be3ce9430a2",
"number":11,
"timestamp":1603404112,
"nonce":"0x0000000000000042",
"difficulty":131712,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x2b5cd67036954f3c4cce09951c076adb3d0517750167e712562abba7910bd536",
"blockHash":"0x709bb2a24ac6120a12bf56e806cfcf3ceab71a41408b0685892800eebc4e3a45",
"blockNumber":11,
"transactionIndex":0,
"confirmations":6,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0x0f4240"
},
"to":"0x6454C9d69a4721feBA60e26A367bD4D56196Ee7c",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":10,
"data":"0x6fee07e00000000000000000000000000101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000c350000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000201111111111111111111111111111111111111111111111111111111111111111",
"r":"0x7538f2153c482a762f133f1438b30c8874887f6da52f04adeba5cc65632ee661",
"s":"0x3dc3a219d164b159ad61f874ccb5c75766cb1d73954196eaba142d890b299d0e",
"v":62710,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x13e683738bac942ae739e45b0a0e451b20d7e986d9463a2dac0acf2015e8d09b",
"parentHash":"0x709bb2a24ac6120a12bf56e806cfcf3ceab71a41408b0685892800eebc4e3a45",
"number":12,
"timestamp":1603404113,
"nonce":"0x0000000000000042",
"difficulty":131776,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x339282191d55c5b9fc53d68f199c53ea9b946c9975ed34f15312acd8a70054f7",
"blockHash":"0x13e683738bac942ae739e45b0a0e451b20d7e986d9463a2dac0acf2015e8d09b",
"blockNumber":12,
"transactionIndex":0,
"confirmations":5,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0x0f4240"
},
"to":"0x6454C9d69a4721feBA60e26A367bD4D56196Ee7c",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":11,
"data":"0x6fee07e00000000000000000000000000101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000c350000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000202222222222222222222222222222222222222222222222222222222222222222",
"r":"0x4ab1b83146fbfa4f0cce0110149c2c52e57971ce7cbe5b97a3fd3086bf9f0935",
"s":"0x11dac6b6c1e1d66a89d833ddd230120e6ecdedf49ae9fb38496f4385cb80057d",
"v":62709,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x382c468636dafe28d7b3aad0f0521c2e7b9395fc0146b16502d85fa2a49ffe7b",
"parentHash":"0x13e683738bac942ae739e45b0a0e451b20d7e986d9463a2dac0acf2015e8d09b",
"number":13,
"timestamp":1603404114,
"nonce":"0x0000000000000042",
"difficulty":131840,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x8c260f877daaaaeba12b41da6763ce2a641a55ec2e545c4dcbc211b340480e93",
"blockHash":"0x382c468636dafe28d7b3aad0f0521c2e7b9395fc0146b16502d85fa2a49ffe7b",
"blockNumber":13,
"transactionIndex":0,
"confirmations":4,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0x0f4240"
},
"to":"0x6454C9d69a4721feBA60e26A367bD4D56196Ee7c",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":12,
"data":"0x6fee07e00000000000000000000000000101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000c350000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000203333333333333333333333333333333333333333333333333333333333333333",
"r":"0x52f5316fc04aafc95110ac6be222ed656cd0a4ace50fac3e09384408c8b7e32a",
"s":"0x03de7779650526057629e842c45017cfd2dc19137d309cd99f244c0ce9b13186",
"v":62709,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x5e5f592a3c96a45bc7fceea8681d066af84709dc155d305fe4e3d68f0bb7bd63",
"parentHash":"0x382c468636dafe28d7b3aad0f0521c2e7b9395fc0146b16502d85fa2a49ffe7b",
"number":14,
"timestamp":1603404115,
"nonce":"0x0000000000000042",
"difficulty":131904,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x2fbd6a3a19c6e5778762619ecfd849b4c3beb35bce0c99b1f357077f3380fa9e",
"blockHash":"0x5e5f592a3c96a45bc7fceea8681d066af84709dc155d305fe4e3d68f0bb7bd63",
"blockNumber":14,
"transactionIndex":0,
"confirmations":3,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0x0f4240"
},
"to":"0x6454C9d69a4721feBA60e26A367bD4D56196Ee7c",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":13,
"data":"0x6fee07e00000000000000000000000000101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000c350000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000204444444444444444444444444444444444444444444444444444444444444444",
"r":"0x546c5a59b1753dd9b9f1f7ff0ae10f6f7fe07bfadf9882e140a021b0ca1a8ab4",
"s":"0x8b6c62b492ea8cf8e7dc8e8236ce7283a24e18370ce7950bda5f450fb7993547",
"v":62709,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
},
{
"hash":"0x13be1ecbdbaae00332acaa341ea3168781b112e7aff368a8bab060fa102085f4",
"parentHash":"0x5e5f592a3c96a45bc7fceea8681d066af84709dc155d305fe4e3d68f0bb7bd63",
"number":15,
"timestamp":1603404116,
"nonce":"0x0000000000000042",
"difficulty":131968,
"gasLimit":{
"type":"BigNumber",
"hex":"0x02625a00"
},
"gasUsed":{
"type":"BigNumber",
"hex":"0x00"
},
"miner":"0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
"extraData":"0x",
"transactions":[
{
"hash":"0x3a60f459be600341f831b6e9b6b75a242cd31d1e4ae6b0bbd763a6b56054ef7b",
"blockHash":"0x13be1ecbdbaae00332acaa341ea3168781b112e7aff368a8bab060fa102085f4",
"blockNumber":15,
"transactionIndex":0,
"confirmations":2,
"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff",
"gasPrice":{
"type":"BigNumber",
"hex":"0x01dcd65000"
},
"gasLimit":{
"type":"BigNumber",
"hex":"0x0f4240"
},
"to":"0x6454C9d69a4721feBA60e26A367bD4D56196Ee7c",
"value":{
"type":"BigNumber",
"hex":"0x00"
},
"nonce":14,
"data":"0x6fee07e00000000000000000000000000101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000c350000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000205555555555555555555555555555555555555555555555555555555555555555",
"r":"0x3b214ae35181aaf5542f970325ef36ca881db2fc1b145524534a4fb6885b05d7",
"s":"0xdf4fd847db5d5a246f4cedd2dee14e15e70ff469a7d88cdc86efa7c6d61d7cad",
"v":62709,
"creates":null,
"l1BlockNumber":"1",
"l1TxOrigin":"0x3333333333333333333333333333333333333333",
"rawTransaction":"0x420420",
"signatureHashType":"0",
"queueOrigin":"sequencer",
"chainId":31337
}
]
}
]
`)
/* External Imports */
import { defaultAccounts } from 'ethereum-waffle'
export const FORCE_INCLUSION_PERIOD_SECONDS = 600
export const DEFAULT_ACCOUNTS = defaultAccounts
export const DEFAULT_ACCOUNTS_HARDHAT = defaultAccounts.map((account) => {
return {
balance: account.balance,
privateKey: account.secretKey,
}
})
export const OVM_TX_GAS_LIMIT = 10_000_000
export const RUN_OVM_TEST_GAS = 20_000_000
/* External Imports */
import { keccak256 } from 'ethers/lib/utils'
export const DUMMY_BYTECODE = '0x123412341234'
export const DUMMY_BYTECODE_BYTELEN = 6
export const DUMMY_BYTECODE_HASH = keccak256(DUMMY_BYTECODE)
/* External Imports */
import { ethers } from 'ethers'
export const DUMMY_BYTES32: string[] = Array.from(
{
length: 10,
},
(_, i) => {
return ethers.utils.keccak256(`0x0${i}`)
}
)
export * from './bytes32'
export * from './bytecode'
export * from './dummy'
export * from './constants'
export * from './resolver'
/* External Imports */
import { ethers } from 'hardhat'
import { Contract } from 'ethers'
import { getContractFactory as ctFactory } from 'old-contracts'
export const getContractFactory = async (contract: string) =>
ctFactory(contract, (await ethers.getSigners())[0])
export const setProxyTarget = async (
AddressManager: Contract,
name: string,
target: Contract
): Promise<void> => {
const SimpleProxy: Contract = await (
await getContractFactory('Helper_SimpleProxy')
).deploy()
await SimpleProxy.setTarget(target.address)
await AddressManager.setAddress(name, SimpleProxy.address)
}
export const makeAddressManager = async (): Promise<Contract> => {
return (await getContractFactory('Lib_AddressManager')).deploy()
}
/* External Imports */
import chai = require('chai')
import sinonChai from 'sinon-chai'
import Mocha from 'mocha'
const should = chai.should()
const expect = chai.expect
chai.use(sinonChai)
export { should, expect, chai, Mocha }
import { ethers, BigNumber, Signer } from 'ethers'
import {
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { expect } from '../setup'
import { submitTransactionWithYNATM } from '../../src/utils/tx-submission'
import { ResubmissionConfig } from '../../src'
const nullFunction = () => undefined
const nullHooks = {
beforeSendTransaction: nullFunction,
onTransactionResponse: nullFunction,
}
describe('submitTransactionWithYNATM', async () => {
it('calls sendTransaction, waitForTransaction, and hooks with correct inputs', async () => {
const called = {
sendTransaction: false,
waitForTransaction: false,
beforeSendTransaction: false,
onTransactionResponse: false,
}
const dummyHash = 'dummy hash'
const numConfirmations = 3
const tx = {
data: 'we here though',
} as ethers.PopulatedTransaction
const sendTransaction = async (
_tx: ethers.PopulatedTransaction
): Promise<TransactionResponse> => {
called.sendTransaction = true
expect(_tx.data).to.equal(tx.data)
return {
hash: dummyHash,
} as TransactionResponse
}
const waitForTransaction = async (
hash: string,
_numConfirmations: number
): Promise<TransactionReceipt> => {
called.waitForTransaction = true
expect(hash).to.equal(dummyHash)
expect(_numConfirmations).to.equal(numConfirmations)
return {
to: '',
from: '',
status: 1,
} as TransactionReceipt
}
const signer = {
getGasPrice: async () => ethers.BigNumber.from(0),
sendTransaction,
provider: {
waitForTransaction,
},
} as Signer
const hooks = {
beforeSendTransaction: (submittingTx: ethers.PopulatedTransaction) => {
called.beforeSendTransaction = true
expect(submittingTx.data).to.equal(tx.data)
},
onTransactionResponse: (txResponse: TransactionResponse) => {
called.onTransactionResponse = true
expect(txResponse.hash).to.equal(dummyHash)
},
}
const config: ResubmissionConfig = {
resubmissionTimeout: 1000,
minGasPriceInGwei: 0,
maxGasPriceInGwei: 0,
gasRetryIncrement: 1,
}
await submitTransactionWithYNATM(
tx,
signer,
config,
numConfirmations,
hooks
)
expect(called.sendTransaction).to.be.true
expect(called.waitForTransaction).to.be.true
expect(called.beforeSendTransaction).to.be.true
expect(called.onTransactionResponse).to.be.true
})
it('repeatedly increases the gas limit of the transaction when wait takes too long', async () => {
// Make transactions take longer to be included
// than our resubmission timeout
const resubmissionTimeout = 100
const txReceiptDelay = resubmissionTimeout * 3
let lastGasPrice = BigNumber.from(0)
// Create a transaction which has a gas price that we will watch increment
const tx = {
gasPrice: lastGasPrice.add(1),
data: 'hello world!',
} as ethers.PopulatedTransaction
const sendTransaction = async (
_tx: ethers.PopulatedTransaction
): Promise<TransactionResponse> => {
// Ensure the gas price is always increasing
expect(_tx.gasPrice > lastGasPrice).to.be.true
lastGasPrice = _tx.gasPrice
return {
hash: 'dummy hash',
} as TransactionResponse
}
const waitForTransaction = async (): Promise<TransactionReceipt> => {
await new Promise((r) => setTimeout(r, txReceiptDelay))
return {} as TransactionReceipt
}
const signer = {
getGasPrice: async () => ethers.BigNumber.from(0),
sendTransaction,
provider: {
waitForTransaction: waitForTransaction as any,
},
} as Signer
const config: ResubmissionConfig = {
resubmissionTimeout,
minGasPriceInGwei: 0,
maxGasPriceInGwei: 1000,
gasRetryIncrement: 1,
}
await submitTransactionWithYNATM(tx, signer, config, 0, nullHooks)
})
it('should immediately reject if a nonce error is encountered', async () => {
const tx = {
gasPrice: BigNumber.from(1),
data: 'hello world!',
} as ethers.PopulatedTransaction
let txCount = 0
const waitForTransaction = async (): Promise<TransactionReceipt> => {
return {} as TransactionReceipt
}
const sendTransaction = async () => {
txCount++
throw new Error('Transaction nonce is too low.')
}
const signer = {
getGasPrice: async () => BigNumber.from(1),
sendTransaction: sendTransaction as any,
provider: {
waitForTransaction: waitForTransaction as any,
},
} as Signer
const config: ResubmissionConfig = {
resubmissionTimeout: 100,
minGasPriceInGwei: 0,
maxGasPriceInGwei: 1000,
gasRetryIncrement: 1,
}
try {
await submitTransactionWithYNATM(tx, signer, config, 0, nullHooks)
} catch (e) {
expect(txCount).to.equal(1)
return
}
expect.fail('Expected an error.')
})
})
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*"
]
}
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"resolveJsonModule": true
}
}
......@@ -514,28 +514,6 @@
"@eth-optimism/core-utils" "^0.5.1"
bn.js "^5.2.0"
"@eth-optimism/solc@^0.6.12-alpha.1":
version "0.6.12-alpha.1"
resolved "https://registry.yarnpkg.com/@eth-optimism/solc/-/solc-0.6.12-alpha.1.tgz#041876f83b34c6afe2f19dfe9626568df6ed8590"
integrity sha512-Ky73mo+2iNJs/VTaT751nMeZ7hXns0TBAlffTOxIOsScjAZ/zi/KWsDUo3r89aV2JKXcYAU/bLidxF40MVJeUw==
dependencies:
command-exists "^1.2.8"
commander "3.0.2"
follow-redirects "^1.12.1"
fs-extra "^0.30.0"
js-sha3 "0.8.0"
memorystream "^0.3.1"
require-from-string "^2.0.0"
semver "^5.5.0"
tmp "0.0.33"
"@eth-optimism/ynatm@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@eth-optimism/ynatm/-/ynatm-0.2.2.tgz#b1f165c3149188f184b66329228746260ae18677"
integrity sha512-R/hIAFWEj2sjf3inNEGCffmGofqMFY/7PS/Hh4A/62Kg0wMM8rsyMyW8pXngMnD/EQAjR8WTtKDutq/L5vSMTQ==
dependencies:
bluebird "^3.7.2"
"@ethereum-waffle/chai@^3.4.0":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@ethereum-waffle/chai/-/chai-3.4.0.tgz#2477877410a96bf370edd64df905b04fb9aba9d5"
......@@ -689,7 +667,7 @@
"@ethersproject/properties" "^5.0.3"
"@ethersproject/strings" "^5.0.4"
"@ethersproject/abi@5.4.0", "@ethersproject/abi@^5.0.0", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.0.1", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0":
"@ethersproject/abi@5.4.0", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.0.1", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.4.0.tgz#a6d63bdb3672f738398846d4279fa6b6c9818242"
integrity sha512-9gU2H+/yK1j2eVMdzm6xvHSnMxk8waIHQGYCZg5uvAyH0rsAzxkModzBSpbAkAuhKFEovC2S9hM4nPuLym8IZw==
......@@ -734,7 +712,7 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
"@ethersproject/abstract-provider@5.4.1", "@ethersproject/abstract-provider@^5.0.0", "@ethersproject/abstract-provider@^5.4.0", "@ethersproject/abstract-provider@^5.4.1":
"@ethersproject/abstract-provider@5.4.1", "@ethersproject/abstract-provider@^5.4.0", "@ethersproject/abstract-provider@^5.4.1":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz#e404309a29f771bd4d28dbafadcaa184668c2a6e"
integrity sha512-3EedfKI3LVpjSKgAxoUaI+gB27frKsxzm+r21w9G60Ugk+3wVLQwhi1LsEJAKNV7WoZc8CIpNrATlL1QFABjtQ==
......@@ -760,7 +738,7 @@
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/web" "^5.5.0"
"@ethersproject/abstract-signer@5.4.1", "@ethersproject/abstract-signer@^5.0.0", "@ethersproject/abstract-signer@^5.4.0", "@ethersproject/abstract-signer@^5.4.1":
"@ethersproject/abstract-signer@5.4.1", "@ethersproject/abstract-signer@^5.4.0", "@ethersproject/abstract-signer@^5.4.1":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz#e4e9abcf4dd4f1ba0db7dff9746a5f78f355ea81"
integrity sha512-SkkFL5HVq1k4/25dM+NWP9MILgohJCgGv5xT5AcRruGz4ILpfHeBtO/y6j+Z3UN/PAjDeb4P7E51Yh8wcGNLGA==
......@@ -782,7 +760,7 @@
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/address@5.4.0", "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.0.0", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.0.4", "@ethersproject/address@^5.4.0":
"@ethersproject/address@5.4.0", "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.0.4", "@ethersproject/address@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.4.0.tgz#ba2d00a0f8c4c0854933b963b9a3a9f6eb4a37a3"
integrity sha512-SD0VgOEkcACEG/C6xavlU1Hy3m5DGSXW3CUHkaaEHbAPPsgi0coP5oNPsxau8eTlZOk/bpa/hKeCNoK5IzVI2Q==
......@@ -804,7 +782,7 @@
"@ethersproject/logger" "^5.5.0"
"@ethersproject/rlp" "^5.5.0"
"@ethersproject/base64@5.4.0", "@ethersproject/base64@^5.0.0", "@ethersproject/base64@^5.4.0":
"@ethersproject/base64@5.4.0", "@ethersproject/base64@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.4.0.tgz#7252bf65295954c9048c7ca5f43e5c86441b2a9a"
integrity sha512-CjQw6E17QDSSC5jiM9YpF7N1aSCHmYGMt9bWD8PWv6YPMxjsys2/Q8xLrROKI3IWJ7sFfZ8B3flKDTM5wlWuZQ==
......@@ -834,7 +812,7 @@
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/bignumber@5.4.1", "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@^5.0.0", "@ethersproject/bignumber@^5.0.7", "@ethersproject/bignumber@^5.4.0":
"@ethersproject/bignumber@5.4.1", "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@^5.0.7", "@ethersproject/bignumber@^5.4.0":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.4.1.tgz#64399d3b9ae80aa83d483e550ba57ea062c1042d"
integrity sha512-fJhdxqoQNuDOk6epfM7yD6J8Pol4NUCy1vkaGAkuujZm0+lNow//MKu1hLhRiYV4BsOHyBv5/lsTjF+7hWwhJg==
......@@ -861,7 +839,7 @@
"@ethersproject/logger" "^5.4.0"
bn.js "^4.11.9"
"@ethersproject/bytes@5.4.0", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.0", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.4.0":
"@ethersproject/bytes@5.4.0", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.4.0.tgz#56fa32ce3bf67153756dbaefda921d1d4774404e"
integrity sha512-H60ceqgTHbhzOj4uRc/83SCN9d+BSUnOkrr2intevqdtEMO1JFVZ1XL84OEZV+QjV36OaZYxtnt4lGmxcGsPfA==
......@@ -875,7 +853,7 @@
dependencies:
"@ethersproject/logger" "^5.5.0"
"@ethersproject/constants@5.4.0", "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.0", "@ethersproject/constants@^5.0.4", "@ethersproject/constants@^5.4.0":
"@ethersproject/constants@5.4.0", "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.4", "@ethersproject/constants@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.4.0.tgz#ee0bdcb30bf1b532d2353c977bf2ef1ee117958a"
integrity sha512-tzjn6S7sj9+DIIeKTJLjK9WGN2Tj0P++Z8ONEIlZjyoTkBuODN+0VfhAyYksKi43l1Sx9tX2VlFfzjfmr5Wl3Q==
......@@ -889,7 +867,7 @@
dependencies:
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/contracts@5.4.1", "@ethersproject/contracts@^5.0.0", "@ethersproject/contracts@^5.0.5", "@ethersproject/contracts@^5.4.1":
"@ethersproject/contracts@5.4.1", "@ethersproject/contracts@^5.4.1":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.4.1.tgz#3eb4f35b7fe60a962a75804ada2746494df3e470"
integrity sha512-m+z2ZgPy4pyR15Je//dUaymRUZq5MtDajF6GwFbGAVmKz/RF+DNIPwF0k5qEcL3wPGVqUjFg2/krlCRVTU4T5w==
......@@ -921,18 +899,6 @@
"@ethersproject/properties" "^5.5.0"
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/hardware-wallets@^5.0.8":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/hardware-wallets/-/hardware-wallets-5.4.0.tgz#bce275b395e26b6f50481095331157614490a473"
integrity sha512-Ea4ymm4etZoSWy93OcEGZkuVqyYdl/RjMlaXY6yQIYjsGi75sm4apbTiBA8DA9uajkv1FVakJZEBBTaVGgnBLA==
dependencies:
"@ledgerhq/hw-app-eth" "5.27.2"
"@ledgerhq/hw-transport" "5.26.0"
"@ledgerhq/hw-transport-u2f" "5.26.0"
ethers "^5.4.0"
optionalDependencies:
"@ledgerhq/hw-transport-node-hid" "5.26.0"
"@ethersproject/hardware-wallets@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/hardware-wallets/-/hardware-wallets-5.5.0.tgz#b4a3bc99a843c3b78b133cdf94485a567ba17b8d"
......@@ -945,7 +911,7 @@
optionalDependencies:
"@ledgerhq/hw-transport-node-hid" "5.26.0"
"@ethersproject/hash@5.4.0", "@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@^5.0.0", "@ethersproject/hash@^5.0.4", "@ethersproject/hash@^5.4.0":
"@ethersproject/hash@5.4.0", "@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@^5.0.4", "@ethersproject/hash@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.4.0.tgz#d18a8e927e828e22860a011f39e429d388344ae0"
integrity sha512-xymAM9tmikKgbktOCjW60Z5sdouiIIurkZUr9oW5NOex5uwxrbsYG09kb5bMcNjlVeJD3yPivTNzViIs1GCbqA==
......@@ -973,7 +939,7 @@
"@ethersproject/properties" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/hdnode@5.4.0", "@ethersproject/hdnode@^5.0.0", "@ethersproject/hdnode@^5.4.0":
"@ethersproject/hdnode@5.4.0", "@ethersproject/hdnode@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.4.0.tgz#4bc9999b9a12eb5ce80c5faa83114a57e4107cac"
integrity sha512-pKxdS0KAaeVGfZPp1KOiDLB0jba11tG6OP1u11QnYfb7pXn6IZx0xceqWRr6ygke8+Kw74IpOoSi7/DwANhy8Q==
......@@ -1009,7 +975,7 @@
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/wordlists" "^5.5.0"
"@ethersproject/json-wallets@5.4.0", "@ethersproject/json-wallets@^5.0.0", "@ethersproject/json-wallets@^5.4.0":
"@ethersproject/json-wallets@5.4.0", "@ethersproject/json-wallets@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.4.0.tgz#2583341cfe313fc9856642e8ace3080154145e95"
integrity sha512-igWcu3fx4aiczrzEHwG1xJZo9l1cFfQOWzTqwRw/xcvxTk58q4f9M7cjh51EKphMHvrJtcezJ1gf1q1AUOfEQQ==
......@@ -1047,7 +1013,7 @@
aes-js "3.0.0"
scrypt-js "3.0.1"
"@ethersproject/keccak256@5.4.0", "@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0", "@ethersproject/keccak256@^5.0.3", "@ethersproject/keccak256@^5.4.0":
"@ethersproject/keccak256@5.4.0", "@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.3", "@ethersproject/keccak256@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.4.0.tgz#7143b8eea4976080241d2bd92e3b1f1bf7025318"
integrity sha512-FBI1plWet+dPUvAzPAeHzRKiPpETQzqSUWR1wXJGHVWi4i8bOSrpC3NwpkPjgeXG7MnugVc1B42VbfnQikyC/A==
......@@ -1063,7 +1029,7 @@
"@ethersproject/bytes" "^5.5.0"
js-sha3 "0.8.0"
"@ethersproject/logger@5.4.0", "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.0.0", "@ethersproject/logger@^5.0.5", "@ethersproject/logger@^5.4.0":
"@ethersproject/logger@5.4.0", "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.0.5", "@ethersproject/logger@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.4.0.tgz#f39adadf62ad610c420bcd156fd41270e91b3ca9"
integrity sha512-xYdWGGQ9P2cxBayt64d8LC8aPFJk6yWCawQi/4eJ4+oJdMMjEBMrIcIMZ9AxhwpPVmnBPrsB10PcXGmGAqgUEQ==
......@@ -1073,7 +1039,7 @@
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d"
integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==
"@ethersproject/networks@5.4.2", "@ethersproject/networks@^5.0.0", "@ethersproject/networks@^5.4.0":
"@ethersproject/networks@5.4.2", "@ethersproject/networks@^5.4.0":
version "5.4.2"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.4.2.tgz#2247d977626e97e2c3b8ee73cd2457babde0ce35"
integrity sha512-eekOhvJyBnuibfJnhtK46b8HimBc5+4gqpvd1/H9LEl7Q7/qhsIhM81dI9Fcnjpk3jB1aTy6bj0hz3cifhNeYw==
......@@ -1087,7 +1053,7 @@
dependencies:
"@ethersproject/logger" "^5.5.0"
"@ethersproject/pbkdf2@5.4.0", "@ethersproject/pbkdf2@^5.0.0", "@ethersproject/pbkdf2@^5.4.0":
"@ethersproject/pbkdf2@5.4.0", "@ethersproject/pbkdf2@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz#ed88782a67fda1594c22d60d0ca911a9d669641c"
integrity sha512-x94aIv6tiA04g6BnazZSLoRXqyusawRyZWlUhKip2jvoLpzJuLb//KtMM6PEovE47pMbW+Qe1uw+68ameJjB7g==
......@@ -1103,7 +1069,7 @@
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/sha2" "^5.5.0"
"@ethersproject/properties@5.4.0", "@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@^5.0.0", "@ethersproject/properties@^5.0.3", "@ethersproject/properties@^5.4.0":
"@ethersproject/properties@5.4.0", "@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@^5.0.3", "@ethersproject/properties@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.4.0.tgz#38ba20539b44dcc5d5f80c45ad902017dcdbefe7"
integrity sha512-7jczalGVRAJ+XSRvNA6D5sAwT4gavLq3OXPuV/74o3Rd2wuzSL035IMpIMgei4CYyBdialJMrTqkOnzccLHn4A==
......@@ -1117,7 +1083,7 @@
dependencies:
"@ethersproject/logger" "^5.5.0"
"@ethersproject/providers@5.4.4", "@ethersproject/providers@^5.0.0":
"@ethersproject/providers@5.4.4":
version "5.4.4"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.4.4.tgz#6729120317942fc0ab0ecdb35e944ec6bbedb795"
integrity sha512-mQevyXj2X2D3l8p/JGDYFZbODhZjW6On15DnCK4Xc9y6b+P0vqorQC/j46omWSm4cyo7BQ/rgfhXNYmvAfyZoQ==
......@@ -1192,7 +1158,7 @@
bech32 "1.1.4"
ws "7.4.6"
"@ethersproject/random@5.4.0", "@ethersproject/random@^5.0.0", "@ethersproject/random@^5.4.0":
"@ethersproject/random@5.4.0", "@ethersproject/random@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.4.0.tgz#9cdde60e160d024be39cc16f8de3b9ce39191e16"
integrity sha512-pnpWNQlf0VAZDEOVp1rsYQosmv2o0ITS/PecNw+mS2/btF8eYdspkN0vIXrCMtkX09EAh9bdk8GoXmFXM1eAKw==
......@@ -1216,7 +1182,7 @@
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/rlp@5.4.0", "@ethersproject/rlp@^5.0.0", "@ethersproject/rlp@^5.4.0":
"@ethersproject/rlp@5.4.0", "@ethersproject/rlp@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.4.0.tgz#de61afda5ff979454e76d3b3310a6c32ad060931"
integrity sha512-0I7MZKfi+T5+G8atId9QaQKHRvvasM/kqLyAH4XxBCBchAooH2EX5rL9kYZWwcm3awYV+XC7VF6nLhfeQFKVPg==
......@@ -1232,7 +1198,7 @@
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/sha2@5.4.0", "@ethersproject/sha2@^5.0.0", "@ethersproject/sha2@^5.4.0":
"@ethersproject/sha2@5.4.0", "@ethersproject/sha2@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.4.0.tgz#c9a8db1037014cbc4e9482bd662f86c090440371"
integrity sha512-siheo36r1WD7Cy+bDdE1BJ8y0bDtqXCOxRMzPa4bV1TGt/eTUUt03BHoJNB6reWJD8A30E/pdJ8WFkq+/uz4Gg==
......@@ -1250,7 +1216,7 @@
"@ethersproject/logger" "^5.5.0"
hash.js "1.1.7"
"@ethersproject/signing-key@5.4.0", "@ethersproject/signing-key@^5.0.0", "@ethersproject/signing-key@^5.4.0":
"@ethersproject/signing-key@5.4.0", "@ethersproject/signing-key@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.4.0.tgz#2f05120984e81cf89a3d5f6dec5c68ee0894fbec"
integrity sha512-q8POUeywx6AKg2/jX9qBYZIAmKSB4ubGXdQ88l40hmATj29JnG5pp331nAWwwxPn2Qao4JpWHNZsQN+bPiSW9A==
......@@ -1274,7 +1240,7 @@
elliptic "6.5.4"
hash.js "1.1.7"
"@ethersproject/solidity@5.4.0", "@ethersproject/solidity@^5.0.0", "@ethersproject/solidity@^5.0.9", "@ethersproject/solidity@^5.4.0":
"@ethersproject/solidity@5.4.0", "@ethersproject/solidity@^5.0.9", "@ethersproject/solidity@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.4.0.tgz#1305e058ea02dc4891df18b33232b11a14ece9ec"
integrity sha512-XFQTZ7wFSHOhHcV1DpcWj7VXECEiSrBuv7JErJvB9Uo+KfCdc3QtUZV+Vjh/AAaYgezUEKbCtE6Khjm44seevQ==
......@@ -1297,7 +1263,7 @@
"@ethersproject/sha2" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/strings@5.4.0", "@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.0.0", "@ethersproject/strings@^5.0.4", "@ethersproject/strings@^5.4.0":
"@ethersproject/strings@5.4.0", "@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.0.4", "@ethersproject/strings@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.4.0.tgz#fb12270132dd84b02906a8d895ae7e7fa3d07d9a"
integrity sha512-k/9DkH5UGDhv7aReXLluFG5ExurwtIpUfnDNhQA29w896Dw3i4uDTz01Quaptbks1Uj9kI8wo9tmW73wcIEaWA==
......@@ -1315,7 +1281,7 @@
"@ethersproject/constants" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/transactions@5.4.0", "@ethersproject/transactions@^5.0.0", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.4.0":
"@ethersproject/transactions@5.4.0", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.4.0.tgz#a159d035179334bd92f340ce0f77e83e9e1522e0"
integrity sha512-s3EjZZt7xa4BkLknJZ98QGoIza94rVjaEed0rzZ/jB9WrIuu/1+tjvYCWzVrystXtDswy7TPBeIepyXwSYa4WQ==
......@@ -1345,7 +1311,7 @@
"@ethersproject/rlp" "^5.5.0"
"@ethersproject/signing-key" "^5.5.0"
"@ethersproject/units@5.4.0", "@ethersproject/units@^5.0.0":
"@ethersproject/units@5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.4.0.tgz#d57477a4498b14b88b10396062c8cbbaf20c79fe"
integrity sha512-Z88krX40KCp+JqPCP5oPv5p750g+uU6gopDYRTBGcDvOASh6qhiEYCRatuM/suC4S2XW9Zz90QI35MfSrTIaFg==
......@@ -1363,7 +1329,7 @@
"@ethersproject/constants" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/wallet@5.4.0", "@ethersproject/wallet@^5.0.0", "@ethersproject/wallet@^5.4.0":
"@ethersproject/wallet@5.4.0", "@ethersproject/wallet@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.4.0.tgz#fa5b59830b42e9be56eadd45a16a2e0933ad9353"
integrity sha512-wU29majLjM6AjCjpat21mPPviG+EpK7wY1+jzKD0fg3ui5fgedf2zEu1RDgpfIMsfn8fJHJuzM4zXZ2+hSHaSQ==
......@@ -1405,7 +1371,7 @@
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/wordlists" "^5.5.0"
"@ethersproject/web@5.4.0", "@ethersproject/web@^5.0.0", "@ethersproject/web@^5.4.0":
"@ethersproject/web@5.4.0", "@ethersproject/web@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.4.0.tgz#49fac173b96992334ed36a175538ba07a7413d1f"
integrity sha512-1bUusGmcoRLYgMn6c1BLk1tOKUIFuTg8j+6N8lYlbMpDesnle+i3pGSagGNvwjaiLo4Y5gBibwctpPRmjrh4Og==
......@@ -1427,7 +1393,7 @@
"@ethersproject/properties" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/wordlists@5.4.0", "@ethersproject/wordlists@^5.0.0", "@ethersproject/wordlists@^5.4.0":
"@ethersproject/wordlists@5.4.0", "@ethersproject/wordlists@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.4.0.tgz#f34205ec3bbc9e2c49cadaee774cf0b07e7573d7"
integrity sha512-FemEkf6a+EBKEPxlzeVgUaVSodU7G0Na89jqKjmWMlDB0tomoU8RlEMgUvXyqtrg8N4cwpLh8nyRnm1Nay1isA==
......@@ -2523,11 +2489,6 @@
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.3.2.tgz#ff80affd6d352dbe1bbc5b4e1833c41afd6283b6"
integrity sha512-AybF1cesONZStg5kWf6ao9OlqTZuPqddvprc0ky7lrUVOjXeKpmQ2Y9FK+6ygxasb+4aic4O5pneFBfwVsRRRg==
"@openzeppelin/contracts@^3.3.0":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527"
integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA==
"@openzeppelin/contracts@^4.3.2":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.3.3.tgz#ff6ee919fc2a1abaf72b22814bfb72ed129ec137"
......@@ -2723,20 +2684,13 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1":
"@sinonjs/commons@^1.7.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==
dependencies:
type-detect "4.0.8"
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/fake-timers@^7.1.0":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5"
......@@ -2744,20 +2698,6 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/samsam@^5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f"
integrity sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==
dependencies:
"@sinonjs/commons" "^1.6.0"
lodash.get "^4.4.2"
type-detect "^4.0.8"
"@sinonjs/text-encoding@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@solidity-parser/parser@^0.11.0":
version "0.11.1"
resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.11.1.tgz#fa840af64840c930f24a9c82c08d4a092a068add"
......@@ -2864,11 +2804,6 @@
resolved "https://registry.yarnpkg.com/@types/abstract-leveldown/-/abstract-leveldown-5.0.2.tgz#ee81917fe38f770e29eec8139b6f16ee4a8b0a5f"
integrity sha512-+jA1XXF3jsz+Z7FcuiNqgK53hTa/luglT2TyTpKPqoYbxVY+mCPF22Rm+q3KPBrMHJwNXFrTViHszBOfU4vftQ==
"@types/bluebird@^3.5.34":
version "3.5.36"
resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652"
integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==
"@types/bn.js@*", "@types/bn.js@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68"
......@@ -3175,7 +3110,7 @@
"@types/mime" "^1"
"@types/node" "*"
"@types/sinon-chai@^3.2.3", "@types/sinon-chai@^3.2.5":
"@types/sinon-chai@^3.2.3":
version "3.2.5"
resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.5.tgz#df21ae57b10757da0b26f512145c065f2ad45c48"
integrity sha512-bKQqIpew7mmIGNRlxW6Zli/QVyc3zikpGzCa797B/tRnD9OtHvZ/ts8sYXV+Ilj9u3QRaUEM8xrjgd1gwm1BpQ==
......@@ -3190,18 +3125,6 @@
dependencies:
"@sinonjs/fake-timers" "^7.1.0"
"@types/sinon@^9.0.10":
version "9.0.11"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.11.tgz#7af202dda5253a847b511c929d8b6dda170562eb"
integrity sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg==
dependencies:
"@types/sinonjs__fake-timers" "*"
"@types/sinonjs__fake-timers@*":
version "6.0.3"
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz#79df6f358ae8f79e628fe35a63608a0ea8e7cf08"
integrity sha512-E1dU4fzC9wN2QK2Cr1MLCfyHM8BoNnRFvuf45LYMPNDA+WqbNzC45S4UzPxvp1fFJ1rvSGU0bPvdd35VLmXG8g==
"@types/tz-offset@*":
version "0.0.0"
resolved "https://registry.yarnpkg.com/@types/tz-offset/-/tz-offset-0.0.0.tgz#d58f1cebd794148d245420f8f0660305d320e565"
......@@ -4594,7 +4517,7 @@ blakejs@^1.1.0:
resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.1.tgz#bf313053978b2cd4c444a48795710be05c785702"
integrity sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==
bluebird@^3.5.0, bluebird@^3.5.2, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2:
bluebird@^3.5.0, bluebird@^3.5.2, bluebird@^3.5.3, bluebird@^3.5.5:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
......@@ -6165,7 +6088,7 @@ diff@5.0.0, diff@^5.0.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
diff@^4.0.1, diff@^4.0.2:
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
......@@ -7238,41 +7161,6 @@ ethereumjs-wallet@0.6.5:
utf8 "^3.0.0"
uuid "^3.3.2"
ethers@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.0.0.tgz#76558a3020766f310a49f4e1a4c6c1e331761abd"
integrity sha512-uOSACd2E8dg8XuiOewpL42uFH7SvrkA5k0oGkHoqSJl2lflrMPV+7ciWzyuPBjyHnOFvAPPJUpsXrwpFKaLFww==
dependencies:
"@ethersproject/abi" "^5.0.0"
"@ethersproject/abstract-provider" "^5.0.0"
"@ethersproject/abstract-signer" "^5.0.0"
"@ethersproject/address" "^5.0.0"
"@ethersproject/base64" "^5.0.0"
"@ethersproject/bignumber" "^5.0.0"
"@ethersproject/bytes" "^5.0.0"
"@ethersproject/constants" "^5.0.0"
"@ethersproject/contracts" "^5.0.0"
"@ethersproject/hash" "^5.0.0"
"@ethersproject/hdnode" "^5.0.0"
"@ethersproject/json-wallets" "^5.0.0"
"@ethersproject/keccak256" "^5.0.0"
"@ethersproject/logger" "^5.0.0"
"@ethersproject/networks" "^5.0.0"
"@ethersproject/pbkdf2" "^5.0.0"
"@ethersproject/properties" "^5.0.0"
"@ethersproject/providers" "^5.0.0"
"@ethersproject/random" "^5.0.0"
"@ethersproject/rlp" "^5.0.0"
"@ethersproject/sha2" "^5.0.0"
"@ethersproject/signing-key" "^5.0.0"
"@ethersproject/solidity" "^5.0.0"
"@ethersproject/strings" "^5.0.0"
"@ethersproject/transactions" "^5.0.0"
"@ethersproject/units" "^5.0.0"
"@ethersproject/wallet" "^5.0.0"
"@ethersproject/web" "^5.0.0"
"@ethersproject/wordlists" "^5.0.0"
ethers@^4.0.32, ethers@^4.0.40:
version "4.0.49"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894"
......@@ -7288,7 +7176,7 @@ ethers@^4.0.32, ethers@^4.0.40:
uuid "2.0.1"
xmlhttprequest "1.8.0"
ethers@^5.0.0, ethers@^5.0.1, ethers@^5.0.2, ethers@^5.4.0, ethers@^5.4.5:
ethers@^5.0.0, ethers@^5.0.1, ethers@^5.0.2, ethers@^5.4.5:
version "5.4.5"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.4.5.tgz#cec133b9f5b514dc55e2561ee7aa7218c33affd7"
integrity sha512-PPZ6flOAj230sXEWf/r/It6ZZ5c7EOVWx+PU87Glkbg79OtT7pLE1WgL4MRdwx6iF7HzSOvUUI+8cAmcdzo12w==
......@@ -8069,7 +7957,7 @@ ganache-cli@^6.12.2:
source-map-support "0.5.12"
yargs "13.2.4"
ganache-core@^2.12.1, ganache-core@^2.13.2:
ganache-core@^2.13.2:
version "2.13.2"
resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.13.2.tgz#27e6fc5417c10e6e76e2e646671869d7665814a3"
integrity sha512-tIF5cR+ANQz0+3pHWxHjIwHqFXcVo0Mb+kcsNhglNFALcYo49aQpnS9dqHartqPfMFjiHh/qFoD3mYK0d/qGgw==
......@@ -9865,11 +9753,6 @@ jsprim@^1.2.2:
array-includes "^3.1.2"
object.assign "^4.1.2"
just-extend@^4.0.2:
version "4.2.1"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744"
integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==
keccak@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff"
......@@ -10377,11 +10260,6 @@ lodash.flattendeep@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
......@@ -11363,17 +11241,6 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
nise@^4.0.4:
version "4.1.0"
resolved "https://registry.yarnpkg.com/nise/-/nise-4.1.0.tgz#8fb75a26e90b99202fa1e63f448f58efbcdedaf6"
integrity sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/fake-timers" "^6.0.0"
"@sinonjs/text-encoding" "^0.7.1"
just-extend "^4.0.2"
path-to-regexp "^1.7.0"
node-abi@^2.18.0, node-abi@^2.21.0, node-abi@^2.7.0:
version "2.30.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.0.tgz#8be53bf3e7945a34eea10e0fc9a5982776cf550b"
......@@ -11877,18 +11744,6 @@ oboe@2.1.5:
dependencies:
http-https "^1.0.0"
"old-contracts@npm:@eth-optimism/contracts@^0.0.2-alpha.7":
version "0.0.2-alpha.15"
resolved "https://registry.yarnpkg.com/@eth-optimism/contracts/-/contracts-0.0.2-alpha.15.tgz#d602bdb6f1186d111ce9f7c282fc80e023086892"
integrity sha512-upJfYHDWQY7nM0AYT9MKQiuOus2uMUhvqS962qiBq3Ly/9GUq5mS0UALynsrZBGbzT6pflOMKFFEv7jQEORGmA==
dependencies:
"@eth-optimism/solc" "^0.6.12-alpha.1"
"@ethersproject/contracts" "^5.0.5"
"@ethersproject/hardware-wallets" "^5.0.8"
"@openzeppelin/contracts" "^3.3.0"
ethers "5.0.0"
ganache-core "^2.12.1"
on-finished@^2.3.0, on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
......@@ -12349,13 +12204,6 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
dependencies:
isarray "0.0.1"
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
......@@ -13889,23 +13737,6 @@ simple-get@^3.0.3:
once "^1.3.1"
simple-concat "^1.0.0"
sinon-chai@^3.5.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783"
integrity sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==
sinon@^9.2.4:
version "9.2.4"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b"
integrity sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==
dependencies:
"@sinonjs/commons" "^1.8.1"
"@sinonjs/fake-timers" "^6.0.1"
"@sinonjs/samsam" "^5.3.1"
diff "^4.0.2"
nise "^4.0.4"
supports-color "^7.1.0"
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
......@@ -15203,7 +15034,7 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8:
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
......
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