Commit 86c2fbe8 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into refcell/remove/contracts

parents 0f9ed2f6 6ab755d1
......@@ -88,11 +88,9 @@ jobs:
- "packages/contracts-governance/node_modules"
- "packages/contracts-periphery/node_modules"
- "packages/core-utils/node_modules"
- "packages/data-transport-layer/node_modules"
- "packages/drippie-mon/node_modules"
- "packages/fault-detector/node_modules"
- "packages/hardhat-deploy-config/node_modules"
- "packages/message-relayer/node_modules"
- "packages/migration-data/node_modules"
- "packages/replica-healthcheck/node_modules"
- "packages/sdk/node_modules"
......@@ -672,10 +670,6 @@ jobs:
name: Check core-utils
command: npx depcheck
working_directory: packages/core-utils
- run:
name: Check data-transport-layer
command: npx depcheck
working_directory: packages/data-transport-layer
- run:
name: Check sdk
command: npx depcheck
......@@ -1180,13 +1174,6 @@ workflows:
dependencies: "(contracts|contracts-bedrock|core-utils|hardhat-deploy-config)"
requires:
- yarn-monorepo
- js-lint-test:
name: dtl-tests
coverage_flag: dtl-tests
package_name: data-transport-layer
dependencies: "(common-ts|contracts|core-utils)"
requires:
- yarn-monorepo
- js-lint-test:
name: chain-mon-tests
coverage_flag: chain-mon-tests
......@@ -1201,13 +1188,6 @@ workflows:
dependencies: "(common-ts|contracts|core-utils|sdk)"
requires:
- yarn-monorepo
- js-lint-test:
name: message-relayer-tests
coverage_flag: message-relayer-tests
package_name: message-relayer
dependencies: "(common-ts|core-utils|sdk)"
requires:
- yarn-monorepo
- js-lint-test:
name: replica-healthcheck-tests
coverage_flag: replica-healthcheck-tests
......
......@@ -6,11 +6,9 @@
/packages/contracts-bedrock @ethereum-optimism/contract-reviewers
/packages/contracts-periphery @ethereum-optimism/contract-reviewers
/packages/core-utils @ethereum-optimism/legacy-reviewers
/packages/data-transport-layer @ethereum-optimism/legacy-reviewers
/packages/chain-mon @smartcontracts
/packages/fault-detector @ethereum-optimism/devxpod
/packages/hardhat-deploy-config @ethereum-optimism/legacy-reviewers
/packages/message-relayer @ethereum-optimism/legacy-reviewers
/packages/migration-data @ethereum-optimism/legacy-reviewers
/packages/replica-healthcheck @ethereum-optimism/legacy-reviewers
/packages/sdk @ethereum-optimism/devxpod
......
---
C-Protocol-Critical:
- 'packages/data-transport-layer/**/*.ts'
- 'packages/contracts-bedrock/**/*.sol'
- 'l2geth/**/*.go'
......@@ -16,12 +16,10 @@ jobs:
# map the step outputs to job outputs
outputs:
l2geth: ${{ steps.packages.outputs.l2geth }}
message-relayer: ${{ steps.packages.outputs.message-relayer }}
fault-detector: ${{ steps.packages.outputs.fault-detector }}
balance-mon: ${{ steps.packages.outputs.balance-mon }}
drippie-mon: ${{ steps.packages.outputs.drippie-mon }}
wd-mon: ${{ steps.packages.outputs.wd-mon }}
data-transport-layer: ${{ steps.packages.outputs.data-transport-layer }}
contracts: ${{ steps.packages.outputs.contracts }}
contracts-bedrock: ${{ steps.packages.outputs.contracts-bedrock }}
replica-healthcheck: ${{ steps.packages.outputs.replica-healthcheck }}
......@@ -149,33 +147,6 @@ jobs:
push: true
tags: ethereumoptimism/hardhat-node:${{ needs.canary-publish.outputs.canary-docker-tag }}
message-relayer:
name: Publish Message Relayer Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.message-relayer != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: message-relayer
push: true
tags: ethereumoptimism/message-relayer:${{ needs.canary-publish.outputs.canary-docker-tag }}
fault-detector:
name: Publish Fault Detector Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
......@@ -284,33 +255,6 @@ jobs:
push: true
tags: ethereumoptimism/wd-mon:${{ needs.canary-publish.outputs.canary-docker-tag }}
data-transport-layer:
name: Publish Data Transport Layer Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.data-transport-layer != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: data-transport-layer
push: true
tags: ethereumoptimism/data-transport-layer:${{ needs.canary-publish.outputs.canary-docker-tag }}
contracts:
name: Publish Deployer Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
......
......@@ -15,12 +15,10 @@ jobs:
# map the step outputs to job outputs
outputs:
l2geth: ${{ steps.packages.outputs.l2geth }}
message-relayer: ${{ steps.packages.outputs.message-relayer }}
fault-detector: ${{ steps.packages.outputs.fault-detector }}
balance-mon: ${{ steps.packages.outputs.drippie-mon }}
drippie-mon: ${{ steps.packages.outputs.drippie-mon }}
wd-mon: ${{ steps.packages.outputs.wd-mon }}
data-transport-layer: ${{ steps.packages.outputs.data-transport-layer }}
contracts: ${{ steps.packages.outputs.contracts }}
contracts-bedrock: ${{ steps.packages.outputs.contracts-bedrock }}
balance-monitor: ${{ steps.packages.outputs.balance-monitor }}
......@@ -198,33 +196,6 @@ jobs:
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
message-relayer:
name: Publish Message Relayer Version ${{ needs.release.outputs.message-relayer }}
needs: release
if: needs.release.outputs.message-relayer != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: message-relayer
push: true
tags: ethereumoptimism/message-relayer:${{ needs.release.outputs.message-relayer }},ethereumoptimism/message-relayer:latest
fault-detector:
name: Publish Fault Detector Version ${{ needs.release.outputs.fault-detector }}
needs: release
......@@ -333,33 +304,6 @@ jobs:
push: true
tags: ethereumoptimism/drippie-mon:${{ needs.release.outputs.drippie-mon }},ethereumoptimism/drippie-mon:latest
data-transport-layer:
name: Publish Data Transport Layer Version ${{ needs.release.outputs.data-transport-layer }}
needs: release
if: needs.release.outputs.data-transport-layer != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: data-transport-layer
push: true
tags: ethereumoptimism/data-transport-layer:${{ needs.release.outputs.data-transport-layer }},ethereumoptimism/data-transport-layer:latest
contracts:
name: Publish Deployer Version ${{ needs.release.outputs.contracts }}
needs: release
......
......@@ -23,8 +23,6 @@ packages/contracts-periphery/@openzeppelin*
packages/contracts-periphery/hardhat*
packages/contracts-periphery/forge-artifacts*
packages/data-transport-layer/db
packages/contracts-bedrock/deployments/devnetL1
packages/contracts-bedrock/deployments/anvil
......
......@@ -20,18 +20,10 @@
"directory": "packages/contracts-periphery",
"changeProcessCWD": true
},
{
"directory": "packages/data-transport-layer",
"changeProcessCWD": true
},
{
"directory": "packages/chain-mon",
"changeProcessCWD": true
},
{
"directory": "packages/message-relayer",
"changeProcessCWD": true
},
{
"directory": "packages/fault-detector",
"changeProcessCWD": true
......
......@@ -124,10 +124,8 @@ This will build the following containers:
* [`l1_chain`](https://hub.docker.com/r/ethereumoptimism/hardhat): simulated L1 chain using hardhat-evm as a backend
* [`deployer`](https://hub.docker.com/r/ethereumoptimism/deployer): process that deploys L1 smart contracts to the L1 chain
* [`dtl`](https://hub.docker.com/r/ethereumoptimism/data-transport-layer): service that indexes transaction data from the L1 chain
* [`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
If you want to make a change to a container, you'll need to take it down and rebuild it.
For example, if you make a change in l2geth:
......
......@@ -54,10 +54,8 @@ Refer to the Directory Structure section below to understand which packages are
│ ├── <a href="./packages/contracts-bedrock">contracts-bedrock</a>: Bedrock smart contracts.
│ ├── <a href="./packages/contracts-periphery">contracts-periphery</a>: Peripheral contracts for Optimism
│ ├── <a href="./packages/core-utils">core-utils</a>: Low-level utilities that make building Optimism easier
│ ├── <a href="./packages/data-transport-layer">data-transport-layer</a>: Service for indexing Optimism-related L1 data
│ ├── <a href="./packages/chain-mon">chain-mon</a>: Chain monitoring services
│ ├── <a href="./packages/fault-detector">fault-detector</a>: Service for detecting Sequencer faults
│ ├── <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="./packages/sdk">sdk</a>: provides a set of tools for interacting with Optimism
├── <a href="./op-bindings">op-bindings</a>: Go bindings for Bedrock smart contracts.
......@@ -83,10 +81,8 @@ Refer to the Directory Structure section below to understand which packages are
│ ├── <a href="./packages/common-ts">common-ts</a>: Common tools for building apps in TypeScript
│ ├── <a href="./packages/contracts-periphery">contracts-periphery</a>: Peripheral contracts for Optimism
│ ├── <a href="./packages/core-utils">core-utils</a>: Low-level utilities that make building Optimism easier
│ ├── <a href="./packages/data-transport-layer">data-transport-layer</a>: Service for indexing Optimism-related L1 data
│ ├── <a href="./packages/chain-mon">chain-mon</a>: Chain monitoring services
│ ├── <a href="./packages/fault-detector">fault-detector</a>: Service for detecting Sequencer faults
│ ├── <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="./packages/sdk">sdk</a>: provides a set of tools for interacting with Optimism
├── <a href="./indexer">indexer</a>: indexes and syncs transactions
......
......@@ -39,6 +39,5 @@ flag_management:
- name: dtl-tests
- name: chain-mon-tests
- name: fault-detector-tests
- name: message-relayer-tests
- name: replica-healthcheck-tests
- name: sdk-tests
......@@ -2,7 +2,6 @@ package api
import (
"encoding/json"
"log"
"net/http"
"github.com/ethereum-optimism/optimism/indexer/database"
......@@ -101,9 +100,6 @@ func NewApi(bv database.BridgeView) *Api {
}
func (a *Api) Listen(port string) {
err := http.ListenAndServe(port, a.Router)
if err != nil {
log.Fatal("Http server failed to start listening", err)
}
func (a *Api) Listen(port string) error {
return http.ListenAndServe(port, a.Router)
}
......@@ -5,6 +5,9 @@ import (
"errors"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/eth"
)
......@@ -15,38 +18,60 @@ var (
ErrInvalidBlockNumber = errors.New("invalid block number")
// ErrUnsupportedL2OOVersion is returned when the output version is not supported.
ErrUnsupportedL2OOVersion = errors.New("unsupported l2oo version")
// ErrInvalidOutputLogTopic is returned when the output log topic is invalid.
ErrInvalidOutputLogTopic = errors.New("invalid output log topic")
// ErrInvalidOutputTopicLength is returned when the output log topic length is invalid.
ErrInvalidOutputTopicLength = errors.New("invalid output log topic length")
)
// ParseOutputLog parses a log from the L2OutputOracle contract.
func (c *Challenger) ParseOutputLog(log *types.Log) (*bindings.TypesOutputProposal, error) {
// Check the length of log topics
if len(log.Topics) != 4 {
return nil, ErrInvalidOutputTopicLength
}
// Validate the first topic is the output log topic
if log.Topics[0] != c.l2ooABI.Events["OutputProposed"].ID {
return nil, ErrInvalidOutputLogTopic
}
l2BlockNumber := new(big.Int).SetBytes(log.Topics[3][:])
expected := log.Topics[1]
return &bindings.TypesOutputProposal{
L2BlockNumber: l2BlockNumber,
OutputRoot: eth.Bytes32(expected),
}, nil
}
// ValidateOutput checks that a given output is expected via a trusted rollup node rpc.
// It returns: if the output is correct, the fetched output, error
func (c *Challenger) ValidateOutput(ctx context.Context, l2BlockNumber *big.Int, expected eth.Bytes32) (bool, *eth.Bytes32, error) {
func (c *Challenger) ValidateOutput(ctx context.Context, proposal bindings.TypesOutputProposal) (bool, eth.Bytes32, error) {
// Fetch the output from the rollup node
ctx, cancel := context.WithTimeout(ctx, c.networkTimeout)
defer cancel()
output, err := c.rollupClient.OutputAtBlock(ctx, l2BlockNumber.Uint64())
output, err := c.rollupClient.OutputAtBlock(ctx, proposal.L2BlockNumber.Uint64())
if err != nil {
c.log.Error("Failed to fetch output", "blockNum", l2BlockNumber, "err", err)
return false, nil, err
c.log.Error("Failed to fetch output", "blockNum", proposal.L2BlockNumber, "err", err)
return false, eth.Bytes32{}, err
}
// Compare the output root to the expected output root
equalRoots, err := c.compareOutputRoots(output, expected, l2BlockNumber)
equalRoots, err := c.compareOutputRoots(output, proposal)
if err != nil {
return false, nil, err
return false, eth.Bytes32{}, err
}
return equalRoots, &output.OutputRoot, nil
return equalRoots, output.OutputRoot, nil
}
// compareOutputRoots compares the output root of the given block number to the expected output root.
func (c *Challenger) compareOutputRoots(received *eth.OutputResponse, expected eth.Bytes32, blockNumber *big.Int) (bool, error) {
func (c *Challenger) compareOutputRoots(received *eth.OutputResponse, expected bindings.TypesOutputProposal) (bool, error) {
if received.Version != supportedL2OutputVersion {
c.log.Error("Unsupported l2 output version", "version", received.Version)
return false, ErrUnsupportedL2OOVersion
}
if received.BlockRef.Number != blockNumber.Uint64() {
c.log.Error("Invalid blockNumber", "expected", blockNumber, "actual", received.BlockRef.Number)
if received.BlockRef.Number != expected.L2BlockNumber.Uint64() {
c.log.Error("Invalid blockNumber", "expected", expected.L2BlockNumber, "actual", received.BlockRef.Number)
return false, ErrInvalidBlockNumber
}
return received.OutputRoot == expected, nil
return received.OutputRoot == expected.OutputRoot, nil
}
......@@ -10,13 +10,55 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/testlog"
)
func TestChallenger_OutputProposed_Signature(t *testing.T) {
computed := crypto.Keccak256Hash([]byte("OutputProposed(bytes32,uint256,uint256,uint256)"))
challenger := newTestChallenger(t, eth.OutputResponse{}, true)
expected := challenger.l2ooABI.Events["OutputProposed"].ID
require.Equal(t, expected, computed)
}
func TestParseOutputLog_Succeeds(t *testing.T) {
challenger := newTestChallenger(t, eth.OutputResponse{}, true)
expectedBlockNumber := big.NewInt(0x04)
expectedOutputRoot := [32]byte{0x02}
logTopic := challenger.l2ooABI.Events["OutputProposed"].ID
log := types.Log{
Topics: []common.Hash{logTopic, common.Hash(expectedOutputRoot), {0x03}, common.BigToHash(expectedBlockNumber)},
}
outputProposal, err := challenger.ParseOutputLog(&log)
require.NoError(t, err)
require.Equal(t, expectedBlockNumber, outputProposal.L2BlockNumber)
require.Equal(t, expectedOutputRoot, outputProposal.OutputRoot)
}
func TestParseOutputLog_WrongLogTopic_Errors(t *testing.T) {
challenger := newTestChallenger(t, eth.OutputResponse{}, true)
_, err := challenger.ParseOutputLog(&types.Log{
Topics: []common.Hash{{0x01}, {0x02}, {0x03}, {0x04}},
})
require.ErrorIs(t, err, ErrInvalidOutputLogTopic)
}
func TestParseOutputLog_WrongTopicLength_Errors(t *testing.T) {
challenger := newTestChallenger(t, eth.OutputResponse{}, true)
logTopic := challenger.l2ooABI.Events["OutputProposed"].ID
_, err := challenger.ParseOutputLog(&types.Log{
Topics: []common.Hash{logTopic, {0x02}, {0x03}},
})
require.ErrorIs(t, err, ErrInvalidOutputTopicLength)
}
func TestChallenger_ValidateOutput_RollupClientErrors(t *testing.T) {
output := eth.OutputResponse{
Version: supportedL2OutputVersion,
......@@ -26,9 +68,13 @@ func TestChallenger_ValidateOutput_RollupClientErrors(t *testing.T) {
challenger := newTestChallenger(t, output, true)
valid, received, err := challenger.ValidateOutput(context.Background(), big.NewInt(0), output.OutputRoot)
checked := bindings.TypesOutputProposal{
L2BlockNumber: big.NewInt(0),
OutputRoot: output.OutputRoot,
}
valid, received, err := challenger.ValidateOutput(context.Background(), checked)
require.False(t, valid)
require.Nil(t, received)
require.Equal(t, eth.Bytes32{}, received)
require.ErrorIs(t, err, mockOutputApiError)
}
......@@ -41,9 +87,13 @@ func TestChallenger_ValidateOutput_ErrorsWithWrongVersion(t *testing.T) {
challenger := newTestChallenger(t, output, false)
valid, received, err := challenger.ValidateOutput(context.Background(), big.NewInt(0), eth.Bytes32{})
checked := bindings.TypesOutputProposal{
L2BlockNumber: big.NewInt(0),
OutputRoot: output.OutputRoot,
}
valid, received, err := challenger.ValidateOutput(context.Background(), checked)
require.False(t, valid)
require.Nil(t, received)
require.Equal(t, eth.Bytes32{}, received)
require.ErrorIs(t, err, ErrUnsupportedL2OOVersion)
}
......@@ -56,9 +106,13 @@ func TestChallenger_ValidateOutput_ErrorsInvalidBlockNumber(t *testing.T) {
challenger := newTestChallenger(t, output, false)
valid, received, err := challenger.ValidateOutput(context.Background(), big.NewInt(1), output.OutputRoot)
checked := bindings.TypesOutputProposal{
L2BlockNumber: big.NewInt(1),
OutputRoot: output.OutputRoot,
}
valid, received, err := challenger.ValidateOutput(context.Background(), checked)
require.False(t, valid)
require.Nil(t, received)
require.Equal(t, eth.Bytes32{}, received)
require.ErrorIs(t, err, ErrInvalidBlockNumber)
}
......@@ -71,8 +125,12 @@ func TestOutput_ValidateOutput(t *testing.T) {
challenger := newTestChallenger(t, output, false)
valid, expected, err := challenger.ValidateOutput(context.Background(), big.NewInt(0), output.OutputRoot)
require.Equal(t, *expected, output.OutputRoot)
checked := bindings.TypesOutputProposal{
L2BlockNumber: big.NewInt(0),
OutputRoot: output.OutputRoot,
}
valid, expected, err := challenger.ValidateOutput(context.Background(), checked)
require.Equal(t, expected, output.OutputRoot)
require.True(t, valid)
require.NoError(t, err)
}
......@@ -86,7 +144,11 @@ func TestChallenger_CompareOutputRoots_ErrorsWithDifferentRoots(t *testing.T) {
challenger := newTestChallenger(t, output, false)
valid, err := challenger.compareOutputRoots(&output, output.OutputRoot, big.NewInt(0))
checked := bindings.TypesOutputProposal{
L2BlockNumber: big.NewInt(0),
OutputRoot: output.OutputRoot,
}
valid, err := challenger.compareOutputRoots(&output, checked)
require.False(t, valid)
require.ErrorIs(t, err, ErrUnsupportedL2OOVersion)
}
......@@ -100,7 +162,11 @@ func TestChallenger_CompareOutputRoots_ErrInvalidBlockNumber(t *testing.T) {
challenger := newTestChallenger(t, output, false)
valid, err := challenger.compareOutputRoots(&output, output.OutputRoot, big.NewInt(1))
checked := bindings.TypesOutputProposal{
L2BlockNumber: big.NewInt(1),
OutputRoot: output.OutputRoot,
}
valid, err := challenger.compareOutputRoots(&output, checked)
require.False(t, valid)
require.ErrorIs(t, err, ErrInvalidBlockNumber)
}
......@@ -114,11 +180,19 @@ func TestChallenger_CompareOutputRoots_Succeeds(t *testing.T) {
challenger := newTestChallenger(t, output, false)
valid, err := challenger.compareOutputRoots(&output, output.OutputRoot, big.NewInt(0))
checked := bindings.TypesOutputProposal{
L2BlockNumber: big.NewInt(0),
OutputRoot: output.OutputRoot,
}
valid, err := challenger.compareOutputRoots(&output, checked)
require.True(t, valid)
require.NoError(t, err)
valid, err = challenger.compareOutputRoots(&output, eth.Bytes32{0x01}, big.NewInt(0))
checked = bindings.TypesOutputProposal{
L2BlockNumber: big.NewInt(0),
OutputRoot: eth.Bytes32{0x01},
}
valid, err = challenger.compareOutputRoots(&output, checked)
require.False(t, valid)
require.NoError(t, err)
}
......@@ -127,11 +201,14 @@ func newTestChallenger(t *testing.T, output eth.OutputResponse, errors bool) *Ch
outputApi := newMockOutputApi(output, errors)
log := testlog.Logger(t, log.LvlError)
metr := metrics.NewMetrics("test")
parsedL2oo, err := bindings.L2OutputOracleMetaData.GetAbi()
require.NoError(t, err)
challenger := Challenger{
rollupClient: outputApi,
log: log,
metr: metr,
networkTimeout: time.Duration(5) * time.Second,
l2ooABI: parsedL2oo,
}
return &challenger
}
......
version: '3.4'
x-system-addr-env: &system-addr-env
# private key: a6aecc98b63bafb0de3b29ae9964b14acb4086057808be29f90150214ebd4a0f
# OK to publish this since it will only ever be used in itests
SYSTEM_ADDRESS_0_DEPLOYER: '0xa961b0d6dce82db098cf70a42a14add3ee3db2d5'
# private key: 3b8d2345102cce2443acb240db6e87c8edd4bb3f821b17fab8ea2c9da08ea132
# OK to publish this since it will only ever be used in itests
SYSTEM_ADDRESS_1_DEPLOYER: '0xdfc82d475833a50de90c642770f34a9db7deb725'
services:
# this is a helper service used because there's no official hardhat image
l1_chain:
image: ethereumoptimism/hardhat-node:${DOCKER_TAG_HARDHAT:-latest}
build:
context: ./docker/hardhat
dockerfile: Dockerfile
env_file:
- ./envs/l1_chain.env
ports:
# expose the service to the host for integration testing
- ${L1CHAIN_HTTP_PORT:-9545}:8545
deployer:
depends_on:
- l1_chain
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.packages
target: deployer
image: ethereumoptimism/deployer:${DOCKER_TAG_DEPLOYER:-latest}
entrypoint: ./deployer.sh
environment:
# Env vars for the deployment script.
CONTRACTS_RPC_URL: http://l1_chain:8545
CONTRACTS_DEPLOYER_KEY: 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
CONTRACTS_TARGET_NETWORK: 'local'
ports:
# expose the service to the host for getting the contract addrs
- ${DEPLOYER_PORT:-8080}:8081
dtl:
depends_on:
- l1_chain
- deployer
- l2geth
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.packages
target: data-transport-layer
image: ethereumoptimism/data-transport-layer:${DOCKER_TAG_DATA_TRANSPORT_LAYER:-latest}
# override with the dtl script and the env vars required for it
entrypoint: ./dtl.sh
env_file:
- ./envs/dtl.env
# set the rest of the env vars for the network whcih do not
# depend on the docker-compose setup
environment:
# used for setting the address manager address
URL: http://deployer:8081/addresses.json
# connect to the 2 layers
DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT: http://l1_chain:8545
DATA_TRANSPORT_LAYER__L2_RPC_ENDPOINT: http://l2geth:8545
DATA_TRANSPORT_LAYER__SYNC_FROM_L2: 'true'
DATA_TRANSPORT_LAYER__L2_CHAIN_ID: 17
ports:
- ${DTL_PORT:-7878}:7878
l2geth:
depends_on:
- l1_chain
- deployer
build:
context: ..
dockerfile: ./l2geth/Dockerfile
image: ethereumoptimism/l2geth:${DOCKER_TAG_L2GETH:-latest}
# override with the geth script and the env vars required for it
entrypoint: sh ./geth.sh
env_file:
- ./envs/geth.env
environment:
<<: *system-addr-env
ETH1_HTTP: http://l1_chain:8545
ROLLUP_TIMESTAMP_REFRESH: 5s
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
# connecting to the DTL
ROLLUP_CLIENT_HTTP: http://dtl:7878
ETH1_CTC_DEPLOYMENT_HEIGHT: 8
RETRIES: 60
# no need to keep this secret, only used internally to sign blocks
BLOCK_SIGNER_KEY: '6587ae678cf4fc9a33000cdbf9f35226b71dcc6a4684a31203241f9bcfd55d27'
BLOCK_SIGNER_ADDRESS: '0x00000398232E2064F896018496b4b44b3D62751F'
ROLLUP_ENFORCE_FEES: ${ROLLUP_ENFORCE_FEES:-true}
ROLLUP_FEE_THRESHOLD_DOWN: 0.9
ROLLUP_FEE_THRESHOLD_UP: 1.1
ports:
- ${L2GETH_HTTP_PORT:-8545}:8545
- ${L2GETH_WS_PORT:-8546}:8546
relayer:
depends_on:
- l1_chain
- l2geth
deploy:
replicas: 0
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.packages
target: message-relayer
image: ethereumoptimism/message-relayer:${DOCKER_TAG_MESSAGE_RELAYER:-latest}
entrypoint: ./relayer.sh
environment:
MESSAGE_RELAYER__L1_RPC_PROVIDER: http://l1_chain:8545
MESSAGE_RELAYER__L2_RPC_PROVIDER: http://l2geth:8545
MESSAGE_RELAYER__L1_WALLET: '0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97'
RETRIES: 60
fault_detector:
depends_on:
- l1_chain
- l2geth
deploy:
replicas: 0
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.packages
target: fault-detector
image: ethereumoptimism/fault-detector:${DOCKER_TAG_FAULT_DETECTOR:-latest}
entrypoint: ./detector.sh
environment:
FAULT_DETECTOR__L1_RPC_PROVIDER: http://l1_chain:8545
FAULT_DETECTOR__L2_RPC_PROVIDER: http://l2geth:8545
RETRIES: 60
verifier:
depends_on:
- l1_chain
- deployer
- dtl
- l2geth
deploy:
replicas: 1
build:
context: ..
dockerfile: ./l2geth/Dockerfile
image: ethereumoptimism/l2geth:${DOCKER_TAG_L2GETH:-latest}
entrypoint: sh ./geth.sh
env_file:
- ./envs/geth.env
environment:
<<: *system-addr-env
ETH1_HTTP: http://l1_chain:8545
SEQUENCER_CLIENT_HTTP: http://l2geth:8545
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
ROLLUP_CLIENT_HTTP: http://dtl:7878
ROLLUP_BACKEND: 'l1'
ETH1_CTC_DEPLOYMENT_HEIGHT: 8
RETRIES: 60
ROLLUP_VERIFIER_ENABLE: 'true'
ports:
- ${VERIFIER_HTTP_PORT:-8547}:8545
- ${VERIFIER_WS_PORT:-8548}:8546
replica:
depends_on:
- dtl
- l2geth
deploy:
replicas: 1
build:
context: ..
dockerfile: ./l2geth/Dockerfile
image: ethereumoptimism/l2geth:${DOCKER_TAG_L2GETH:-latest}
entrypoint: sh ./geth.sh
env_file:
- ./envs/geth.env
environment:
<<: *system-addr-env
ETH1_HTTP: http://l1_chain:8545
SEQUENCER_CLIENT_HTTP: http://l2geth:8545
ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json
ROLLUP_CLIENT_HTTP: http://dtl:7878
ROLLUP_BACKEND: 'l2'
ROLLUP_VERIFIER_ENABLE: 'true'
ETH1_CTC_DEPLOYMENT_HEIGHT: 8
RETRIES: 60
ports:
- ${REPLICA_HTTP_PORT:-8549}:8545
- ${REPLICA_WS_PORT:-8550}:8546
replica_healthcheck:
depends_on:
- l2geth
- replica
deploy:
replicas: 0
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.packages
target: replica-healthcheck
image: ethereumoptimism/replica-healthcheck:${DOCKER_TAG_REPLICA_HEALTHCHECK:-latest}
environment:
HEALTHCHECK__REFERENCE_RPC_PROVIDER: http://l2geth:8545
HEALTHCHECK__TARGET_RPC_PROVIDER: http://replica:8545
ports:
- ${HEALTHCHECK_HTTP_PORT:-7300}:7300
......@@ -88,17 +88,6 @@ FROM base as deployer-bedrock
WORKDIR /opt/optimism/packages/contracts-bedrock
CMD ["yarn", "run", "deploy"]
FROM base as data-transport-layer
WORKDIR /opt/optimism/packages/data-transport-layer
COPY ./ops/scripts/dtl.sh .
CMD ["node", "dist/src/services/run.js"]
FROM base as message-relayer
WORKDIR /opt/optimism/packages/message-relayer
COPY ./ops/scripts/relayer.sh .
CMD ["npm", "run", "start"]
FROM base as fault-detector
WORKDIR /opt/optimism/packages/fault-detector
......
......@@ -22,7 +22,7 @@ DRIPPIE_MON__DRIPPIE_ADDRESS=
# ↓ wd-mon ↓ #
###############################################################################
# RPCs pointing to a base chain and ptimism chain
# RPCs pointing to a base chain and Optimism chain
TWO_STEP_MONITOR__L1_RPC_PROVIDER=
TWO_STEP_MONITOR__L2_RPC_PROVIDER=
......
......@@ -2,17 +2,29 @@ import assert from 'assert'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import '@eth-optimism/hardhat-deploy-config'
import { ethers } from 'ethers'
import { BigNumber } from 'ethers'
import { defaultResourceConfig } from '../src/constants'
import { assertContractVariable, deploy } from '../src/deploy-utils'
const uint128Max = ethers.BigNumber.from('0xffffffffffffffffffffffffffffffff')
const deployFn: DeployFunction = async (hre) => {
const batcherHash = hre.ethers.utils
.hexZeroPad(hre.deployConfig.batchSenderAddress, 32)
.toLowerCase()
const l2GenesisBlockGasLimit = BigNumber.from(
hre.deployConfig.l2GenesisBlockGasLimit
)
const l2GasLimitLowerBound = BigNumber.from(
defaultResourceConfig.systemTxMaxGas +
defaultResourceConfig.maxResourceLimit
)
if (l2GenesisBlockGasLimit.lt(l2GasLimitLowerBound)) {
throw new Error(
`L2 genesis block gas limit must be at least ${l2GasLimitLowerBound}`
)
}
await deploy({
hre,
name: 'SystemConfig',
......@@ -21,16 +33,9 @@ const deployFn: DeployFunction = async (hre) => {
hre.deployConfig.gasPriceOracleOverhead,
hre.deployConfig.gasPriceOracleScalar,
batcherHash,
hre.deployConfig.l2GenesisBlockGasLimit,
l2GenesisBlockGasLimit,
hre.deployConfig.p2pSequencerAddress,
{
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
systemTxMaxGas: 1_000_000,
minimumBaseFee: ethers.utils.parseUnits('1', 'gwei'),
maximumBaseFee: uint128Max,
},
defaultResourceConfig,
],
postDeployAction: async (contract) => {
await assertContractVariable(
......@@ -56,12 +61,30 @@ const deployFn: DeployFunction = async (hre) => {
)
const config = await contract.resourceConfig()
assert(config.maxResourceLimit === 20_000_000)
assert(config.elasticityMultiplier === 10)
assert(config.baseFeeMaxChangeDenominator === 8)
assert(config.systemTxMaxGas === 1_000_000)
assert(ethers.utils.parseUnits('1', 'gwei').eq(config.minimumBaseFee))
assert(config.maximumBaseFee.eq(uint128Max))
assert(config.maxResourceLimit === defaultResourceConfig.maxResourceLimit)
assert(
config.elasticityMultiplier ===
defaultResourceConfig.elasticityMultiplier
)
assert(
config.baseFeeMaxChangeDenominator ===
defaultResourceConfig.baseFeeMaxChangeDenominator
)
assert(
BigNumber.from(config.systemTxMaxGas).eq(
defaultResourceConfig.systemTxMaxGas
)
)
assert(
BigNumber.from(config.minimumBaseFee).eq(
defaultResourceConfig.minimumBaseFee
)
)
assert(
BigNumber.from(config.maximumBaseFee).eq(
defaultResourceConfig.maximumBaseFee
)
)
},
})
}
......
import assert from 'assert'
import { ethers, BigNumber } from 'ethers'
import { ethers } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { awaitCondition } from '@eth-optimism/core-utils'
import '@eth-optimism/hardhat-deploy-config'
......@@ -10,6 +10,7 @@ import {
getContractsFromArtifacts,
getDeploymentAddress,
} from '../src/deploy-utils'
import { defaultResourceConfig } from '../src/constants'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
......@@ -102,16 +103,7 @@ const deployFn: DeployFunction = async (hre) => {
unsafeBlockSigner: hre.deployConfig.p2pSequencerAddress,
// The resource config is not exposed to the end user
// to simplify deploy config. It may be introduced in the future.
resourceConfig: {
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: ethers.utils.parseUnits('1', 'gwei'),
systemTxMaxGas: 1_000_000,
maximumBaseFee: BigNumber.from(
'0xffffffffffffffffffffffffffffffff'
).toString(),
},
resourceConfig: defaultResourceConfig,
},
}
......
import { ethers } from 'ethers'
/**
* Predeploys are Solidity contracts that are injected into the initial L2 state and provide
* various useful functions.
......@@ -26,3 +28,14 @@ export const predeploys = {
BaseFeeVault: '0x4200000000000000000000000000000000000019',
L1FeeVault: '0x420000000000000000000000000000000000001a',
}
const uint128Max = ethers.BigNumber.from('0xffffffffffffffffffffffffffffffff')
export const defaultResourceConfig = {
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: ethers.utils.parseUnits('1', 'gwei'),
systemTxMaxGas: 1_000_000,
maximumBaseFee: uint128Max,
}
ignores: [
"@babel/eslint-parser",
"@types/level",
"@typescript-eslint/parser",
"eslint-plugin-import",
"eslint-plugin-unicorn",
"eslint-plugin-jsdoc",
"eslint-plugin-prefer-arrow",
"eslint-plugin-react",
"@typescript-eslint/eslint-plugin",
"eslint-config-prettier",
"eslint-plugin-prettier",
"chai"
]
# General options
DATA_TRANSPORT_LAYER__NODE_ENV=development
# Leave blank during local development
DATA_TRANSPORT_LAYER__ETH_NETWORK_NAME=
DATA_TRANSPORT_LAYER__DB_PATH=./db
DATA_TRANSPORT_LAYER__ADDRESS_MANAGER=
DATA_TRANSPORT_LAYER__POLLING_INTERVAL=5000
DATA_TRANSPORT_LAYER__DANGEROUSLY_CATCH_ALL_ERRORS=true
DATA_TRANSPORT_LAYER__CONFIRMATIONS=12
# Server options
DATA_TRANSPORT_LAYER__SERVER_HOSTNAME=localhost
DATA_TRANSPORT_LAYER__SERVER_PORT=7878
# Set to "true" if you want to sync confirmed transactions from L1 (Ethereum).
# You probably want to set this to "true".
DATA_TRANSPORT_LAYER__SYNC_FROM_L1=true
DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT=
DATA_TRANSPORT_LAYER__LOGS_PER_POLLING_INTERVAL=2000
# Set to "true" if you want to sync unconfirmed transactions from a sequencer.
# Make sure to fill in the below values if you intend to do so.
DATA_TRANSPORT_LAYER__SYNC_FROM_L2=false
DATA_TRANSPORT_LAYER__L2_RPC_ENDPOINT=
DATA_TRANSPORT_LAYER__TRANSACTIONS_PER_POLLING_INTERVAL=1000
DATA_TRANSPORT_LAYER__L2_CHAIN_ID=69
DATA_TRANSPORT_LAYER__LEGACY_SEQUENCER_COMPATIBILITY=false
# Monitoring
# Leave the SENTRY_DSN variable unset during local development
DATA_TRANSPORT_LAYER__USE_SENTRY=
DATA_TRANSPORT_LAYER__SENTRY_DSN=
DATA_TRANSPORT_LAYER__SENTRY_TRACE_RATE=
DATA_TRANSPORT_LAYER__ENABLE_METRICS=
module.exports = {
extends: '../../.eslintrc.js',
}
module.exports = {
...require('../../.prettierrc.js'),
};
\ No newline at end of file
# data transport layer
## 0.5.56
### Patch Changes
- Updated dependencies [5063a69fb]
- @eth-optimism/contracts@0.6.0
## 0.5.55
### Patch Changes
- b33208a8f: Add better logging to DTL about shutoff block
- dbe5eb308: Empty patch release to re-release packages that failed to be released by a bug in the release process.
## 0.5.54
### Patch Changes
- Updated dependencies [fecd42d67]
- @eth-optimism/common-ts@0.8.1
## 0.5.53
### Patch Changes
- Updated dependencies [0e179781b]
- Updated dependencies [4ae94b412]
- @eth-optimism/common-ts@0.8.0
## 0.5.52
### Patch Changes
- Updated dependencies [f04e5db2d]
- @eth-optimism/common-ts@0.7.1
## 0.5.51
### Patch Changes
- 4396e187d: Fixes a bug in the DTL that would cause it to not be able to sync beyond the deposit shutoff block.
## 0.5.50
### Patch Changes
- 9b2891852: Refactors BaseServiceV2 slightly, merges standard options with regular options
- Updated dependencies [e23f60f63]
- Updated dependencies [ab8ec365c]
- Updated dependencies [9b2891852]
- Updated dependencies [d1f9098f9]
- Updated dependencies [c6c9c7dbf]
- Updated dependencies [ffcee1013]
- Updated dependencies [eceb0de1d]
- @eth-optimism/common-ts@0.7.0
- @eth-optimism/contracts@0.5.40
## 0.5.49
### Patch Changes
- 1d3c749a2: Bumps the version of ts-node used
- 7c90d5a48: Patch for fast Goerli sync
- 902985f2c: Add L1 sync shutoff block
- 21ee72b3a: Give users a nicer revert when connected to wrong RPC
- 36bd8fbff: Updates the DTL in preparation for shutoff during the Bedrock migration. So long, DTL!
- e941ee73f: Fixes a bug with the DTL shutoff block logic
- Updated dependencies [1d3c749a2]
- Updated dependencies [c975c9620]
- Updated dependencies [1d3c749a2]
- Updated dependencies [136ea1785]
- @eth-optimism/contracts@0.5.39
- @eth-optimism/core-utils@0.12.0
- @eth-optimism/common-ts@0.6.8
## 0.5.48
### Patch Changes
- Updated dependencies [1e76cdb86]
- @eth-optimism/core-utils@0.11.0
- @eth-optimism/common-ts@0.6.7
- @eth-optimism/contracts@0.5.38
## 0.5.47
### Patch Changes
- 81c1cd99: Adds consistency checks for transaction entries in L1 syncing nodes
- Updated dependencies [ce7da914]
- Updated dependencies [628affc7]
- Updated dependencies [740e1bcc]
- @eth-optimism/common-ts@0.6.6
- @eth-optimism/contracts@0.5.37
## 0.5.46
### Patch Changes
- cf3fcf41: Optimization for mainnet under the assumption that addresses do not change.
## 0.5.45
### Patch Changes
- 7215f4ce: Bump ethers to 5.7.0 globally
- b1cec1d5: Fixes a bug that would break DTL sync when addresses are changed
- Updated dependencies [7215f4ce]
- Updated dependencies [206f6033]
- Updated dependencies [d7679ca4]
- @eth-optimism/common-ts@0.6.5
- @eth-optimism/contracts@0.5.36
- @eth-optimism/core-utils@0.10.1
## 0.5.44
### Patch Changes
- Updated dependencies [334a3eb0]
- @eth-optimism/contracts@0.5.35
## 0.5.43
### Patch Changes
- Updated dependencies [dbfea116]
- Updated dependencies [299157e7]
- @eth-optimism/core-utils@0.10.0
- @eth-optimism/contracts@0.5.34
- @eth-optimism/common-ts@0.6.4
## 0.5.42
### Patch Changes
- Updated dependencies [0c2719f8]
- Updated dependencies [a1a73e64]
- @eth-optimism/contracts@0.5.33
## 0.5.41
### Patch Changes
- Updated dependencies [0df744f6]
- Updated dependencies [8ae39154]
- Updated dependencies [dac4a9f0]
- @eth-optimism/core-utils@0.9.3
- @eth-optimism/common-ts@0.6.3
- @eth-optimism/contracts@0.5.32
## 0.5.40
### Patch Changes
- Updated dependencies [0bf3b9b4]
- Updated dependencies [8d26459b]
- Updated dependencies [4477fe9f]
- Updated dependencies [1de4f48e]
- @eth-optimism/core-utils@0.9.2
- @eth-optimism/contracts@0.5.31
- @eth-optimism/common-ts@0.6.2
## 0.5.39
### Patch Changes
- 6e3449ba: Upgrade import path for new typechain
- Updated dependencies [6e3449ba]
- Updated dependencies [f9fee446]
- @eth-optimism/contracts@0.5.30
- @eth-optimism/core-utils@0.9.1
- @eth-optimism/common-ts@0.6.1
## 0.5.38
### Patch Changes
- Updated dependencies [700dcbb0]
- Updated dependencies [3d1cb720]
- @eth-optimism/core-utils@0.9.0
- @eth-optimism/common-ts@0.6.0
- @eth-optimism/contracts@0.5.29
## 0.5.37
### Patch Changes
- Updated dependencies [cb71fcde]
- Updated dependencies [10e41522]
- @eth-optimism/common-ts@0.5.0
## 0.5.36
### Patch Changes
- 29ff7462: Revert es target back to 2017
- Updated dependencies [27234f68]
- Updated dependencies [c201f3f1]
- Updated dependencies [29ff7462]
- Updated dependencies [52b26878]
- @eth-optimism/contracts@0.5.28
- @eth-optimism/common-ts@0.4.0
- @eth-optimism/core-utils@0.8.7
## 0.5.35
### Patch Changes
- Updated dependencies [9ba869a7]
- Updated dependencies [050859fd]
- @eth-optimism/common-ts@0.3.1
## 0.5.34
### Patch Changes
- Updated dependencies [d9e39931]
- Updated dependencies [84a8934c]
- @eth-optimism/common-ts@0.3.0
## 0.5.33
### Patch Changes
- Updated dependencies [7c5ac36f]
- Updated dependencies [3d4d988c]
- Updated dependencies [9ecbf3e5]
- @eth-optimism/contracts@0.5.27
- @eth-optimism/common-ts@0.2.10
## 0.5.32
### Patch Changes
- Updated dependencies [17962ca9]
- @eth-optimism/core-utils@0.8.6
- @eth-optimism/common-ts@0.2.9
- @eth-optimism/contracts@0.5.26
## 0.5.31
### Patch Changes
- 553180e9: Fix race condition
- d18ae135: Updates all ethers versions in response to BN.js bug
- Updated dependencies [f16383f2]
- Updated dependencies [d18ae135]
- @eth-optimism/common-ts@0.2.8
- @eth-optimism/contracts@0.5.25
- @eth-optimism/core-utils@0.8.5
## 0.5.30
### Patch Changes
- Updated dependencies [b7a04acf]
- @eth-optimism/contracts@0.5.24
## 0.5.29
### Patch Changes
- 412688d5: Replace calls to getNetwork() with getChainId util
- Updated dependencies [412688d5]
- @eth-optimism/contracts@0.5.23
## 0.5.28
### Patch Changes
- Updated dependencies [51adb389]
- Updated dependencies [5cb3a5f7]
- Updated dependencies [6b9fc055]
- @eth-optimism/contracts@0.5.22
- @eth-optimism/core-utils@0.8.4
- @eth-optimism/common-ts@0.2.7
## 0.5.27
### Patch Changes
- e12e8976: Use Basic Authentication in L1TransportServer
## 0.5.26
### Patch Changes
- Updated dependencies [5818decb]
- @eth-optimism/contracts@0.5.21
## 0.5.25
### Patch Changes
- b57014d1: Update to typescript@4.6.2
- Updated dependencies [d040a8d9]
- Updated dependencies [b57014d1]
- @eth-optimism/contracts@0.5.20
- @eth-optimism/common-ts@0.2.6
- @eth-optimism/core-utils@0.8.3
## 0.5.24
### Patch Changes
- 3e5b2092: Patch for Kovan DTL halting issue
- 05894239: Add new metrics to the data-transport-layer
- c1957126: Update Dockerfile to use Alpine
- d9a51154: Bump to hardhat@2.9.1
- Updated dependencies [e36b085c]
- Updated dependencies [c1957126]
- Updated dependencies [51673b90]
- Updated dependencies [7a179003]
- Updated dependencies [d9a51154]
- @eth-optimism/common-ts@0.2.5
- @eth-optimism/contracts@0.5.19
- @eth-optimism/core-utils@0.8.2
## 0.5.23
### Patch Changes
- Updated dependencies [f981b8da]
- @eth-optimism/common-ts@0.2.4
## 0.5.22
### Patch Changes
- 075f4b66: Removes the unused L1DataTransportClient and its dependencies
- 88601cb7: Refactored Dockerfiles
- 134a0966: dtl: Support basic authentication for RPC endpoints
- Updated dependencies [88601cb7]
- Updated dependencies [f7761058]
- Updated dependencies [5ae15042]
- Updated dependencies [5cd1e996]
- @eth-optimism/contracts@0.5.18
- @eth-optimism/common-ts@0.2.3
## 0.5.21
### Patch Changes
- Updated dependencies [b3f9bdef]
- Updated dependencies [175ae0bf]
- Updated dependencies [e53b5783]
- @eth-optimism/common-ts@0.2.2
- @eth-optimism/contracts@0.5.17
## 0.5.20
### Patch Changes
- 3873b696: Enable typed batch support
- Updated dependencies [962f36e4]
- Updated dependencies [f2179e37]
- Updated dependencies [b6a4fa4b]
- Updated dependencies [b7c0a5ca]
- Updated dependencies [5a6f539c]
- Updated dependencies [27d8942e]
- @eth-optimism/contracts@0.5.16
- @eth-optimism/core-utils@0.8.1
## 0.5.19
### Patch Changes
- 275eb818: Include patch contexts for bss hf1
- baece507: Hardcodes BSS HF1 block into the DTL
## 0.5.18
### Patch Changes
- b8ee3e05: Add logging when BSS HF1 is active
- 4878667a: Deletes common.ts in data-transport-layer. Uses core-utils.
- e9602d86: Handle null response for `eth_getBlockRange` query
- e63f3b61: Add logs displaying current sync from l2
- Updated dependencies [0b4453f7]
- Updated dependencies [78298782]
- @eth-optimism/core-utils@0.8.0
- @eth-optimism/contracts@0.5.15
## 0.5.17
### Patch Changes
- Updated dependencies [b4165299]
- Updated dependencies [3c2acd91]
- @eth-optimism/core-utils@0.7.7
- @eth-optimism/contracts@0.5.14
## 0.5.16
### Patch Changes
- 8f72064d: Handle case where the remote block isn't found for `GET /eth/context/latest` and `GET /eth/context/blocknumber/:number`
- Updated dependencies [438bc78a]
- @eth-optimism/contracts@0.5.13
## 0.5.15
### Patch Changes
- 1741d884: Updates DTL to correctly parse L1 to L2 tx timestamps after the first bss hardfork
- 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.5.14
### Patch Changes
- Updated dependencies [e631c39c]
- @eth-optimism/contracts@0.5.11
## 0.5.13
### Patch Changes
- Updated dependencies [ad94b9d1]
- @eth-optimism/core-utils@0.7.5
- @eth-optimism/contracts@0.5.10
## 0.5.12
### Patch Changes
- Updated dependencies [ba96a455]
- Updated dependencies [c3e85fef]
- @eth-optimism/core-utils@0.7.4
- @eth-optimism/contracts@0.5.9
## 0.5.11
### Patch Changes
- ffadcb45: Smaller filter query for searching for L1 start height. This number should be configured so that the search does not need to happen because using a smaller filter will cause it to take too long.
- b3efb8b7: String update to change the system name from OE to Optimism
- Updated dependencies [b3efb8b7]
- Updated dependencies [279603e5]
- Updated dependencies [b6040bb3]
- @eth-optimism/contracts@0.5.8
## 0.5.10
### Patch Changes
- Updated dependencies [b6f89fad]
- @eth-optimism/contracts@0.5.7
## 0.5.9
### Patch Changes
- Updated dependencies [bbd42e03]
- Updated dependencies [453f0774]
- @eth-optimism/contracts@0.5.6
## 0.5.8
### Patch Changes
- f13fdaf4: Cleanly catch sigterm, sigint and sigkill
## 0.5.7
### Patch Changes
- 57d5b8f9: Build docker images with node.js version 16
## 0.5.6
### Patch Changes
- 0ab37fc9: Update to node.js version 16
## 0.5.5
### Patch Changes
- 128e0f84: Handle unprotected transactions
- Updated dependencies [584cbc25]
- @eth-optimism/core-utils@0.7.3
- @eth-optimism/contracts@0.5.5
## 0.5.4
### Patch Changes
- Updated dependencies [8e634b49]
- @eth-optimism/core-utils@0.7.2
- @eth-optimism/contracts@0.5.4
## 0.5.3
### Patch Changes
- Updated dependencies [b9049406]
- Updated dependencies [a8b14a7d]
- @eth-optimism/contracts@0.5.3
## 0.5.2
### Patch Changes
- 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.5.1
### Patch Changes
- Updated dependencies [c0fc7fee]
- @eth-optimism/contracts@0.5.1
## 0.5.0
### Minor Changes
- 3f590e33: Remove the "OVM" Prefix from contract names
- 872f5976: Removes various unused OVM contracts
- 81ccd6e4: `regenesis/0.5.0` release
### Patch Changes
- 222a3eef: Add 'User-Agent' to the http headers for ethers providers
- 13d8a11e: Use ethers.StaticJsonRpcProvider for the L2 Provider to lower API requests
- b70ee70c: upgraded to solidity 0.8.9
- a98a1884: Fixes dependencies instead of using caret constraints
- 1344e37d: Add config for L1 start height to allow for syncing specifically after a regenesis. Using the auto detect method will not work with the regenesis scheme of using the same AddressManager
- 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.4.9
### 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.4.8
### Patch Changes
- e0be02e1: Add fallback provider support to DTL using helper function in core-utils
- Updated dependencies [e0be02e1]
- Updated dependencies [7f7f35c3]
- Updated dependencies [8da04505]
- @eth-optimism/core-utils@0.6.0
- @eth-optimism/contracts@0.4.13
## 0.4.7
### Patch Changes
- 21b17edd: Added coverage for packages
- 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.4.6
### 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.4.5
### Patch Changes
- b5b9fd89: Migrate to using `ethers.StaticJsonRpcProvider`
- Updated dependencies [ecc2f8c1]
- @eth-optimism/contracts@0.4.9
## 0.4.4
### 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.4.3
### Patch Changes
- 390fd8a6: Allow the L1 gas price to be fetched from either the sequencer or a L1 provider based on the config `--l1-gas-price-backend` as well as overriding the config by using a query param. Valid values are `l1` or `l2` and it defaults to `l1`
- 049200f4: removed unused functions from core-utils
- Updated dependencies [0313794b]
- Updated dependencies [049200f4]
- Updated dependencies [21e47e1f]
- @eth-optimism/contracts@0.4.2
- @eth-optimism/core-utils@0.5.0
## 0.4.2
### Patch Changes
- 70b8ae84: Attach correct TransportDB object to method handler
## 0.4.1
### Patch Changes
- 67eedaf6: Correctly bind the event handlers to the correct `this` in the missing event error path
- Updated dependencies [224b04c0]
- @eth-optimism/core-utils@0.4.7
## 0.4.0
### Minor Changes
- 2e72fd90: Update AddressSet event to speed search up a bit. Breaks AddressSet API.
- 8582fc16: Define L1 Starting block via OwnershipTransferred (occurring on block 1) rather than AddressSet (occuring on block 2 onwards)
### Patch Changes
- 0b91df42: Adds additional code into the DTL to defend against situations where an RPC provider might be missing an event.
- 8fee7bed: Add extra overflow protection for the DTL types
- ca7d65db: Removes a function that was previously used for backwards compatibility but is no longer necessary
- 16f68159: Have DTL log failed HTTP requests as ERROR instead of INFO
- a415d017: Updates the DTL to use the same L2 chain ID everywhere
- 29431d6a: Add highest L1 and L2 block number Gauge metrics to DTL
- 5c89c45f: Move the metric prefix string to a label #1047
- b8e2d685: Add replica sync test to integration tests; handle 0 L2 blocks in DTL
- 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.6
### Patch Changes
- baa3b761: Improve Sentry support, initializing as needed and ensuring ERROR logs route to Sentry
- Updated dependencies [baa3b761]
- @eth-optimism/common-ts@0.1.3
## 0.3.5
### Patch Changes
- 1b692415: incorrect parsing of eth_getBlockRange result
## 0.3.4
### Patch Changes
- f1b27318: Represent gaslimit as a string to avoid an overflow
- 750a5021: Remove dead imports from core-utils
- 1293825c: Fix gasLimit overflow
- a75f05b7: Fixes a bug that prevented verifiers from syncing properly with the DTL
- e52ccd98: Logs the error stacktrace for a failed HTTP request
- 8ac4c74c: improve slow blocking JSON parsing that occurs during l2 sync
- 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.3
### Patch Changes
- e4c3b4b: Add Sentry and Metrics switches and environment tag to DTL
- Updated dependencies [5e5d4a1]
- @eth-optimism/contracts@0.3.3
## 0.3.2
### Patch Changes
- f5185bb: Fix bug with replica syncing where contract creations would fail in replicas but pass in the sequencer. This was due to the change from a custom batched tx serialization to the batch serialzation for txs being regular RLP encoding
- Updated dependencies [7dd2f72]
- @eth-optimism/contracts@0.3.2
## 0.3.1
### Patch Changes
- e28cec7: Fixes a bug where L2 synced transactions were not RLP encoded
- 96a586e: Migrate bcfg interface to core-utils
- fa4898a: Explicitly log error messages so that they do not show as empty objects
- 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
- b799caa: Parse and index the value field in the data transport layer
- b799caa: Account for the off by one with regards to the l2geth block number and the CTC index
- b799caa: Remove legacy transaction deserialization to support RLP batch encoding
- b799caa: Prevent access of null value in L1 transaction deserialization
- 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
- ce7fa52: Add an additional enum for EthSign transactions as they now are batch submitted with 2 different enum values
- 575bcf6: add environment and network to dtl, move metric init to app from base-service
- 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
- 47e40a2: Update the config parsing so that it gives a better error message
- a0a0052: Parse and index the value field in the data transport layer
- 34ab776: Better error logging in the DTL
- e6350e2: add metrics to measure http endpoint latency
- 28dc442: move metrics, logger, and base-service to new common-ts package
- a0a0052: Prevent access of null value in L1 transaction deserialization
- 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
- 01a2e7d: Clean up config parsing to match CLI argument configuration
- 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
## 0.2.1
### Patch Changes
- a3dc553: Adds a release version to batch-submitter and data-transport-layer usage of Sentry
- 27f32ca: Allow the DTL to provide data from either L1 or L2, configurable via a query param sent by the client.
The config option `default-backend` can be used to specify the backend to be
used if the query param is not specified. This allows it to be backwards
compatible with how the DTL was previously used.
- 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.2.0
### Minor Changes
- 91460d9: add Metrics and use in base-service, rename DTL services to avoid spaces
### Patch Changes
- 0497d7d: Re-organize event typings to core-utils
- Updated dependencies [91460d9]
- Updated dependencies [a0a7956]
- Updated dependencies [0497d7d]
- @eth-optimism/core-utils@0.3.0
- @eth-optimism/contracts@0.2.5
## 0.1.6
### Patch Changes
- 35b99b0: add Sentry to TypeScript services for error tracking
- Updated dependencies [35b99b0]
- @eth-optimism/core-utils@0.2.3
## 0.1.5
### Patch Changes
- 01eaf2c: added extra logs to base-service / dtl to improve observability
- Updated dependencies [01eaf2c]
- @eth-optimism/core-utils@0.2.2
## 0.1.4
### Patch Changes
- 3b00b7c: bump private package versions to try triggering a tag
## 0.1.3
### Patch Changes
- Updated dependencies [6cbc54d]
- @eth-optimism/core-utils@0.2.0
- @eth-optimism/contracts@0.2.2
## v0.1.2
- Fix bug in L2 sync
## v0.1.1
- Prioritize L2 synced API requests
- Stop syncing L2 at a certain height
## v0.1.0
- Sync From L1
- Sync From L2
- Initial 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.
# @eth-optimism/data-transport-layer
[![codecov](https://codecov.io/gh/ethereum-optimism/optimism/branch/develop/graph/badge.svg?token=0VTG7PG7YR&flag=dtl-tests)](https://codecov.io/gh/ethereum-optimism/optimism)
## What is this?
The Optimism Data Transport Layer is a long-running software service (written in TypeScript) designed to reliably index Optimism transaction data from Layer 1 (Ethereum). Specifically, this service indexes:
* Transactions that have been enqueued for submission to the CanonicalTransactionChain via [`CanonicalTransactionChain.enqueue`].
* Transactions that have been included in the CanonicalTransactionChain via [`CanonicalTransactionChain.appendQueueBatch`] or [`CanonicalTransactionChain.appendSequencerBatch`].
* State roots (transaction results) that have been published to the StateCommitmentChain via [`StateCommitmentChain.appendStateBatch`].
## How does it work?
We run two sub-services, the [`L1IngestionService`](./src/services/l1-ingestion/service.ts) and the [`L1TransportServer`](./src/services/server/service.ts). The `L1IngestionService` is responsible for querying for the various events and transaction data necessary to accurately index information from our Layer 1 (Ethereum) smart contracts. The `L1TransportServer` simply provides an API for accessing this information.
## Getting started
### Configuration
See an example config at [.env.example](.env.example); copy into a `.env` file before running.
`L1_TRANSPORT__L1_RPC_ENDPOINT` can be the JSON RPC endpoint of any L1 Ethereum node. `L1_TRANSPORT__ADDRESS_MANAGER` should be the contract addresss of the Address Manager on the corresponding network; find their values in the [contracts package](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments).
### Building and usage
After cloning and switching to the repository, install dependencies:
```bash
$ yarn
```
Use the following commands to build, use, test, and lint:
```bash
$ yarn build
$ yarn start
$ yarn test
$ yarn lint
```
## Configuration
We're using `dotenv` for our configuration.
Copy `.env.example` into `.env`, feel free to modify it.
Here's the list of environment variables you can change:
| Variable | Default | Description |
| ------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| DATA_TRANSPORT_LAYER__DB_PATH | ./db | Path to the database for this service. |
| DATA_TRANSPORT_LAYER__ADDRESS_MANAGER | - | Address of the AddressManager contract on L1. See [contracts](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments) package to find this address for mainnet or kovan. |
| DATA_TRANSPORT_LAYER__POLLING_INTERVAL | 5000 | Period of time between execution loops. |
| DATA_TRANSPORT_LAYER__DANGEROUSLY_CATCH_ALL_ERRORS | false | If true, will catch all errors without throwing. |
| DATA_TRANSPORT_LAYER__CONFIRMATIONS | 12 | Number of confirmations to wait before accepting transactions as "canonical". |
| DATA_TRANSPORT_LAYER__SERVER_HOSTNAME | localhost | Host to run the API on. |
| DATA_TRANSPORT_LAYER__SERVER_PORT | 7878 | Port to run the API on. |
| DATA_TRANSPORT_LAYER__SYNC_FROM_L1 | true | Whether or not to sync from L1. |
| DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT | - | RPC endpoint for an L1 node. |
| DATA_TRANSPORT_LAYER__L1_RPC_USER | - | Basic Authentication user for the L1 node endpoint. |
| DATA_TRANSPORT_LAYER__L1_RPC_PASSWORD | - | Basic Authentication password for the L1 node endpoint. |
| DATA_TRANSPORT_LAYER__LOGS_PER_POLLING_INTERVAL | 2000 | Logs to sync per polling interval. |
| DATA_TRANSPORT_LAYER__SYNC_FROM_L2 | false | Whether or not to sync from L2. |
| DATA_TRANSPORT_LAYER__L2_RPC_ENDPOINT | - | RPC endpoint for an L2 node. |
| DATA_TRANSPORT_LAYER__L2_RPC_USER | - | Basic Authentication user for the L2 node endpoint. |
| DATA_TRANSPORT_LAYER__L2_RPC_PASSWORD | - | Basic Authentication password for the L2 node endpoint. |
| DATA_TRANSPORT_LAYER__TRANSACTIONS_PER_POLLING_INTERVAL | 1000 | Number of L2 transactions to query per polling interval. |
| DATA_TRANSPORT_LAYER__L2_CHAIN_ID | - | L2 chain ID. |
| DATA_TRANSPORT_LAYER__LEGACY_SEQUENCER_COMPATIBILITY | false | Whether or not to enable "legacy" sequencer sync (without the custom `eth_getBlockRange` endpoint) |
| DATA_TRANSPORT_LAYER__NODE_ENV | development | Environment the service is running in: production, development, or test. |
| DATA_TRANSPORT_LAYER__ETH_NETWORK_NAME | - | L1 Ethereum network the service is deployed to: mainnet, kovan, goerli. |
| DATA_TRANSPORT_LAYER__L1_GAS_PRICE_BACKEND | l1 | Where to pull the l1 gas price from (l1 or l2) |
| DATA_TRANSPORT_LAYER__DEFAULT_BACKEND | l1 | Where to sync transactions from (l1 or l2) |
To enable proper error tracking via Sentry on deployed instances, make sure `NODE_ENV` and `ETH_NETWORK_NAME` are set in addition to [`SENTRY_DSN`](https://docs.sentry.io/platforms/node/).
## HTTP API
This section describes the HTTP API for accessing indexed Layer 1 data.
### Latest Ethereum Block Context
#### Request
```
GET /eth/context/latest
```
#### Response
```ts
{
"blockNumber": number,
"timestamp": number
}
```
### Latest Ethereum L1 Gas Price
#### Request
```
GET /eth/gasprice
```
Defaults to pulling L1 gas price from config option `DATA_TRANSPORT_LAYER__L1_GAS_PRICE_BACKEND`. Can be overridden with query parameter `backend` (`/eth/gasprice?backend=l1`).
#### Response
```ts
{
"gasPrice": string
}
```
### Enqueue by Index
#### Request
```
GET /enqueue/index/{index: number}
```
#### Response
```ts
{
"index": number,
"target": string,
"data": string,
"gasLimit": number,
"origin": string,
"blockNumber": number,
"timestamp": number
}
```
### Transaction by Index
#### Request
```
GET /transaction/index/{index: number}
```
#### Response
```ts
{
"transaction": {
"index": number,
"batchIndex": number,
"data": string,
"blockNumber": number,
"timestamp": number,
"gasLimit": number,
"target": string,
"origin": string,
"queueOrigin": string,
"type": string | null,
"decoded": {
"sig": {
"r": string,
"s": string,
"v": string
},
"gasLimit": number,
"gasPrice": number,
"nonce": number,
"target": string,
"data": string
} | null,
"queueIndex": number | null,
},
"batch": {
"index": number,
"blockNumber": number,
"timestamp": number,
"submitter": string,
"size": number,
"root": string,
"prevTotalElements": number,
"extraData": string
}
}
```
### Transaction Batch by Index
#### Request
```
GET /batch/transaction/index/{index: number}
```
#### Response
```ts
{
"batch": {
"index": number,
"blockNumber": number,
"timestamp": number,
"submitter": string,
"size": number,
"root": string,
"prevTotalElements": number,
"extraData": string
},
"transactions": [
{
"index": number,
"batchIndex": number,
"data": string,
"blockNumber": number,
"timestamp": number,
"gasLimit": number,
"target": string,
"origin": string,
"queueOrigin": string,
"type": string | null,
"decoded": {
"sig": {
"r": string,
"s": string,
"v": string
},
"gasLimit": number,
"gasPrice": number,
"nonce": number,
"target": string,
"data": string
} | null,
"queueIndex": number | null,
}
]
}
```
### State Root by Index
#### Request
```
GET /stateroot/index/{index: number}
```
#### Response
```ts
{
"stateRoot": {
"index": number,
"batchIndex": number,
"value": string
},
"batch": {
"index": number,
"blockNumber": number,
"timestamp": number,
"submitter": string,
"size": number,
"root": string,
"prevTotalElements": number,
"extraData": string
},
}
```
### State Root Batch by Index
#### Request
```
GET /batch/stateroot/index/{index: number}
```
#### Response
```ts
{
"batch": {
"index": number,
"blockNumber": number,
"timestamp": number,
"submitter": string,
"size": number,
"root": string,
"prevTotalElements": number,
"extraData": string
},
"stateRoots": [
{
"index": number,
"batchIndex": number,
"value": string
}
]
}
```
openapi: 3.0.0
info:
title: Ethereum Optimism Data Transport Later
description: |
Make sequencer or contract data available behind a common API
version: 0.1.0
components:
schemas:
EthereumContext:
type: object
properties:
blockNumber:
type: integer
format: "int64"
timestamp:
type: integer
format: "int64"
blockHash:
type: string
EnqueueEntry:
type: object
properties:
index:
type: number
target:
type: string
data:
type: string
gasLimit:
type: number
origin:
type: string
blockNumber:
type: number
timestamp:
type: number
ctcIndex:
type: number
BatchEntry:
type: object
properties:
index:
type: number
blockNumber:
type: number
timestamp:
type: number
submitter:
type: string
size:
type: number
root:
type: string
prevTotalElements:
type: number
extraData:
type: string
l1TransactionHash:
type: string
TransactionEntry:
type: object
properties:
index:
type: number
batchIndex:
type: number
data:
type: string
blockNumber:
type: number
timestamp:
type: number
gasLimit:
type: number
target:
type: string
origin:
type: string
queueOrigin:
type: string
queueIndex:
type: number
decoded:
type: object
$ref: '#/components/schemas/DecodedSequencerBatchTransaction'
TransactionResponse:
type: object
properties:
batch:
type: object
$ref: '#/components/schemas/BatchEntry'
transaction:
type: object
$ref: '#/components/schemas/TransactionEntry'
TransactionBatchResponse:
type: object
properties:
batch:
type: object
$ref: '#/components/schemas/BatchEntry'
transactions:
type: array
items:
type: object
$ref: '#/components/schemas/TransactionEntry'
DecodedSequencerBatchTransaction:
type: object
properties:
sig:
type: object
$ref: '#/components/schemas/ECDSASignature'
gasLimit:
type: number
gasPrice:
type: number
nonce:
type: number
target:
type: string
data:
type: string
ECDSASignature:
type: object
properties:
r:
type: string
s:
type: string
v:
type: number
StateRootEntry:
type: object
properties:
index:
type: number
batchIndex:
type: number
value:
type: string
StateRootResponse:
type: object
properties:
batch:
type: object
$ref: '#/components/schemas/BatchEntry'
stateRoot:
type: object
$ref: '#/components/schemas/StateRootEntry'
StateRootBatchResponse:
type: object
properties:
batch:
type: object
$ref: '#/components/schemas/BatchEntry'
stateRoots:
type: array
items:
type: object
$ref: '#/components/schemas/StateRootEntry'
paths:
/eth/syncing:
get:
summary: Returns the sync status
description: |
The node may still be syncing to the tip and downstream services can learn up to what
point has been indexed.
responses:
'200':
description: An object representing the progression of the service
content:
application/json:
schema:
type: object
properties:
syncing:
type: boolean
currentTransactionIndex:
type: integer
format: "int64"
highestKnownTransactionIndex:
type: integer
format: "int64"
/eth/context/latest:
get:
summary: Returns the latest Ethereum Layer one context
description: |
This returns the L1 blocknumber, block hash and timestamp corresponding to the most
recently ingested block
responses:
'200':
description: An object representing the Ethereum context
content:
application/json:
schema:
$ref: '#/components/schemas/EthereumContext'
/eth/context/blocknumber/{number}:
get:
parameters:
- in: path
name: number
schema:
type: integer
format: "int64"
required: true
summary: Returns the Ethereum Layer one context at a specific height
description: |
This returns the L1 blocknumber, block hash and timestamp corresponding to a specific
Ethereum context
responses:
'200':
description: An object representing the Ethereum context
content:
application/json:
schema:
$ref: '#/components/schemas/EthereumContext'
/enqueue/latest:
get:
summary: Returns the latest enqueued transaction
description: |
This returns the latest transaction sent to the Canonical Transaction Chain `enqueue()`
responses:
'200':
description: An object representing the latest enqueued transaction
content:
application/json:
schema:
$ref: '#/components/schemas/EnqueueEntry'
/enqueue/index/{index}:
get:
parameters:
- in: path
name: index
schema:
type: integer
format: "int64"
required: true
summary: Returns the enqueued transaction by index
description: |
This returns the Canonical Transaction Chain `enqueue()` by index
responses:
'200':
description: An object representing the enqueued transaction by index
content:
application/json:
schema:
$ref: '#/components/schemas/EnqueueEntry'
/transaction/latest:
get:
summary: Returns the latest Canonical Transaction Chain transaction
description: |
This returns the latest transaction to be appended to the Canonical Transaction
Chain via a `sequencerBatchAppend()` or `queueBatchAppend()`
responses:
'200':
description: An object representing the latest transaction
content:
application/json:
schema:
$ref: '#/components/schemas/EnqueueEntry'
/transaction/index/{index}:
get:
parameters:
- in: path
name: index
schema:
type: integer
format: "int64"
required: true
summary: Returns a Canonical Transaction Chain transaction by index
description: |
This returns a transaction that has been appended to the Canonical Transaction
Chain via a `sequencerBatchAppend()` or `queueBatchAppend()` by index
responses:
'200':
description: An object representing a Transaction by index
content:
application/json:
schema:
$ref: '#/components/schemas/TransactionResponse'
/batch/transaction/latest:
get:
summary: Returns the latest Batch to be appended to the Canonical Transaction Chain
description: |
This returns the latest batch that has been appended to the Canonical Transaction
Chain via a `sequencerBatchAppend()`
responses:
'200':
description: An object representing the latest batch
content:
application/json:
schema:
$ref: '#/components/schemas/TransactionBatchResponse'
/batch/transaction/index/{index}:
get:
parameters:
- in: path
name: index
schema:
type: integer
format: "int64"
required: true
summary: Returns the Batch to be appended to the Canonical Transaction Chain by index
description: |
This returns a batch that has been appended to the Canonical Transaction
Chain via a `sequencerBatchAppend()` by index
responses:
'200':
description: An object representing the latest batch
content:
application/json:
schema:
$ref: '#/components/schemas/TransactionBatchResponse'
/stateroot/latest:
get:
summary: Returns the latest state root
description: |
This returns the latest state root appended to the State Commitment Chain
responses:
'200':
description: An object representing the latest state root
content:
application/json:
schema:
$ref: '#/components/schemas/StateRootResponse'
/stateroot/index/{index}:
get:
parameters:
- in: path
name: index
schema:
type: integer
format: "int64"
required: true
summary: Returns the state root by index
description: |
This returns a state root appended to the State Commitment Chain by index
responses:
'200':
description: An object representing the a state root
content:
application/json:
schema:
$ref: '#/components/schemas/StateRootResponse'
/batch/stateroot/latest:
get:
summary: Returns the latest state root batch
description: |
This returns the latest batch of state roots appended to the State Commitment Chain
responses:
'200':
description: An object representing the latest state root batch
content:
application/json:
schema:
$ref: '#/components/schemas/StateRootBatchResponse'
/batch/stateroot/index/{index}:
get:
summary: Returns the state root batch by index
parameters:
- in: path
name: index
schema:
type: integer
format: "int64"
required: true
description: |
This returns a state root batch appended to the State Commitment Chain by index
responses:
'200':
description: An object representing the state root batch by index
content:
application/json:
schema:
$ref: '#/components/schemas/StateRootBatchResponse'
{
"private": true,
"name": "@eth-optimism/data-transport-layer",
"version": "0.5.56",
"description": "[Optimism] Service for shuttling data from L1 into L2",
"main": "dist/index",
"types": "dist/index",
"files": [
"dist/index"
],
"scripts": {
"clean": "rimraf ./dist ./tsconfig.tsbuildinfo",
"clean:db": "rimraf ./db",
"lint": "yarn run lint:fix && yarn run lint:check",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint . --max-warnings=0",
"start": "ts-node ./src/services/run.ts",
"start:local": "ts-node ./src/services/run.ts | pino-pretty",
"test": "hardhat --config test/config/hardhat.config.ts test",
"test:coverage": "nyc hardhat --config test/config/hardhat.config.ts test && nyc merge .nyc_output coverage.json",
"build": "tsc -p tsconfig.json",
"pre-commit": "lint-staged"
},
"keywords": [
"optimism",
"ethereum",
"data",
"transport",
"layer"
],
"homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/packages/data-transport-layer#readme",
"license": "MIT",
"author": "Optimism PBC",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@eth-optimism/common-ts": "0.8.1",
"@eth-optimism/contracts": "0.6.0",
"@eth-optimism/core-utils": "0.12.0",
"@ethersproject/providers": "^5.7.0",
"@ethersproject/transactions": "^5.7.0",
"@sentry/node": "^6.3.1",
"@sentry/tracing": "^6.3.1",
"@types/express": "^4.17.12",
"axios": "^0.21.1",
"bcfg": "^0.1.6",
"bfj": "^7.0.2",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"ethers": "^5.7.0",
"express": "^4.17.1",
"express-prom-bundle": "^6.3.6",
"level6": "npm:level@^6.0.1",
"levelup": "^4.4.0"
},
"devDependencies": {
"@types/cors": "^2.8.9",
"@types/levelup": "^4.3.0",
"@types/level": "^6.0.1",
"bfj": "^7.0.2",
"chai-as-promised": "^7.1.1",
"hardhat": "^2.9.6",
"mocha": "^8.4.0",
"pino-pretty": "^4.7.1",
"prettier": "^2.8.0",
"prom-client": "^13.1.0",
"rimraf": "^3.0.2",
"ts-node": "^10.9.1"
}
}
export * from './chain-constants'
export * from './patch-contexts'
export const PATCH_CONTEXTS = {
10: {
2817218: 1643139411,
2817287: 1643139718,
2817898: 1643140952,
2818512: 1643141859,
2818984: 1643142762,
2819864: 1643144275,
2820902: 1643146079,
2821157: 1643146389,
2821170: 1643146389,
2821339: 1643146689,
2821772: 1643147604,
2821814: 1643147909,
2821952: 1643147909,
2822262: 1643148824,
2822342: 1643149130,
2822425: 1643149130,
2822602: 1643149430,
2822742: 1643149733,
2822987: 1643150660,
2822999: 1643150660,
2823039: 1643150964,
2823046: 1643150964,
2823055: 1643150964,
2823096: 1643151269,
2823205: 1643151572,
2823260: 1643151572,
2823306: 1643151572,
2823322: 1643151572,
2823413: 1643151872,
2823419: 1643151872,
2823460: 1643151872,
2823561: 1643152174,
2823592: 1643152174,
2824036: 1643152774,
2824050: 1643153075,
2824107: 1643153075,
2824247: 1643153376,
2832642: 1643173416,
2835330: 1643181396,
2838173: 1643188371,
2838174: 1643188371,
2838175: 1643188371,
2840388: 1643192601,
2844171: 1643202366,
2845370: 1643204181,
2845931: 1643205096,
2846484: 1643205696,
2894118: 1643281866,
2894119: 1643281866,
2959506: 1643399826,
2967959: 1643419611,
2971530: 1643432181,
2974571: 1643443881,
2981176: 1643465226,
2984205: 1643470986,
2995760: 1643498166,
2996847: 1643501211,
2997086: 1643501811,
2997087: 1643501811,
2997569: 1643503026,
2998970: 1643506101,
3000041: 1643510376,
3000042: 1643510376,
3000973: 1643514306,
3001008: 1643514606,
3001009: 1643514606,
3002529: 1643520081,
3008446: 1643541501,
3009141: 1643543016,
3012287: 1643551521,
3012348: 1643551821,
3022052: 1643574336,
3042815: 1643624616,
3043000: 1643625516,
3060328: 1643656446,
3060471: 1643656746,
3064982: 1643667996,
3070655: 1643683461,
},
}
/* Imports: External */
import { LevelUp } from 'levelup'
import { BigNumber } from 'ethers'
export class SimpleDB {
constructor(public db: LevelUp) {}
public async get<TEntry>(key: string, index: number): Promise<TEntry | null> {
try {
// TODO: Better checks here.
return JSON.parse(await this.db.get(this._makeKey(key, index)))
} catch (err) {
return null
}
}
public async range<TEntry>(
key: string,
startIndex: number,
endIndex: number
): Promise<TEntry[] | []> {
try {
return new Promise<any[]>((resolve) => {
const entries: any[] = []
this.db
.createValueStream({
gte: this._makeKey(key, startIndex),
lt: this._makeKey(key, endIndex),
})
.on('data', (transaction: string) => {
entries.push(JSON.parse(transaction))
})
.on('error', () => {
resolve(null)
})
.on('close', () => {
// TODO: Close vs end? Need to double check later.
resolve(entries)
})
.on('end', () => {
resolve(entries)
})
})
} catch (err) {
return []
}
}
public async put<TEntry>(
entries: {
key: string
index: number
value: TEntry
}[]
): Promise<void> {
return this.db.batch(
entries.map((entry) => {
return {
type: 'put',
key: this._makeKey(entry.key, entry.index),
value: JSON.stringify(entry.value),
}
})
)
}
private _makeKey(key: string, index: number): string {
// prettier-ignore
return `${key}:${BigNumber.from(index).toString().padStart(32, '0')}`
}
}
/* Imports: External */
import { LevelUp } from 'levelup'
import { BigNumber } from 'ethers'
import { BatchType } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { SimpleDB } from './simple-db'
import { PATCH_CONTEXTS, BSS_HF1_INDEX } from '../config'
import {
EnqueueEntry,
StateRootBatchEntry,
StateRootEntry,
TransactionBatchEntry,
TransactionEntry,
} from '../types/database-types'
const TRANSPORT_DB_KEYS = {
ENQUEUE: `enqueue`,
ENQUEUE_CTC_INDEX: `ctc:enqueue`,
TRANSACTION: `transaction`,
UNCONFIRMED_TRANSACTION: `unconfirmed:transaction`,
UNCONFIRMED_HIGHEST: `unconfirmed:highest`,
TRANSACTION_BATCH: `batch:transaction`,
STATE_ROOT: `stateroot`,
UNCONFIRMED_STATE_ROOT: `unconfirmed:stateroot`,
STATE_ROOT_BATCH: `batch:stateroot`,
STARTING_L1_BLOCK: `l1:starting`,
HIGHEST_L2_BLOCK: `l2:highest`,
HIGHEST_SYNCED_BLOCK: `synced:highest`,
CONSISTENCY_CHECK: `consistency:checked`,
}
interface Indexed {
index: number
}
interface ExtraTransportDBOptions {
l2ChainId?: number
}
export class TransportDB {
public db: SimpleDB
public opts: ExtraTransportDBOptions
constructor(leveldb: LevelUp, opts?: ExtraTransportDBOptions) {
this.db = new SimpleDB(leveldb)
this.opts = opts || {}
}
public async putEnqueueEntries(entries: EnqueueEntry[]): Promise<void> {
await this._putEntries(TRANSPORT_DB_KEYS.ENQUEUE, entries)
}
public async putTransactionEntries(
entries: TransactionEntry[]
): Promise<void> {
await this._putEntries(TRANSPORT_DB_KEYS.TRANSACTION, entries)
}
public async putUnconfirmedTransactionEntries(
entries: TransactionEntry[]
): Promise<void> {
await this._putEntries(TRANSPORT_DB_KEYS.UNCONFIRMED_TRANSACTION, entries)
}
public async putTransactionBatchEntries(
entries: TransactionBatchEntry[]
): Promise<void> {
await this._putEntries(TRANSPORT_DB_KEYS.TRANSACTION_BATCH, entries)
}
public async putStateRootEntries(entries: StateRootEntry[]): Promise<void> {
await this._putEntries(TRANSPORT_DB_KEYS.STATE_ROOT, entries)
}
public async putUnconfirmedStateRootEntries(
entries: StateRootEntry[]
): Promise<void> {
await this._putEntries(TRANSPORT_DB_KEYS.UNCONFIRMED_STATE_ROOT, entries)
}
public async putStateRootBatchEntries(
entries: StateRootBatchEntry[]
): Promise<void> {
await this._putEntries(TRANSPORT_DB_KEYS.STATE_ROOT_BATCH, entries)
}
public async putTransactionIndexByQueueIndex(
queueIndex: number,
index: number
): Promise<void> {
await this.db.put([
{
key: TRANSPORT_DB_KEYS.ENQUEUE_CTC_INDEX,
index: queueIndex,
value: index,
},
])
}
public async getTransactionIndexByQueueIndex(index: number): Promise<number> {
return this.db.get(TRANSPORT_DB_KEYS.ENQUEUE_CTC_INDEX, index)
}
public async getEnqueueByIndex(index: number): Promise<EnqueueEntry> {
return this._getEntryByIndex(TRANSPORT_DB_KEYS.ENQUEUE, index)
}
public async getTransactionByIndex(index: number): Promise<TransactionEntry> {
return this._getEntryByIndex(TRANSPORT_DB_KEYS.TRANSACTION, index)
}
public async getUnconfirmedTransactionByIndex(
index: number
): Promise<TransactionEntry> {
return this._getEntryByIndex(
TRANSPORT_DB_KEYS.UNCONFIRMED_TRANSACTION,
index
)
}
public async getTransactionsByIndexRange(
start: number,
end: number
): Promise<TransactionEntry[]> {
return this._getEntries(TRANSPORT_DB_KEYS.TRANSACTION, start, end)
}
public async getTransactionBatchByIndex(
index: number
): Promise<TransactionBatchEntry> {
const entry = (await this._getEntryByIndex(
TRANSPORT_DB_KEYS.TRANSACTION_BATCH,
index
)) as TransactionBatchEntry
if (entry && typeof entry.type === 'undefined') {
entry.type = BatchType[BatchType.LEGACY]
}
return entry
}
public async getStateRootByIndex(index: number): Promise<StateRootEntry> {
return this._getEntryByIndex(TRANSPORT_DB_KEYS.STATE_ROOT, index)
}
public async getUnconfirmedStateRootByIndex(
index: number
): Promise<StateRootEntry> {
return this._getEntryByIndex(
TRANSPORT_DB_KEYS.UNCONFIRMED_STATE_ROOT,
index
)
}
public async getStateRootsByIndexRange(
start: number,
end: number
): Promise<StateRootEntry[]> {
return this._getEntries(TRANSPORT_DB_KEYS.STATE_ROOT, start, end)
}
public async getStateRootBatchByIndex(
index: number
): Promise<StateRootBatchEntry> {
return this._getEntryByIndex(TRANSPORT_DB_KEYS.STATE_ROOT_BATCH, index)
}
public async getLatestEnqueue(): Promise<EnqueueEntry> {
return this._getLatestEntry(TRANSPORT_DB_KEYS.ENQUEUE)
}
public async getLatestTransaction(): Promise<TransactionEntry> {
return this._getLatestEntry(TRANSPORT_DB_KEYS.TRANSACTION)
}
public async getLatestUnconfirmedTransaction(): Promise<TransactionEntry> {
return this._getLatestEntry(TRANSPORT_DB_KEYS.UNCONFIRMED_TRANSACTION)
}
public async getLatestTransactionBatch(): Promise<TransactionBatchEntry> {
const entry = (await this._getLatestEntry(
TRANSPORT_DB_KEYS.TRANSACTION_BATCH
)) as TransactionBatchEntry
if (entry && typeof entry.type === 'undefined') {
entry.type = BatchType[BatchType.LEGACY]
}
return entry
}
public async getLatestStateRoot(): Promise<StateRootEntry> {
return this._getLatestEntry(TRANSPORT_DB_KEYS.STATE_ROOT)
}
public async getLatestUnconfirmedStateRoot(): Promise<StateRootEntry> {
return this._getLatestEntry(TRANSPORT_DB_KEYS.UNCONFIRMED_STATE_ROOT)
}
public async getLatestStateRootBatch(): Promise<StateRootBatchEntry> {
return this._getLatestEntry(TRANSPORT_DB_KEYS.STATE_ROOT_BATCH)
}
public async getHighestL2BlockNumber(): Promise<number> {
return this.db.get<number>(TRANSPORT_DB_KEYS.HIGHEST_L2_BLOCK, 0)
}
public async getConsistencyCheckFlag(): Promise<boolean> {
return this.db.get<boolean>(TRANSPORT_DB_KEYS.CONSISTENCY_CHECK, 0)
}
public async putConsistencyCheckFlag(flag: boolean): Promise<void> {
return this.db.put<boolean>([
{
key: TRANSPORT_DB_KEYS.CONSISTENCY_CHECK,
index: 0,
value: flag,
},
])
}
public async putHighestL2BlockNumber(
block: number | BigNumber
): Promise<void> {
if (block <= (await this.getHighestL2BlockNumber())) {
return
}
return this.db.put<number>([
{
key: TRANSPORT_DB_KEYS.HIGHEST_L2_BLOCK,
index: 0,
value: BigNumber.from(block).toNumber(),
},
])
}
public async getHighestSyncedUnconfirmedBlock(): Promise<number> {
return (
(await this.db.get<number>(TRANSPORT_DB_KEYS.UNCONFIRMED_HIGHEST, 0)) || 0
)
}
public async setHighestSyncedUnconfirmedBlock(block: number): Promise<void> {
return this.db.put<number>([
{
key: TRANSPORT_DB_KEYS.UNCONFIRMED_HIGHEST,
index: 0,
value: block,
},
])
}
public async getHighestSyncedL1Block(): Promise<number> {
return (
(await this.db.get<number>(TRANSPORT_DB_KEYS.HIGHEST_SYNCED_BLOCK, 0)) ||
0
)
}
public async setHighestSyncedL1Block(block: number): Promise<void> {
return this.db.put<number>([
{
key: TRANSPORT_DB_KEYS.HIGHEST_SYNCED_BLOCK,
index: 0,
value: block,
},
])
}
public async getStartingL1Block(): Promise<number> {
return this.db.get<number>(TRANSPORT_DB_KEYS.STARTING_L1_BLOCK, 0)
}
public async setStartingL1Block(block: number): Promise<void> {
return this.db.put<number>([
{
key: TRANSPORT_DB_KEYS.STARTING_L1_BLOCK,
index: 0,
value: block,
},
])
}
// Not sure if this next section belongs in this class.
public async getFullTransactionByIndex(
index: number
): Promise<TransactionEntry> {
const transaction = await this.getTransactionByIndex(index)
if (transaction === null) {
return null
}
return this._makeFullTransaction(transaction)
}
public async getLatestFullTransaction(): Promise<TransactionEntry> {
return this.getFullTransactionByIndex(
await this._getLatestEntryIndex(TRANSPORT_DB_KEYS.TRANSACTION)
)
}
public async getFullTransactionsByIndexRange(
start: number,
end: number
): Promise<TransactionEntry[]> {
const transactions = await this.getTransactionsByIndexRange(start, end)
if (transactions === null) {
return null
}
const fullTransactions = []
for (const transaction of transactions) {
fullTransactions.push(await this._makeFullTransaction(transaction))
}
return fullTransactions
}
private async _makeFullTransaction(
transaction: TransactionEntry
): Promise<TransactionEntry> {
// We only need to do extra work for L1 to L2 transactions.
if (transaction.queueOrigin !== 'l1') {
return transaction
}
const enqueue = await this.getEnqueueByIndex(transaction.queueIndex)
if (enqueue === null) {
return null
}
let timestamp = enqueue.timestamp
// BSS HF1 activates at block 0 if not specified.
const bssHf1Index = BSS_HF1_INDEX[this.opts.l2ChainId] || 0
if (transaction.index >= bssHf1Index) {
timestamp = transaction.timestamp
}
// Override with patch contexts if necessary
const contexts = PATCH_CONTEXTS[this.opts.l2ChainId]
if (contexts && contexts[transaction.index + 1]) {
timestamp = contexts[transaction.index + 1]
}
return {
...transaction,
...{
blockNumber: enqueue.blockNumber,
timestamp,
gasLimit: enqueue.gasLimit,
target: enqueue.target,
origin: enqueue.origin,
data: enqueue.data,
},
}
}
private async _getLatestEntryIndex(key: string): Promise<number> {
return this.db.get<number>(`${key}:latest`, 0) || 0
}
private async _putLatestEntryIndex(
key: string,
index: number
): Promise<void> {
return this.db.put<number>([
{
key: `${key}:latest`,
index: 0,
value: index,
},
])
}
private async _getLatestEntry<TEntry extends Indexed>(
key: string
): Promise<TEntry | null> {
return this._getEntryByIndex(key, await this._getLatestEntryIndex(key))
}
private async _putLatestEntry<TEntry extends Indexed>(
key: string,
entry: TEntry
): Promise<void> {
const latest = await this._getLatestEntryIndex(key)
if (entry.index >= latest) {
await this._putLatestEntryIndex(key, entry.index)
}
}
private async _putEntries<TEntry extends Indexed>(
key: string,
entries: TEntry[]
): Promise<void> {
if (entries.length === 0) {
return
}
await this.db.put<TEntry>(
entries.map((entry) => {
return {
key: `${key}:index`,
index: entry.index,
value: entry,
}
})
)
await this._putLatestEntry(key, entries[entries.length - 1])
}
private async _getEntryByIndex<TEntry extends Indexed>(
key: string,
index: number
): Promise<TEntry | null> {
if (index === null) {
return null
}
return this.db.get<TEntry>(`${key}:index`, index)
}
private async _getEntries<TEntry extends Indexed>(
key: string,
startIndex: number,
endIndex: number
): Promise<TEntry[] | []> {
return this.db.range<TEntry>(`${key}:index`, startIndex, endIndex)
}
}
export type EventName =
| 'TransactionEnqueued'
| 'SequencerBatchAppended'
| 'StateBatchAppended'
| 'SequencerBatchAppendedTransaction'
export class MissingElementError extends Error {
constructor(public name: EventName) {
super(`missing event: ${name}`)
}
}
/* Imports: External */
import { BigNumber, ethers, constants } from 'ethers'
import { serialize, Transaction } from '@ethersproject/transactions'
import { getContractFactory } from '@eth-optimism/contracts'
import {
toHexString,
toRpcHexString,
BatchType,
SequencerBatch,
} from '@eth-optimism/core-utils'
import { SequencerBatchAppendedEvent } from '@eth-optimism/contracts/dist/types/contracts/L1/rollup/CanonicalTransactionChain'
/* Imports: Internal */
import { MissingElementError } from './errors'
import {
DecodedSequencerBatchTransaction,
SequencerBatchAppendedExtraData,
SequencerBatchAppendedParsedEvent,
TransactionBatchEntry,
TransactionEntry,
EventHandlerSet,
} from '../../../types'
import { parseSignatureVParam } from '../../../utils'
export const handleEventsSequencerBatchAppended: EventHandlerSet<
SequencerBatchAppendedEvent,
SequencerBatchAppendedExtraData,
SequencerBatchAppendedParsedEvent
> = {
getExtraData: async (event, l1RpcProvider) => {
const l1Transaction = await event.getTransaction()
const eventBlock = await event.getBlock()
// TODO: We need to update our events so that we actually have enough information to parse this
// batch without having to pull out this extra event. For the meantime, we need to find this
// "TransactonBatchAppended" event to get the rest of the data.
const CanonicalTransactionChain = getContractFactory(
'CanonicalTransactionChain'
)
.attach(event.address)
.connect(l1RpcProvider)
const batchSubmissionEvent = (
await CanonicalTransactionChain.queryFilter(
CanonicalTransactionChain.filters.TransactionBatchAppended(),
eventBlock.number,
eventBlock.number
)
).find((foundEvent: ethers.Event) => {
// We might have more than one event in this block, so we specifically want to find a
// "TransactonBatchAppended" event emitted immediately before the event in question.
return (
foundEvent.transactionHash === event.transactionHash &&
foundEvent.logIndex === event.logIndex - 1
)
})
if (!batchSubmissionEvent) {
throw new Error(
`Well, this really shouldn't happen. A SequencerBatchAppended event doesn't have a corresponding TransactionBatchAppended event.`
)
}
return {
timestamp: eventBlock.timestamp,
blockNumber: eventBlock.number,
submitter: l1Transaction.from,
l1TransactionHash: l1Transaction.hash,
l1TransactionData: l1Transaction.data,
prevTotalElements: batchSubmissionEvent.args._prevTotalElements,
batchIndex: batchSubmissionEvent.args._batchIndex,
batchSize: batchSubmissionEvent.args._batchSize,
batchRoot: batchSubmissionEvent.args._batchRoot,
batchExtraData: batchSubmissionEvent.args._extraData,
}
},
parseEvent: (event, extraData, l2ChainId) => {
const transactionEntries: TransactionEntry[] = []
// 12 * 2 + 2 = 26
if (extraData.l1TransactionData.length < 26) {
throw new Error(
`Block ${extraData.blockNumber} transaction data is too small: ${extraData.l1TransactionData.length}`
)
}
// TODO: typings not working?
const decoded = (SequencerBatch as any).fromHex(extraData.l1TransactionData)
// Keep track of the CTC index
let transactionIndex = 0
// Keep track of the number of deposits
let enqueuedCount = 0
// Keep track of the tx index in the current batch
let index = 0
for (const context of decoded.contexts) {
for (let j = 0; j < context.numSequencedTransactions; j++) {
const buf = decoded.transactions[index]
if (!buf) {
throw new Error(
`Invalid batch context, tx count: ${decoded.transactions.length}, attempting to parse ${index}`
)
}
const tx = buf.toTransaction()
transactionEntries.push({
index: extraData.prevTotalElements
.add(BigNumber.from(transactionIndex))
.toNumber(),
batchIndex: extraData.batchIndex.toNumber(),
blockNumber: BigNumber.from(context.blockNumber).toNumber(),
timestamp: BigNumber.from(context.timestamp).toNumber(),
gasLimit: BigNumber.from(0).toString(),
target: constants.AddressZero,
origin: null,
data: serialize(
{
nonce: tx.nonce,
gasPrice: tx.gasPrice,
gasLimit: tx.gasLimit,
to: tx.to,
value: tx.value,
data: tx.data,
},
{
v: tx.v,
r: tx.r,
s: tx.s,
}
),
queueOrigin: 'sequencer',
value: toRpcHexString(tx.value),
queueIndex: null,
decoded: mapSequencerTransaction(tx, l2ChainId),
confirmed: true,
})
transactionIndex++
index++
}
for (let j = 0; j < context.numSubsequentQueueTransactions; j++) {
const queueIndex = event.args._startingQueueIndex.add(
BigNumber.from(enqueuedCount)
)
// Okay, so. Since events are processed in parallel, we don't know if the Enqueue
// event associated with this queue element has already been processed. So we'll ask
// the api to fetch that data for itself later on and we use fake values for some
// fields. The real TODO here is to make sure we fix this data structure to avoid ugly
// "dummy" fields.
transactionEntries.push({
index: extraData.prevTotalElements
.add(BigNumber.from(transactionIndex))
.toNumber(),
batchIndex: extraData.batchIndex.toNumber(),
blockNumber: BigNumber.from(0).toNumber(),
timestamp: context.timestamp,
gasLimit: BigNumber.from(0).toString(),
target: constants.AddressZero,
origin: constants.AddressZero,
data: '0x',
queueOrigin: 'l1',
value: '0x0',
queueIndex: queueIndex.toNumber(),
decoded: null,
confirmed: true,
})
enqueuedCount++
transactionIndex++
}
}
const transactionBatchEntry: TransactionBatchEntry = {
index: extraData.batchIndex.toNumber(),
root: extraData.batchRoot,
size: extraData.batchSize.toNumber(),
prevTotalElements: extraData.prevTotalElements.toNumber(),
extraData: extraData.batchExtraData,
blockNumber: BigNumber.from(extraData.blockNumber).toNumber(),
timestamp: BigNumber.from(extraData.timestamp).toNumber(),
submitter: extraData.submitter,
l1TransactionHash: extraData.l1TransactionHash,
type: BatchType[decoded.type],
}
return {
transactionBatchEntry,
transactionEntries,
}
},
storeEvent: async (entry, db) => {
// Defend against situations where we missed an event because the RPC provider
// (infura/alchemy/whatever) is missing an event.
if (entry.transactionBatchEntry.index > 0) {
const prevTransactionBatchEntry = await db.getTransactionBatchByIndex(
entry.transactionBatchEntry.index - 1
)
// We should *always* have a previous transaction batch here.
if (prevTransactionBatchEntry === null) {
throw new MissingElementError('SequencerBatchAppended')
}
}
// Same consistency checks but for transaction entries.
if (
entry.transactionEntries.length > 0 &&
entry.transactionEntries[0].index > 0
) {
const prevTransactionEntry = await db.getTransactionByIndex(
entry.transactionEntries[0].index - 1
)
// We should *always* have a previous transaction here.
if (prevTransactionEntry === null) {
throw new MissingElementError('SequencerBatchAppendedTransaction')
}
}
await db.putTransactionEntries(entry.transactionEntries)
// Add an additional field to the enqueued transactions in the database
// if they have already been confirmed
for (const transactionEntry of entry.transactionEntries) {
if (transactionEntry.queueOrigin === 'l1') {
await db.putTransactionIndexByQueueIndex(
transactionEntry.queueIndex,
transactionEntry.index
)
}
}
await db.putTransactionBatchEntries([entry.transactionBatchEntry])
},
}
const mapSequencerTransaction = (
tx: Transaction,
l2ChainId: number
): DecodedSequencerBatchTransaction => {
return {
nonce: BigNumber.from(tx.nonce).toString(),
gasPrice: BigNumber.from(tx.gasPrice).toString(),
gasLimit: BigNumber.from(tx.gasLimit).toString(),
value: toRpcHexString(tx.value),
target: tx.to ? toHexString(tx.to) : null,
data: toHexString(tx.data),
sig: {
v: parseSignatureVParam(tx.v, l2ChainId),
r: toHexString(tx.r),
s: toHexString(tx.s),
},
}
}
/* Imports: External */
import { StateBatchAppendedEvent } from '@eth-optimism/contracts/dist/types/contracts/L1/rollup/StateCommitmentChain'
import { getContractFactory } from '@eth-optimism/contracts'
import { BigNumber } from 'ethers'
/* Imports: Internal */
import { MissingElementError } from './errors'
import {
StateRootBatchEntry,
StateBatchAppendedExtraData,
StateBatchAppendedParsedEvent,
StateRootEntry,
EventHandlerSet,
} from '../../../types'
export const handleEventsStateBatchAppended: EventHandlerSet<
StateBatchAppendedEvent,
StateBatchAppendedExtraData,
StateBatchAppendedParsedEvent
> = {
getExtraData: async (event) => {
const eventBlock = await event.getBlock()
const l1Transaction = await event.getTransaction()
return {
timestamp: eventBlock.timestamp,
blockNumber: eventBlock.number,
submitter: l1Transaction.from,
l1TransactionHash: l1Transaction.hash,
l1TransactionData: l1Transaction.data,
}
},
parseEvent: (event, extraData) => {
const stateRoots = getContractFactory(
'StateCommitmentChain'
).interface.decodeFunctionData(
'appendStateBatch',
extraData.l1TransactionData
)[0]
const stateRootEntries: StateRootEntry[] = []
for (let i = 0; i < stateRoots.length; i++) {
stateRootEntries.push({
index: event.args._prevTotalElements.add(BigNumber.from(i)).toNumber(),
batchIndex: event.args._batchIndex.toNumber(),
value: stateRoots[i],
confirmed: true,
})
}
// Using .toNumber() here and in other places because I want to move everything to use
// BigNumber + hex, but that'll take a lot of work. This makes it easier in the future.
const stateRootBatchEntry: StateRootBatchEntry = {
index: event.args._batchIndex.toNumber(),
blockNumber: BigNumber.from(extraData.blockNumber).toNumber(),
timestamp: BigNumber.from(extraData.timestamp).toNumber(),
submitter: extraData.submitter,
size: event.args._batchSize.toNumber(),
root: event.args._batchRoot,
prevTotalElements: event.args._prevTotalElements.toNumber(),
extraData: event.args._extraData,
l1TransactionHash: extraData.l1TransactionHash,
type: 'LEGACY', // There is currently only 1 state root batch type
}
return {
stateRootBatchEntry,
stateRootEntries,
}
},
storeEvent: async (entry, db) => {
// Defend against situations where we missed an event because the RPC provider
// (infura/alchemy/whatever) is missing an event.
if (entry.stateRootBatchEntry.index > 0) {
const prevStateRootBatchEntry = await db.getStateRootBatchByIndex(
entry.stateRootBatchEntry.index - 1
)
// We should *always* have a previous batch entry here.
if (prevStateRootBatchEntry === null) {
throw new MissingElementError('StateBatchAppended')
}
}
await db.putStateRootBatchEntries([entry.stateRootBatchEntry])
await db.putStateRootEntries(entry.stateRootEntries)
},
}
/* Imports: External */
import { BigNumber } from 'ethers'
import { TransactionEnqueuedEvent } from '@eth-optimism/contracts/dist/types/contracts/L1/rollup/CanonicalTransactionChain'
/* Imports: Internal */
import { MissingElementError } from './errors'
import { EnqueueEntry, EventHandlerSet } from '../../../types'
export const handleEventsTransactionEnqueued: EventHandlerSet<
TransactionEnqueuedEvent,
null,
EnqueueEntry
> = {
getExtraData: async () => {
return null
},
parseEvent: (event) => {
return {
index: event.args._queueIndex.toNumber(),
target: event.args._target,
data: event.args._data,
gasLimit: event.args._gasLimit.toString(),
origin: event.args._l1TxOrigin,
blockNumber: BigNumber.from(event.blockNumber).toNumber(),
timestamp: event.args._timestamp.toNumber(),
ctcIndex: null,
}
},
storeEvent: async (entry, db) => {
// Defend against situations where we missed an event because the RPC provider
// (infura/alchemy/whatever) is missing an event.
if (entry.index > 0) {
const prevEnqueueEntry = await db.getEnqueueByIndex(entry.index - 1)
// We should *alwaus* have a previous enqueue entry here.
if (prevEnqueueEntry === null) {
throw new MissingElementError('TransactionEnqueued')
}
}
await db.putEnqueueEntries([entry])
},
}
/* Imports: External */
import { fromHexString, getChainId, sleep } from '@eth-optimism/core-utils'
import { BaseService, LegacyMetrics } from '@eth-optimism/common-ts'
import { TypedEvent } from '@eth-optimism/contracts/dist/types/common'
import { BaseProvider, StaticJsonRpcProvider } from '@ethersproject/providers'
import { LevelUp } from 'levelup'
import { BigNumber, constants } from 'ethers'
import { Gauge, Counter } from 'prom-client'
/* Imports: Internal */
import { handleEventsTransactionEnqueued } from './handlers/transaction-enqueued'
import { handleEventsSequencerBatchAppended } from './handlers/sequencer-batch-appended'
import { handleEventsStateBatchAppended } from './handlers/state-batch-appended'
import { MissingElementError } from './handlers/errors'
import { TransportDB } from '../../db/transport-db'
import {
OptimismContracts,
loadOptimismContracts,
loadContract,
validators,
} from '../../utils'
import { EventHandlerSet } from '../../types'
import { L1DataTransportServiceOptions } from '../main/service'
interface L1IngestionMetrics {
highestSyncedL1Block: Gauge<string>
missingElementCount: Counter<string>
unhandledErrorCount: Counter<string>
}
const registerMetrics = ({
client,
registry,
}: LegacyMetrics): L1IngestionMetrics => ({
highestSyncedL1Block: new client.Gauge({
name: 'data_transport_layer_highest_synced_l1_block',
help: 'Highest Synced L1 Block Number',
registers: [registry],
}),
missingElementCount: new client.Counter({
name: 'data_transport_layer_missing_element_count',
help: 'Number of times recovery from missing elements happens',
registers: [registry],
}),
unhandledErrorCount: new client.Counter({
name: 'data_transport_layer_l1_unhandled_error_count',
help: 'Number of times recovered from unhandled errors',
registers: [registry],
}),
})
export interface L1IngestionServiceOptions
extends L1DataTransportServiceOptions {
db: LevelUp
metrics: LegacyMetrics
}
const optionSettings = {
db: {
validate: validators.isLevelUP,
},
addressManager: {
validate: validators.isAddress,
},
confirmations: {
default: 35,
validate: validators.isInteger,
},
pollingInterval: {
default: 5000,
validate: validators.isInteger,
},
logsPerPollingInterval: {
default: 2000,
validate: validators.isInteger,
},
dangerouslyCatchAllErrors: {
default: false,
validate: validators.isBoolean,
},
l1RpcProvider: {
validate: (val: any) => {
return validators.isString(val) || validators.isJsonRpcProvider(val)
},
},
l2ChainId: {
validate: validators.isInteger,
},
}
export class L1IngestionService extends BaseService<L1IngestionServiceOptions> {
constructor(options: L1IngestionServiceOptions) {
super('L1_Ingestion_Service', options, optionSettings)
}
private l1IngestionMetrics: L1IngestionMetrics
private state: {
db: TransportDB
contracts: OptimismContracts
l1RpcProvider: BaseProvider
startingL1BlockNumber: number
addressCache: {
[name: string]: string
}
} = {} as any
protected async _init(): Promise<void> {
this.state.db = new TransportDB(this.options.db, {
l2ChainId: this.options.l2ChainId,
})
this.l1IngestionMetrics = registerMetrics(this.metrics)
if (typeof this.options.l1RpcProvider === 'string') {
this.state.l1RpcProvider = new StaticJsonRpcProvider({
url: this.options.l1RpcProvider,
user: this.options.l1RpcProviderUser,
password: this.options.l1RpcProviderPassword,
headers: { 'User-Agent': 'data-transport-layer' },
})
} else {
this.state.l1RpcProvider = this.options.l1RpcProvider
}
// Make sure that the given provider is connected to L1 and not L2
const connectedChainId = await getChainId(this.state.l1RpcProvider)
if (connectedChainId === this.options.l2ChainId) {
throw new Error(
`Given L1 RPC provider is actually an L2 provider, please provide an L1 provider`
)
}
this.logger.info('Using AddressManager', {
addressManager: this.options.addressManager,
})
const Lib_AddressManager = loadContract(
'Lib_AddressManager',
this.options.addressManager,
this.state.l1RpcProvider
)
const code = await this.state.l1RpcProvider.getCode(
Lib_AddressManager.address
)
if (fromHexString(code).length === 0) {
throw new Error(
`Provided AddressManager doesn't have any code: ${Lib_AddressManager.address}`
)
}
try {
// Just check to make sure this doesn't throw. If this is a valid AddressManager, then this
// call should succeed. If it throws, then our AddressManager is broken. We don't care about
// the result.
await Lib_AddressManager.getAddress(
`Here's a contract name that definitely doesn't exist.`
)
} catch (err) {
throw new Error(
`Seems like your AddressManager is busted: ${Lib_AddressManager.address}`
)
}
// Would be nice if this weren't necessary, maybe one day.
// TODO: Probably just assert inside here that all of the contracts have code in them.
this.state.contracts = await loadOptimismContracts(
this.state.l1RpcProvider,
this.options.addressManager
)
// Look up in the database for an indexed starting L1 block
let startingL1BlockNumber = await this.state.db.getStartingL1Block()
// If there isn't an indexed starting L1 block, that means we should pull it
// from config and then fallback to discovering it
if (startingL1BlockNumber === null || startingL1BlockNumber === undefined) {
if (
this.options.l1StartHeight !== null &&
this.options.l1StartHeight !== undefined
) {
startingL1BlockNumber = this.options.l1StartHeight
} else {
this.logger.info(
'Attempting to find an appropriate L1 block height to begin sync. This may take a long time.'
)
startingL1BlockNumber = await this._findStartingL1BlockNumber()
}
}
if (!startingL1BlockNumber) {
throw new Error('Cannot find starting L1 block number')
}
this.logger.info('Starting sync', {
startingL1BlockNumber,
})
this.state.startingL1BlockNumber = startingL1BlockNumber
await this.state.db.setStartingL1Block(this.state.startingL1BlockNumber)
// Store the total number of submitted transactions so the server can tell clients if we're
// done syncing or not
const totalElements =
await this.state.contracts.CanonicalTransactionChain.getTotalElements()
if (totalElements > 0) {
await this.state.db.putHighestL2BlockNumber(totalElements - 1)
}
// Initialize the address cache.
this.state.addressCache = {}
}
protected async _start(): Promise<void> {
// This is our main function. It's basically just an infinite loop that attempts to stay in
// sync with events coming from Ethereum. Loops as quickly as it can until it approaches the
// tip of the chain, after which it starts waiting for a few seconds between each loop to avoid
// unnecessary spam.
while (this.running) {
try {
const highestSyncedL1Block =
(await this.state.db.getHighestSyncedL1Block()) ||
this.state.startingL1BlockNumber
const currentL1Block = await this.state.l1RpcProvider.getBlockNumber()
let targetL1Block = Math.min(
highestSyncedL1Block + this.options.logsPerPollingInterval,
currentL1Block - this.options.confirmations
)
// Don't sync beyond the shutoff block!
if (Number.isInteger(this.options.l1SyncShutoffBlock)) {
targetL1Block = Math.min(
targetL1Block,
this.options.l1SyncShutoffBlock
)
}
// We're already at the head, so no point in attempting to sync.
if (highestSyncedL1Block === targetL1Block) {
await sleep(this.options.pollingInterval)
continue
}
this.logger.info('Synchronizing events from Layer 1 (Ethereum)', {
highestSyncedL1Block,
targetL1Block,
})
// We always try loading the deposit shutoff block in every loop! Less efficient but will
// guarantee that we don't miss the shutoff block being set and that we can automatically
// recover if the shutoff block is set and then unset.
const depositShutoffBlock = BigNumber.from(
await this.state.contracts.Lib_AddressManager.getAddress(
'DTL_SHUTOFF_BLOCK'
)
).toNumber()
// If the deposit shutoff block is set, then we should stop syncing deposits at that block.
let depositTargetL1Block = targetL1Block
if (depositShutoffBlock > 0) {
this.logger.info(`Deposit shutoff active`, {
targetL1Block,
depositShutoffBlock,
})
depositTargetL1Block = Math.min(
depositTargetL1Block,
depositShutoffBlock
)
}
// We should not sync TransactionEnqueued events beyond the deposit shutoff block.
if (depositTargetL1Block >= highestSyncedL1Block) {
await this._syncEvents(
'CanonicalTransactionChain',
'TransactionEnqueued',
highestSyncedL1Block,
depositTargetL1Block,
handleEventsTransactionEnqueued
)
} else {
this.logger.info('Deposit shutoff reached', {
depositTargetL1Block,
highestSyncedL1Block,
depositShutoffBlock,
})
}
await this._syncEvents(
'CanonicalTransactionChain',
'SequencerBatchAppended',
highestSyncedL1Block,
targetL1Block,
handleEventsSequencerBatchAppended
)
await this._syncEvents(
'StateCommitmentChain',
'StateBatchAppended',
highestSyncedL1Block,
targetL1Block,
handleEventsStateBatchAppended
)
await this.state.db.setHighestSyncedL1Block(targetL1Block)
this.l1IngestionMetrics.highestSyncedL1Block.set(targetL1Block)
if (
currentL1Block - highestSyncedL1Block <
this.options.logsPerPollingInterval
) {
await sleep(this.options.pollingInterval)
}
} catch (err) {
if (err instanceof MissingElementError) {
this.logger.warn('recovering from a missing event', {
message: err.toString(),
})
// Different functions for getting the last good element depending on the event type.
const handlers = {
SequencerBatchAppended:
this.state.db.getLatestTransactionBatch.bind(this.state.db),
SequencerBatchAppendedTransaction:
this.state.db.getLatestTransaction.bind(this.state.db),
StateBatchAppended: this.state.db.getLatestStateRootBatch.bind(
this.state.db
),
TransactionEnqueued: this.state.db.getLatestEnqueue.bind(
this.state.db
),
}
// Find the last good element and reset the highest synced L1 block to go back to the
// last good element. Will resync other event types too but we have no issues with
// syncing the same events more than once.
const eventName = err.name
if (!(eventName in handlers)) {
throw new Error(
`unable to recover from missing event, no handler for ${eventName}`
)
}
const lastGoodElement: {
blockNumber: number
} = await handlers[eventName]()
// Erroring out here seems fine. An error like this is only likely to occur quickly after
// this service starts up so someone will be here to deal with it. Automatic recovery is
// nice but not strictly necessary. Could be a good feature for someone to implement.
if (lastGoodElement === null) {
throw new Error(`unable to recover from missing event`)
}
// Rewind back to the block number that the last good element was in.
await this.state.db.setHighestSyncedL1Block(
lastGoodElement.blockNumber
)
this.l1IngestionMetrics.highestSyncedL1Block.set(
lastGoodElement.blockNumber
)
// Something we should be keeping track of.
this.logger.warn('recovered from a missing event', {
eventName,
lastGoodBlockNumber: lastGoodElement.blockNumber,
})
this.l1IngestionMetrics.missingElementCount.inc()
} else if (!this.running || this.options.dangerouslyCatchAllErrors) {
this.l1IngestionMetrics.unhandledErrorCount.inc()
this.logger.error('Caught an unhandled error', {
message: err.toString(),
stack: err.stack,
code: err.code,
})
await sleep(this.options.pollingInterval)
} else {
throw err
}
}
}
}
private async _syncEvents(
contractName: string,
eventName: string,
fromL1Block: number,
toL1Block: number,
handlers: EventHandlerSet<any, any, any>
): Promise<void> {
// Basic sanity checks.
if (!this.state.contracts[contractName]) {
throw new Error(`Contract ${contractName} does not exist.`)
}
// Basic sanity checks.
if (!this.state.contracts[contractName].filters[eventName]) {
throw new Error(
`Event ${eventName} does not exist on contract ${contractName}`
)
}
// We need to figure out how to make this work without Infura. Mark and I think that infura is
// doing some indexing of events beyond Geth's native capabilities, meaning some event logic
// will only work on Infura and not on a local geth instance. Not great.
const addressSetEvents =
await this.state.contracts.Lib_AddressManager.queryFilter(
this.state.contracts.Lib_AddressManager.filters.AddressSet(
contractName
),
fromL1Block,
toL1Block
)
// We're going to parse things out in ranges because the address of a given contract may have
// changed in the range provided by the user.
const eventRanges: {
address: string
fromBlock: number
toBlock: number
}[] = []
let l1BlockRangeStart = fromL1Block
// Addresses on mainnet do NOT change. We can therefore skip costly checks related to cases
// where addresses do change (mainly on testnets).
if (this.options.l2ChainId === 10) {
eventRanges.push({
address: await this._getFixedAddress(contractName),
fromBlock: l1BlockRangeStart,
toBlock: toL1Block,
})
} else if (this.options.l2ChainId === 420) {
if (
l1BlockRangeStart < 7260849 &&
contractName === 'StateCommitmentChain'
) {
if (toL1Block < 7260849) {
eventRanges.push({
address: '0x72281826e90dd8a65ab686ff254eb45be426dd22',
fromBlock: l1BlockRangeStart,
toBlock: toL1Block,
})
} else {
eventRanges.push({
address: '0x72281826e90dd8a65ab686ff254eb45be426dd22',
fromBlock: l1BlockRangeStart,
toBlock: 7260849,
})
eventRanges.push({
address: await this._getFixedAddress(contractName),
fromBlock: 7260849,
toBlock: toL1Block,
})
}
} else {
eventRanges.push({
address: await this._getFixedAddress(contractName),
fromBlock: l1BlockRangeStart,
toBlock: toL1Block,
})
}
} else {
// Addresses can change on non-mainnet deployments. If an address changes, we will
// potentially need to sync events from both the old address and the new address. We will
// add an entry here for each address change.
for (const addressSetEvent of addressSetEvents) {
eventRanges.push({
address: addressSetEvent.args._oldAddress,
fromBlock: l1BlockRangeStart,
toBlock: addressSetEvent.blockNumber,
})
l1BlockRangeStart = addressSetEvent.blockNumber
}
// Add one more range to get us to the end of the user-provided block range.
eventRanges.push({
address: await this._getContractAddressAtBlock(contractName, toL1Block),
fromBlock: l1BlockRangeStart,
toBlock: toL1Block,
})
}
for (const eventRange of eventRanges) {
// Find all relevant events within the range.
const events: TypedEvent[] = await this.state.contracts[contractName]
.attach(eventRange.address)
.queryFilter(
this.state.contracts[contractName].filters[eventName](),
eventRange.fromBlock,
eventRange.toBlock
)
// Handle events, if any.
if (events.length > 0) {
const tick = Date.now()
for (const event of events) {
const extraData = await handlers.getExtraData(
event,
this.state.l1RpcProvider
)
const parsedEvent = await handlers.parseEvent(
event,
extraData,
this.options.l2ChainId
)
await handlers.storeEvent(parsedEvent, this.state.db)
}
const tock = Date.now()
this.logger.info('Processed events', {
eventName,
numEvents: events.length,
durationMs: tock - tick,
})
}
}
}
/**
* Gets the address of a contract at a particular block in the past.
*
* @param contractName Name of the contract to get an address for.
* @param blockNumber Block at which to get an address.
* @return Contract address.
*/
private async _getContractAddressAtBlock(
contractName: string,
blockNumber: number
): Promise<string> {
const events = await this.state.contracts.Lib_AddressManager.queryFilter(
this.state.contracts.Lib_AddressManager.filters.AddressSet(contractName),
this.state.startingL1BlockNumber,
blockNumber
)
if (events.length > 0) {
return events[events.length - 1].args._newAddress
} else {
// Address wasn't set before this.
return constants.AddressZero
}
}
/**
* Returns an address for a contract name whose address isn't expected to change.
*
* @param contractName Name of the contract to get an address for.
* @return Contract address.
*/
private async _getFixedAddress(contractName: string): Promise<string> {
if (this.state.addressCache[contractName]) {
return this.state.addressCache[contractName]
}
const address =
this.state.contracts.Lib_AddressManager.getAddress(contractName)
this.state.addressCache[contractName] = address
return address
}
private async _findStartingL1BlockNumber(): Promise<number> {
const currentL1Block = await this.state.l1RpcProvider.getBlockNumber()
const filter =
this.state.contracts.Lib_AddressManager.filters.OwnershipTransferred()
for (
let i = 0;
i < currentL1Block;
i += this.options.logsPerPollingInterval
) {
const start = i
const end = Math.min(
i + this.options.logsPerPollingInterval,
currentL1Block
)
this.logger.info(`Searching for ${filter} from ${start} to ${end}`)
const events = await this.state.contracts.Lib_AddressManager.queryFilter(
filter,
start,
end
)
if (events.length > 0) {
return events[0].blockNumber
}
}
throw new Error(`Unable to find appropriate L1 starting block number`)
}
}
export type EventName = 'SequencerTransaction'
export class MissingElementError extends Error {
constructor(public name: EventName) {
super(`missing event: ${name}`)
}
}
/* Imports: External */
import { BigNumber, ethers } from 'ethers'
import { serialize } from '@ethersproject/transactions'
import { padHexString } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { TransportDB } from '../../../db/transport-db'
import {
DecodedSequencerBatchTransaction,
StateRootEntry,
TransactionEntry,
} from '../../../types'
import { parseSignatureVParam } from '../../../utils'
import { MissingElementError } from './errors'
export const handleSequencerBlock = {
parseBlock: async (
block: any,
chainId: number
): Promise<{
transactionEntry: TransactionEntry
stateRootEntry: StateRootEntry
}> => {
const transaction = block.transactions[0]
const transactionIndex =
BigNumber.from(transaction.blockNumber).toNumber() - 1
// We make the assumption that you don't need to sync the genesis block
if (transactionIndex < 0) {
throw new Error('should not happen, attempted to sync genesis block')
}
let transactionEntry: Partial<TransactionEntry> = {
// Legacy support.
index: transactionIndex,
value: transaction.value,
batchIndex: null,
blockNumber: BigNumber.from(transaction.l1BlockNumber).toNumber(),
timestamp: BigNumber.from(transaction.l1Timestamp).toNumber(),
queueOrigin: transaction.queueOrigin,
confirmed: false,
}
if (transaction.queueOrigin === 'sequencer') {
const decodedTransaction: DecodedSequencerBatchTransaction = {
sig: {
v: parseSignatureVParam(transaction.v, chainId),
r: padHexString(transaction.r, 32),
s: padHexString(transaction.s, 32),
},
value: transaction.value,
gasLimit: BigNumber.from(transaction.gas).toString(),
gasPrice: BigNumber.from(transaction.gasPrice).toString(),
nonce: BigNumber.from(transaction.nonce).toString(),
target: transaction.to,
data: transaction.input,
}
transactionEntry = {
...transactionEntry,
gasLimit: BigNumber.from(0).toString(),
target: ethers.constants.AddressZero,
origin: null,
data: serialize(
{
value: transaction.value,
gasLimit: transaction.gas,
gasPrice: transaction.gasPrice,
nonce: transaction.nonce,
to: transaction.to,
data: transaction.input,
chainId,
},
{
v: BigNumber.from(transaction.v).toNumber(),
r: padHexString(transaction.r, 32),
s: padHexString(transaction.s, 32),
}
),
decoded: decodedTransaction,
queueIndex: null,
}
} else {
transactionEntry = {
...transactionEntry,
gasLimit: BigNumber.from(transaction.gas).toString(),
target: ethers.utils.getAddress(transaction.to),
origin: ethers.utils.getAddress(transaction.l1TxOrigin),
data: transaction.input,
decoded: null,
queueIndex:
transaction.queueIndex === null ||
transaction.queueIndex === undefined
? BigNumber.from(transaction.nonce).toNumber()
: BigNumber.from(transaction.queueIndex).toNumber(),
}
}
const stateRootEntry: StateRootEntry = {
index: transactionIndex,
batchIndex: null,
value: block.stateRoot,
confirmed: false,
}
return {
transactionEntry: transactionEntry as TransactionEntry, // Not the cleanest thing in the world. Could be improved.
stateRootEntry,
}
},
storeBlock: async (
entry: {
transactionEntry: TransactionEntry
stateRootEntry: StateRootEntry
},
db: TransportDB
): Promise<void> => {
if (entry.transactionEntry.index > 0) {
const prevTransactionEntry = await db.getUnconfirmedTransactionByIndex(
entry.transactionEntry.index - 1
)
// We should *always* have a previous transaction here.
if (prevTransactionEntry === null) {
throw new MissingElementError('SequencerTransaction')
}
}
// Having separate indices for confirmed/unconfirmed means we never have to worry about
// accidentally overwriting a confirmed transaction with an unconfirmed one. Unconfirmed
// transactions are purely extra information.
await db.putUnconfirmedTransactionEntries([entry.transactionEntry])
await db.putUnconfirmedStateRootEntries([entry.stateRootEntry])
},
}
/* Imports: External */
import { BaseService, LegacyMetrics } from '@eth-optimism/common-ts'
import { StaticJsonRpcProvider } from '@ethersproject/providers'
import { getChainId, sleep, toRpcHexString } from '@eth-optimism/core-utils'
import { BigNumber } from 'ethers'
import { LevelUp } from 'levelup'
import axios from 'axios'
import bfj from 'bfj'
import { Gauge, Histogram } from 'prom-client'
/* Imports: Internal */
import { handleSequencerBlock } from './handlers/transaction'
import { TransportDB } from '../../db/transport-db'
import { validators } from '../../utils'
import { L1DataTransportServiceOptions } from '../main/service'
interface L2IngestionMetrics {
highestSyncedL2Block: Gauge<string>
fetchBlocksRequestTime: Histogram<string>
}
const registerMetrics = ({
client,
registry,
}: LegacyMetrics): L2IngestionMetrics => ({
highestSyncedL2Block: new client.Gauge({
name: 'data_transport_layer_highest_synced_l2_block',
help: 'Highest Synced L2 Block Number',
registers: [registry],
}),
fetchBlocksRequestTime: new client.Histogram({
name: 'data_transport_layer_fetch_blocks_time',
help: 'Amount of time fetching remote L2 blocks takes',
buckets: [0.1, 5, 15, 50, 100, 500],
registers: [registry],
}),
})
export interface L2IngestionServiceOptions
extends L1DataTransportServiceOptions {
db: LevelUp
}
const optionSettings = {
db: {
validate: validators.isLevelUP,
},
l2RpcProvider: {
validate: (val: any) => {
return validators.isUrl(val) || validators.isJsonRpcProvider(val)
},
},
l2ChainId: {
validate: validators.isInteger,
},
pollingInterval: {
default: 5000,
validate: validators.isInteger,
},
transactionsPerPollingInterval: {
default: 1000,
validate: validators.isInteger,
},
dangerouslyCatchAllErrors: {
default: false,
validate: validators.isBoolean,
},
legacySequencerCompatibility: {
default: false,
validate: validators.isBoolean,
},
}
export class L2IngestionService extends BaseService<L2IngestionServiceOptions> {
constructor(options: L2IngestionServiceOptions) {
super('L2_Ingestion_Service', options, optionSettings)
}
private l2IngestionMetrics: L2IngestionMetrics
private state: {
db: TransportDB
l2RpcProvider: StaticJsonRpcProvider
} = {} as any
protected async _init(): Promise<void> {
if (this.options.legacySequencerCompatibility) {
this.logger.info(
'Using legacy sync, this will be quite a bit slower than normal'
)
}
this.l2IngestionMetrics = registerMetrics(this.metrics)
this.state.db = new TransportDB(this.options.db, {
l2ChainId: this.options.l2ChainId,
})
this.state.l2RpcProvider =
typeof this.options.l2RpcProvider === 'string'
? new StaticJsonRpcProvider({
url: this.options.l2RpcProvider,
user: this.options.l2RpcProviderUser,
password: this.options.l2RpcProviderPassword,
headers: { 'User-Agent': 'data-transport-layer' },
})
: this.options.l2RpcProvider
}
protected async ensure(): Promise<void> {
let retries = 0
while (true) {
try {
await this.state.l2RpcProvider.getNetwork()
break
} catch (e) {
retries++
this.logger.info(`Cannot connect to L2, retrying ${retries}/20`)
if (retries >= 20) {
this.logger.info('Cannot connect to L2, shutting down')
await this.stop()
process.exit()
}
await sleep(1000 * retries)
}
}
}
protected async checkConsistency(): Promise<void> {
const chainId = await getChainId(this.state.l2RpcProvider)
const shouldDoCheck = !(await this.state.db.getConsistencyCheckFlag())
if (shouldDoCheck && chainId === 69) {
this.logger.info('performing consistency check')
const highestBlock =
await this.state.db.getHighestSyncedUnconfirmedBlock()
for (let i = 0; i < highestBlock; i++) {
const block = await this.state.db.getUnconfirmedTransactionByIndex(i)
if (block === null) {
this.logger.info('resetting to null block', {
index: i,
})
await this.state.db.setHighestSyncedUnconfirmedBlock(i)
break
}
// Log some progress so people know what's goin on.
if (i % 10000 === 0) {
this.logger.info(`consistency check progress`, {
index: i,
})
}
}
this.logger.info('consistency check complete')
await this.state.db.putConsistencyCheckFlag(true)
}
}
protected async _start(): Promise<void> {
await this.ensure()
await this.checkConsistency()
while (this.running) {
try {
const highestSyncedL2BlockNumber =
(await this.state.db.getHighestSyncedUnconfirmedBlock()) || 1
const currentL2Block = await this.state.l2RpcProvider.getBlockNumber()
// Make sure we don't exceed the tip.
const targetL2Block = Math.min(
highestSyncedL2BlockNumber +
this.options.transactionsPerPollingInterval,
currentL2Block
)
// We're already at the head, so no point in attempting to sync.
// Also wait on edge case of no L2 transactions
if (
highestSyncedL2BlockNumber === targetL2Block ||
currentL2Block === 0
) {
this.logger.info(
'All Layer 2 (Optimism) transactions are synchronized',
{
currentL2Block,
targetL2Block,
}
)
await sleep(this.options.pollingInterval)
continue
}
this.logger.info(
'Synchronizing unconfirmed transactions from Layer 2 (Optimism)',
{
fromBlock: highestSyncedL2BlockNumber,
toBlock: targetL2Block,
}
)
// Synchronize by requesting blocks from the sequencer. Sync from L1 takes precedence.
await this._syncSequencerBlocks(
highestSyncedL2BlockNumber,
targetL2Block
)
await this.state.db.setHighestSyncedUnconfirmedBlock(targetL2Block)
this.l2IngestionMetrics.highestSyncedL2Block.set(targetL2Block)
if (
currentL2Block - highestSyncedL2BlockNumber <
this.options.transactionsPerPollingInterval
) {
await sleep(this.options.pollingInterval)
}
} catch (err) {
if (!this.running || this.options.dangerouslyCatchAllErrors) {
this.logger.error('Caught an unhandled error', {
message: err.toString(),
stack: err.stack,
code: err.code,
})
await sleep(this.options.pollingInterval)
} else {
throw err
}
}
}
}
/**
* Synchronizes unconfirmed transactions from a range of sequencer blocks.
*
* @param startBlockNumber Block to start querying from.
* @param endBlockNumber Block to query to.
*/
private async _syncSequencerBlocks(
startBlockNumber: number,
endBlockNumber: number
): Promise<void> {
if (startBlockNumber > endBlockNumber) {
this.logger.warn(
'Cannot query with start block number larger than end block number',
{
startBlockNumber,
endBlockNumber,
}
)
return
}
let blocks: any = []
if (this.options.legacySequencerCompatibility) {
const blockPromises = []
for (let i = startBlockNumber; i <= endBlockNumber; i++) {
blockPromises.push(
this.state.l2RpcProvider.send('eth_getBlockByNumber', [
toRpcHexString(i),
true,
])
)
}
// Just making sure that the blocks will come back in increasing order.
blocks = (await Promise.all(blockPromises)).sort((a, b) => {
return (
BigNumber.from(a.number).toNumber() -
BigNumber.from(b.number).toNumber()
)
})
} else {
// This request returns a large response. Parsing it into JSON inside the ethers library is
// quite slow, and can block the event loop for upwards of multiple seconds. When this happens,
// incoming http requests will likely timeout and fail.
// Instead, we will parse the incoming http stream directly with the bfj package, which yields
// the event loop periodically so that we don't fail to serve requests.
const req = {
jsonrpc: '2.0',
method: 'eth_getBlockRange',
params: [
toRpcHexString(startBlockNumber),
toRpcHexString(endBlockNumber),
true,
],
id: '1',
}
// Retry the `eth_getBlockRange` query in case the endBlockNumber
// is greater than the tip and `null` is returned. This gives time
// for the sync to catch up
let result = null
let retry = 0
while (result === null) {
if (retry === 6) {
throw new Error(
`unable to fetch block range [${startBlockNumber},${endBlockNumber})`
)
}
const end = this.l2IngestionMetrics.fetchBlocksRequestTime.startTimer()
const resp = await axios.post(
this.state.l2RpcProvider.connection.url,
req,
{ responseType: 'stream' }
)
const respJson = await bfj.parse(resp.data, {
yieldRate: 4096, // this yields abit more often than the default of 16384
})
end()
result = respJson.result
if (result === null) {
retry++
this.logger.info(
`request for block range [${startBlockNumber},${endBlockNumber}) returned null, retry ${retry}`
)
await sleep(1000 * retry)
}
}
blocks = result
}
for (const block of blocks) {
const entry = await handleSequencerBlock.parseBlock(
block,
this.options.l2ChainId
)
await handleSequencerBlock.storeBlock(entry, this.state.db)
}
}
}
/* Imports: External */
import { BaseService, LegacyMetrics } from '@eth-optimism/common-ts'
import { LevelUp } from 'levelup'
import level from 'level6'
import { Counter } from 'prom-client'
/* Imports: Internal */
import { L1IngestionService } from '../l1-ingestion/service'
import { L1TransportServer } from '../server/service'
import { validators } from '../../utils'
import { L2IngestionService } from '../l2-ingestion/service'
import { BSS_HF1_INDEX } from '../../config'
export interface L1DataTransportServiceOptions {
nodeEnv: string
ethNetworkName?: 'mainnet' | 'kovan' | 'goerli'
release: string
addressManager: string
confirmations: number
dangerouslyCatchAllErrors?: boolean
hostname: string
l1RpcProvider: string
l1RpcProviderUser?: string
l1RpcProviderPassword?: string
l2ChainId: number
l2RpcProvider: string
l2RpcProviderUser?: string
l2RpcProviderPassword?: string
l1SyncShutoffBlock?: number
metrics?: LegacyMetrics
dbPath: string
logsPerPollingInterval: number
pollingInterval: number
port: number
syncFromL1?: boolean
syncFromL2?: boolean
transactionsPerPollingInterval: number
legacySequencerCompatibility: boolean
useSentry?: boolean
sentryDsn?: string
sentryTraceRate?: number
defaultBackend: string
l1GasPriceBackend: string
l1StartHeight?: number
}
const optionSettings = {
syncFromL1: {
default: true,
validate: validators.isBoolean,
},
syncFromL2: {
default: false,
validate: validators.isBoolean,
},
}
// prettier-ignore
export class L1DataTransportService extends BaseService<L1DataTransportServiceOptions> {
constructor(options: L1DataTransportServiceOptions) {
super('L1_Data_Transport_Service', options, optionSettings)
}
private state: {
db: LevelUp
l1IngestionService?: L1IngestionService
l2IngestionService?: L2IngestionService
l1TransportServer: L1TransportServer
metrics: LegacyMetrics
failureCounter: Counter<string>
} = {} as any
protected async _init(): Promise<void> {
this.logger.info('Initializing L1 Data Transport Service...')
this.state.db = level(this.options.dbPath)
await this.state.db.open()
// BSS HF1 activates at block 0 if not specified.
const bssHf1Index = BSS_HF1_INDEX[this.options.l2ChainId] || 0
this.logger.info(`L2 chain ID is: ${this.options.l2ChainId}`)
this.logger.info(`BSS HF1 will activate at: ${bssHf1Index}`)
this.state.metrics = new LegacyMetrics({
labels: {
environment: this.options.nodeEnv,
network: this.options.ethNetworkName,
release: this.options.release,
service: this.name,
}
})
this.state.metrics.client.collectDefaultMetrics({
prefix: 'data_transport_layer_'
})
this.state.failureCounter = new this.state.metrics.client.Counter({
name: 'data_transport_layer_main_service_failures',
help: 'Counts the number of times that the main service fails',
registers: [this.state.metrics.registry],
})
this.state.l1TransportServer = new L1TransportServer({
...this.options,
metrics: this.state.metrics,
db: this.state.db,
})
// Optionally enable sync from L1.
if (this.options.syncFromL1) {
this.state.l1IngestionService = new L1IngestionService({
...this.options,
metrics: this.state.metrics,
db: this.state.db,
})
}
// Optionally enable sync from L2.
if (this.options.syncFromL2) {
this.state.l2IngestionService = new L2IngestionService({
...(this.options as any), // TODO: Correct thing to do here is to assert this type.
metrics: this.state.metrics,
db: this.state.db,
})
}
await this.state.l1TransportServer.init()
if (this.options.syncFromL1) {
await this.state.l1IngestionService.init()
}
if (this.options.syncFromL2) {
await this.state.l2IngestionService.init()
}
}
protected async _start(): Promise<void> {
try {
await Promise.all([
this.state.l1TransportServer.start(),
this.options.syncFromL1 ? this.state.l1IngestionService.start() : null,
this.options.syncFromL2 ? this.state.l2IngestionService.start() : null,
])
} catch (e) {
this.state.failureCounter.inc()
throw e
}
}
protected async _stop(): Promise<void> {
try {
await Promise.all([
this.state.l1TransportServer.stop(),
this.options.syncFromL1 ? this.state.l1IngestionService.stop() : null,
this.options.syncFromL2 ? this.state.l2IngestionService.stop() : null,
])
await this.state.db.close()
} catch (e) {
this.state.failureCounter.inc()
throw e
}
}
}
/* Imports: External */
import * as dotenv from 'dotenv'
import { Bcfg } from '@eth-optimism/core-utils'
import Config from 'bcfg'
/* Imports: Internal */
import { L1DataTransportService } from './main/service'
type ethNetwork = 'mainnet' | 'kovan' | 'goerli'
;(async () => {
try {
dotenv.config()
const config: Bcfg = new Config('data-transport-layer')
config.load({
env: true,
argv: true,
})
const service = new L1DataTransportService({
nodeEnv: config.str('node-env', 'development'),
ethNetworkName: config.str('eth-network-name') as ethNetwork,
release: `data-transport-layer@${process.env.npm_package_version}`,
dbPath: config.str('db-path', './db'),
port: config.uint('server-port', 7878),
hostname: config.str('server-hostname', 'localhost'),
confirmations: config.uint('confirmations', 35),
l1RpcProvider: config.str('l1-rpc-endpoint'),
l1RpcProviderUser: config.str('l1-rpc-user'),
l1RpcProviderPassword: config.str('l1-rpc-password'),
addressManager: config.str('address-manager'),
l1SyncShutoffBlock: config.uint('l1-sync-shutoff-block'),
pollingInterval: config.uint('polling-interval', 5000),
logsPerPollingInterval: config.uint('logs-per-polling-interval', 2000),
dangerouslyCatchAllErrors: config.bool(
'dangerously-catch-all-errors',
false
),
l2RpcProvider: config.str('l2-rpc-endpoint'),
l2RpcProviderUser: config.str('l2-rpc-user'),
l2RpcProviderPassword: config.str('l2-rpc-password'),
l2ChainId: config.uint('l2-chain-id'),
syncFromL1: config.bool('sync-from-l1', true),
syncFromL2: config.bool('sync-from-l2', false),
transactionsPerPollingInterval: config.uint(
'transactions-per-polling-interval',
1000
),
legacySequencerCompatibility: config.bool(
'legacy-sequencer-compatibility',
false
),
defaultBackend: config.str('default-backend', 'l1'),
l1GasPriceBackend: config.str('l1-gas-price-backend', 'l1'),
l1StartHeight: config.uint('l1-start-height'),
useSentry: config.bool('use-sentry', false),
sentryDsn: config.str('sentry-dsn'),
sentryTraceRate: config.ufloat('sentry-trace-rate', 0.05),
})
const stop = async (signal) => {
console.log(`"{"msg": "${signal} - Stopping data-transport layer"}"`)
await service.stop()
process.exit()
}
process.on('SIGTERM', stop)
process.on('SIGINT', stop)
await service.start()
} catch (err) {
console.error(
`Well, that's that. We ran into a fatal error. Here's the dump. Goodbye!`
)
throw err
}
})()
/* Imports: External */
import { BaseService, Logger, LegacyMetrics } from '@eth-optimism/common-ts'
import express, { Request, Response } from 'express'
import promBundle from 'express-prom-bundle'
import cors from 'cors'
import { BigNumber } from 'ethers'
import { JsonRpcProvider } from '@ethersproject/providers'
import { LevelUp } from 'levelup'
import * as Sentry from '@sentry/node'
import * as Tracing from '@sentry/tracing'
/* Imports: Internal */
import { TransportDB } from '../../db/transport-db'
import {
ContextResponse,
GasPriceResponse,
EnqueueResponse,
StateRootBatchResponse,
StateRootResponse,
SyncingResponse,
TransactionBatchResponse,
TransactionResponse,
} from '../../types'
import { validators } from '../../utils'
import { L1DataTransportServiceOptions } from '../main/service'
export interface L1TransportServerOptions
extends L1DataTransportServiceOptions {
db: LevelUp
metrics: LegacyMetrics
}
const optionSettings = {
db: {
validate: validators.isLevelUP,
},
port: {
default: 7878,
validate: validators.isInteger,
},
hostname: {
default: 'localhost',
validate: validators.isString,
},
confirmations: {
validate: validators.isInteger,
},
l1RpcProvider: {
validate: (val: any) => {
return validators.isUrl(val) || validators.isJsonRpcProvider(val)
},
},
l2RpcProvider: {
validate: (val: unknown) => {
return validators.isUrl(val) || validators.isJsonRpcProvider(val)
},
},
defaultBackend: {
default: 'l1',
validate: (val: string) => {
return val === 'l1' || val === 'l2'
},
},
l1GasPriceBackend: {
default: 'l1',
validate: (val: string) => {
return val === 'l1' || val === 'l2'
},
},
}
export class L1TransportServer extends BaseService<L1TransportServerOptions> {
constructor(options: L1TransportServerOptions) {
super('L1_Transport_Server', options, optionSettings)
}
private state: {
app: express.Express
server: any
db: TransportDB
l1RpcProvider: JsonRpcProvider
l2RpcProvider: JsonRpcProvider
} = {} as any
protected async _init(): Promise<void> {
if (!this.options.db.isOpen()) {
await this.options.db.open()
}
this.state.db = new TransportDB(this.options.db, {
l2ChainId: this.options.l2ChainId,
})
this.state.l1RpcProvider =
typeof this.options.l1RpcProvider === 'string'
? new JsonRpcProvider({
url: this.options.l1RpcProvider,
user: this.options.l1RpcProviderUser,
password: this.options.l1RpcProviderPassword,
headers: { 'User-Agent': 'data-transport-layer' },
})
: this.options.l1RpcProvider
this.state.l2RpcProvider =
typeof this.options.l2RpcProvider === 'string'
? new JsonRpcProvider({
url: this.options.l2RpcProvider,
user: this.options.l2RpcProviderUser,
password: this.options.l2RpcProviderPassword,
headers: { 'User-Agent': 'data-transport-layer' },
})
: this.options.l2RpcProvider
this._initializeApp()
}
protected async _start(): Promise<void> {
this.state.server = this.state.app.listen(
this.options.port,
this.options.hostname
)
this.logger.info('Server started and listening', {
host: this.options.hostname,
port: this.options.port,
})
}
protected async _stop(): Promise<void> {
this.state.server.close()
}
/**
* Initializes the server application.
* Do any sort of initialization here that you want. Mostly just important that
* `_registerAllRoutes` is called at the end.
*/
private _initializeApp() {
// TODO: Maybe pass this in as a parameter instead of creating it here?
this.state.app = express()
if (this.options.useSentry) {
this._initSentry()
}
this.state.app.use(cors())
// Add prometheus middleware to express BEFORE route registering
this.state.app.use(
// This also serves metrics on port 3000 at /metrics
promBundle({
// Provide metrics registry that other metrics uses
promRegistry: this.metrics.registry,
includeMethod: true,
includePath: true,
})
)
this._registerAllRoutes()
// Sentry error handling must be after all controllers
// and before other error middleware
if (this.options.useSentry) {
this.state.app.use(Sentry.Handlers.errorHandler())
}
this.logger.info('HTTP Server Options', {
defaultBackend: this.options.defaultBackend,
l1GasPriceBackend: this.options.l1GasPriceBackend,
})
if (this.state.l1RpcProvider) {
this.logger.info('HTTP Server L1 RPC Provider initialized', {
url: this.state.l1RpcProvider.connection.url,
})
} else {
this.logger.warn('HTTP Server L1 RPC Provider not initialized')
}
if (this.state.l2RpcProvider) {
this.logger.info('HTTP Server L2 RPC Provider initialized', {
url: this.state.l2RpcProvider.connection.url,
})
} else {
this.logger.warn('HTTP Server L2 RPC Provider not initialized')
}
}
/**
* Initialize Sentry and related middleware
*/
private _initSentry() {
const sentryOptions = {
dsn: this.options.sentryDsn,
release: this.options.release,
environment: this.options.ethNetworkName,
}
this.logger = new Logger({
name: this.name,
sentryOptions,
})
Sentry.init({
...sentryOptions,
integrations: [
new Sentry.Integrations.Http({ tracing: true }),
new Tracing.Integrations.Express({
app: this.state.app,
}),
],
tracesSampleRate: this.options.sentryTraceRate,
})
this.state.app.use(Sentry.Handlers.requestHandler())
this.state.app.use(Sentry.Handlers.tracingHandler())
}
/**
* Registers a route on the server.
*
* @param method Http method type.
* @param route Route to register.
* @param handler Handler called and is expected to return a JSON response.
*/
private _registerRoute(
method: 'get', // Just handle GET for now, but could extend this with whatever.
route: string,
handler: (req?: Request, res?: Response) => Promise<any>
): void {
// TODO: Better typing on the return value of the handler function.
// TODO: Check for route collisions.
// TODO: Add a different function to allow for removing routes.
this.state.app[method](route, async (req, res) => {
const start = Date.now()
try {
const json = await handler(req, res)
const elapsed = Date.now() - start
this.logger.info('Served HTTP Request', {
method: req.method,
url: req.url,
elapsed,
})
this.logger.debug('Response body', {
method: req.method,
url: req.url,
body: json,
})
return res.json(json)
} catch (e) {
const elapsed = Date.now() - start
this.logger.error('Failed HTTP Request', {
method: req.method,
url: req.url,
elapsed,
msg: e.toString(),
stack: e.stack,
})
return res.status(400).json({
error: e.toString(),
})
}
})
}
/**
* Registers all of the server routes we want to expose.
* TODO: Link to our API spec.
*/
private _registerAllRoutes(): void {
this._registerRoute(
'get',
'/eth/syncing',
async (req): Promise<SyncingResponse> => {
const backend = req.query.backend || this.options.defaultBackend
let currentL2Block
let highestL2BlockNumber
switch (backend) {
case 'l1':
currentL2Block = await this.state.db.getLatestTransaction()
highestL2BlockNumber = await this.state.db.getHighestL2BlockNumber()
break
case 'l2':
currentL2Block =
await this.state.db.getLatestUnconfirmedTransaction()
highestL2BlockNumber =
(await this.state.db.getHighestSyncedUnconfirmedBlock()) - 1
break
default:
throw new Error(`Unknown transaction backend ${backend}`)
}
if (currentL2Block === null) {
if (highestL2BlockNumber === null) {
return {
syncing: false,
currentTransactionIndex: 0,
}
} else {
return {
syncing: true,
highestKnownTransactionIndex: highestL2BlockNumber,
currentTransactionIndex: 0,
}
}
}
if (highestL2BlockNumber > currentL2Block.index) {
return {
syncing: true,
highestKnownTransactionIndex: highestL2BlockNumber,
currentTransactionIndex: currentL2Block.index,
}
} else {
return {
syncing: false,
currentTransactionIndex: currentL2Block.index,
}
}
}
)
this._registerRoute(
'get',
'/eth/gasprice',
async (req): Promise<GasPriceResponse> => {
const backend = req.query.backend || this.options.l1GasPriceBackend
let gasPrice: BigNumber
if (backend === 'l1') {
gasPrice = await this.state.l1RpcProvider.getGasPrice()
} else if (backend === 'l2') {
const response = await this.state.l2RpcProvider.send(
'rollup_gasPrices',
[]
)
gasPrice = BigNumber.from(response.l1GasPrice)
} else {
throw new Error(`Unknown L1 gas price backend: ${backend}`)
}
return {
gasPrice: gasPrice.toString(),
}
}
)
this._registerRoute(
'get',
'/eth/context/latest',
async (): Promise<ContextResponse> => {
const tip = await this.state.l1RpcProvider.getBlockNumber()
const blockNumber = Math.max(0, tip - this.options.confirmations)
const block = await this.state.l1RpcProvider.getBlock(blockNumber)
if (block === null) {
throw new Error(`Cannot GET /eth/context/latest at ${blockNumber}`)
}
return {
blockNumber: block.number,
timestamp: block.timestamp,
blockHash: block.hash,
}
}
)
this._registerRoute(
'get',
'/eth/context/blocknumber/:number',
async (req): Promise<ContextResponse> => {
const number = BigNumber.from(req.params.number).toNumber()
const tip = await this.state.l1RpcProvider.getBlockNumber()
const blockNumber = Math.max(0, tip - this.options.confirmations)
if (number > blockNumber) {
return {
blockNumber: null,
timestamp: null,
blockHash: null,
}
}
const block = await this.state.l1RpcProvider.getBlock(number)
if (block === null) {
throw new Error(`Cannot GET /eth/context/blocknumber/${number}`)
}
return {
blockNumber: block.number,
timestamp: block.timestamp,
blockHash: block.hash,
}
}
)
this._registerRoute(
'get',
'/enqueue/latest',
async (): Promise<EnqueueResponse> => {
const enqueue = await this.state.db.getLatestEnqueue()
if (enqueue === null) {
return {
index: null,
target: null,
data: null,
gasLimit: null,
origin: null,
blockNumber: null,
timestamp: null,
ctcIndex: null,
}
}
const ctcIndex = await this.state.db.getTransactionIndexByQueueIndex(
enqueue.index
)
return {
...enqueue,
ctcIndex,
}
}
)
this._registerRoute(
'get',
'/enqueue/index/:index',
async (req): Promise<EnqueueResponse> => {
const enqueue = await this.state.db.getEnqueueByIndex(
BigNumber.from(req.params.index).toNumber()
)
if (enqueue === null) {
return {
index: null,
target: null,
data: null,
gasLimit: null,
origin: null,
blockNumber: null,
timestamp: null,
ctcIndex: null,
}
}
const ctcIndex = await this.state.db.getTransactionIndexByQueueIndex(
enqueue.index
)
return {
...enqueue,
ctcIndex,
}
}
)
this._registerRoute(
'get',
'/transaction/latest',
async (req): Promise<TransactionResponse> => {
const backend = req.query.backend || this.options.defaultBackend
let transaction = null
switch (backend) {
case 'l1':
transaction = await this.state.db.getLatestFullTransaction()
break
case 'l2':
transaction = await this.state.db.getLatestUnconfirmedTransaction()
break
default:
throw new Error(`Unknown transaction backend ${backend}`)
}
if (transaction === null) {
return {
transaction: null,
batch: null,
}
}
const batch = await this.state.db.getTransactionBatchByIndex(
transaction.batchIndex
)
return {
transaction,
batch,
}
}
)
this._registerRoute(
'get',
'/transaction/index/:index',
async (req): Promise<TransactionResponse> => {
const backend = req.query.backend || this.options.defaultBackend
let transaction = null
switch (backend) {
case 'l1':
transaction = await this.state.db.getFullTransactionByIndex(
BigNumber.from(req.params.index).toNumber()
)
break
case 'l2':
transaction = await this.state.db.getUnconfirmedTransactionByIndex(
BigNumber.from(req.params.index).toNumber()
)
break
default:
throw new Error(`Unknown transaction backend ${backend}`)
}
if (transaction === null) {
return {
transaction: null,
batch: null,
}
}
const batch = await this.state.db.getTransactionBatchByIndex(
transaction.batchIndex
)
return {
transaction,
batch,
}
}
)
this._registerRoute(
'get',
'/batch/transaction/latest',
async (): Promise<TransactionBatchResponse> => {
const batch = await this.state.db.getLatestTransactionBatch()
if (batch === null) {
return {
batch: null,
transactions: [],
}
}
const transactions =
await this.state.db.getFullTransactionsByIndexRange(
BigNumber.from(batch.prevTotalElements).toNumber(),
BigNumber.from(batch.prevTotalElements).toNumber() +
BigNumber.from(batch.size).toNumber()
)
return {
batch,
transactions,
}
}
)
this._registerRoute(
'get',
'/batch/transaction/index/:index',
async (req): Promise<TransactionBatchResponse> => {
const batch = await this.state.db.getTransactionBatchByIndex(
BigNumber.from(req.params.index).toNumber()
)
if (batch === null) {
return {
batch: null,
transactions: [],
}
}
const transactions =
await this.state.db.getFullTransactionsByIndexRange(
BigNumber.from(batch.prevTotalElements).toNumber(),
BigNumber.from(batch.prevTotalElements).toNumber() +
BigNumber.from(batch.size).toNumber()
)
return {
batch,
transactions,
}
}
)
this._registerRoute(
'get',
'/stateroot/latest',
async (req): Promise<StateRootResponse> => {
const backend = req.query.backend || this.options.defaultBackend
let stateRoot = null
switch (backend) {
case 'l1':
stateRoot = await this.state.db.getLatestStateRoot()
break
case 'l2':
stateRoot = await this.state.db.getLatestUnconfirmedStateRoot()
break
default:
throw new Error(`Unknown transaction backend ${backend}`)
}
if (stateRoot === null) {
return {
stateRoot: null,
batch: null,
}
}
const batch = await this.state.db.getStateRootBatchByIndex(
stateRoot.batchIndex
)
return {
stateRoot,
batch,
}
}
)
this._registerRoute(
'get',
'/stateroot/index/:index',
async (req): Promise<StateRootResponse> => {
const backend = req.query.backend || this.options.defaultBackend
let stateRoot = null
switch (backend) {
case 'l1':
stateRoot = await this.state.db.getStateRootByIndex(
BigNumber.from(req.params.index).toNumber()
)
break
case 'l2':
stateRoot = await this.state.db.getUnconfirmedStateRootByIndex(
BigNumber.from(req.params.index).toNumber()
)
break
default:
throw new Error(`Unknown transaction backend ${backend}`)
}
if (stateRoot === null) {
return {
stateRoot: null,
batch: null,
}
}
const batch = await this.state.db.getStateRootBatchByIndex(
stateRoot.batchIndex
)
return {
stateRoot,
batch,
}
}
)
this._registerRoute(
'get',
'/batch/stateroot/latest',
async (): Promise<StateRootBatchResponse> => {
const batch = await this.state.db.getLatestStateRootBatch()
if (batch === null) {
return {
batch: null,
stateRoots: [],
}
}
const stateRoots = await this.state.db.getStateRootsByIndexRange(
BigNumber.from(batch.prevTotalElements).toNumber(),
BigNumber.from(batch.prevTotalElements).toNumber() +
BigNumber.from(batch.size).toNumber()
)
return {
batch,
stateRoots,
}
}
)
this._registerRoute(
'get',
'/batch/stateroot/index/:index',
async (req): Promise<StateRootBatchResponse> => {
const batch = await this.state.db.getStateRootBatchByIndex(
BigNumber.from(req.params.index).toNumber()
)
if (batch === null) {
return {
batch: null,
stateRoots: [],
}
}
const stateRoots = await this.state.db.getStateRootsByIndexRange(
BigNumber.from(batch.prevTotalElements).toNumber(),
BigNumber.from(batch.prevTotalElements).toNumber() +
BigNumber.from(batch.size).toNumber()
)
return {
batch,
stateRoots,
}
}
)
}
}
import {
EnqueueEntry,
StateRootBatchEntry,
StateRootEntry,
TransactionBatchEntry,
TransactionEntry,
} from './database-types'
export type EnqueueResponse = EnqueueEntry & {
ctcIndex: number | null
}
export interface TransactionResponse {
batch: TransactionBatchEntry
transaction: TransactionEntry
}
export interface TransactionBatchResponse {
batch: TransactionBatchEntry
transactions: TransactionEntry[]
}
export interface StateRootResponse {
batch: StateRootBatchEntry
stateRoot: StateRootEntry
}
export interface StateRootBatchResponse {
batch: StateRootBatchEntry
stateRoots: StateRootEntry[]
}
export interface ContextResponse {
blockNumber: number
timestamp: number
blockHash: string
}
export interface GasPriceResponse {
gasPrice: string
}
export type SyncingResponse =
| {
syncing: true
highestKnownTransactionIndex: number
currentTransactionIndex: number
}
| {
syncing: false
currentTransactionIndex: number
}
export interface DecodedSequencerBatchTransaction {
sig: {
r: string
s: string
v: number
}
value: string
gasLimit: string
gasPrice: string
nonce: string
target: string
data: string
}
export interface EnqueueEntry {
index: number
target: string
data: string
gasLimit: string
origin: string
blockNumber: number
timestamp: number
}
export interface TransactionEntry {
index: number
batchIndex: number
data: string
blockNumber: number
timestamp: number
gasLimit: string
target: string
origin: string
value: string
queueOrigin: 'sequencer' | 'l1'
queueIndex: number | null
decoded: DecodedSequencerBatchTransaction | null
confirmed: boolean
}
interface BatchEntry {
index: number
blockNumber: number
timestamp: number
submitter: string
size: number
root: string
prevTotalElements: number
extraData: string
l1TransactionHash: string
type: string
}
export type TransactionBatchEntry = BatchEntry
export type StateRootBatchEntry = BatchEntry
export interface StateRootEntry {
index: number
batchIndex: number
value: string
confirmed: boolean
}
import { BaseProvider } from '@ethersproject/providers'
import { BigNumber } from 'ethers'
import { TypedEvent } from '@eth-optimism/contracts/dist/types/common'
import {
TransactionBatchEntry,
TransactionEntry,
StateRootBatchEntry,
StateRootEntry,
} from './database-types'
import { TransportDB } from '../db/transport-db'
export type GetExtraDataHandler<TEvent extends TypedEvent, TExtraData> = (
event?: TEvent,
l1RpcProvider?: BaseProvider
) => Promise<TExtraData>
export type ParseEventHandler<
TEvent extends TypedEvent,
TExtraData,
TParsedEvent
> = (event: TEvent, extraData: TExtraData, l2ChainId: number) => TParsedEvent
export type StoreEventHandler<TParsedEvent> = (
parsedEvent: TParsedEvent,
db: TransportDB
) => Promise<void>
export interface EventHandlerSet<
TEvent extends TypedEvent,
TExtraData,
TParsedEvent
> {
getExtraData: GetExtraDataHandler<TEvent, TExtraData>
parseEvent: ParseEventHandler<TEvent, TExtraData, TParsedEvent>
storeEvent: StoreEventHandler<TParsedEvent>
}
export interface SequencerBatchAppendedExtraData {
timestamp: number
blockNumber: number
submitter: string
l1TransactionData: string
l1TransactionHash: string
// Stuff from TransactionBatchAppended.
prevTotalElements: BigNumber
batchIndex: BigNumber
batchSize: BigNumber
batchRoot: string
batchExtraData: string
}
export interface SequencerBatchAppendedParsedEvent {
transactionBatchEntry: TransactionBatchEntry
transactionEntries: TransactionEntry[]
}
export interface StateBatchAppendedExtraData {
timestamp: number
blockNumber: number
submitter: string
l1TransactionHash: string
l1TransactionData: string
}
export interface StateBatchAppendedParsedEvent {
stateRootBatchEntry: StateRootBatchEntry
stateRootEntries: StateRootEntry[]
}
export * from './api-types'
export * from './database-types'
export * from './event-handler-types'
/* Imports: External */
import { constants, Contract, Signer } from 'ethers'
import { BaseProvider } from '@ethersproject/providers'
import { getContractInterface } from '@eth-optimism/contracts'
export const loadContract = (
name: string,
address: string,
provider: BaseProvider
): Contract => {
return new Contract(address, getContractInterface(name) as any, provider)
}
export const loadProxyFromManager = async (
name: string,
proxy: string,
Lib_AddressManager: Contract,
provider: BaseProvider
): Promise<Contract> => {
const address = await Lib_AddressManager.getAddress(proxy)
if (address === constants.AddressZero) {
throw new Error(
`Lib_AddressManager does not have a record for a contract named: ${proxy}`
)
}
return loadContract(name, address, provider)
}
export interface OptimismContracts {
Lib_AddressManager: Contract
StateCommitmentChain: Contract
CanonicalTransactionChain: Contract
}
export const loadOptimismContracts = async (
l1RpcProvider: BaseProvider,
addressManagerAddress: string,
signer?: Signer
): Promise<OptimismContracts> => {
const Lib_AddressManager = loadContract(
'Lib_AddressManager',
addressManagerAddress,
l1RpcProvider
)
const inputs = [
{
name: 'StateCommitmentChain',
interface: 'IStateCommitmentChain',
},
{
name: 'CanonicalTransactionChain',
interface: 'ICanonicalTransactionChain',
},
]
const contracts = {}
for (const input of inputs) {
contracts[input.name] = await loadProxyFromManager(
input.interface,
input.name,
Lib_AddressManager,
l1RpcProvider
)
if (signer) {
contracts[input.name] = contracts[input.name].connect(signer)
}
}
contracts['Lib_AddressManager'] = Lib_AddressManager
// TODO: sorry
return contracts as OptimismContracts
}
/* Imports: External */
import { ethers } from 'ethers'
export const parseSignatureVParam = (
v: number | ethers.BigNumber | string,
chainId: number
): number => {
v = ethers.BigNumber.from(v).toNumber()
// Handle unprotected transactions
if (v === 27 || v === 28) {
return v
}
// Handle EIP155 transactions
return v - 2 * chainId - 35
}
export * from './contracts'
export * from './validation'
export * from './eth-tx'
import * as url from 'url'
import { fromHexString } from '@eth-optimism/core-utils'
export const validators = {
isBoolean: (val: any): boolean => {
return typeof val === 'boolean'
},
isString: (val: any): boolean => {
return typeof val === 'string'
},
isHexString: (val: any): boolean => {
return (
validators.isString(val) &&
val.startsWith('0x') &&
fromHexString(val).length === (val.length - 2) / 2
)
},
isAddress: (val: any): boolean => {
return validators.isHexString(val) && val.length === 42
},
isInteger: (val: any): boolean => {
return Number.isInteger(val)
},
isUrl: (val: any): boolean => {
try {
const parsed = new url.URL(val)
return (
parsed.protocol === 'ws:' ||
parsed.protocol === 'http:' ||
parsed.protocol === 'https:'
)
} catch (err) {
return false
}
},
isJsonRpcProvider: (val: any): boolean => {
return val && val.ready !== undefined
},
isLevelUP: (val: any): boolean => {
// TODO: Fix?
return val && val.db
},
}
import { HardhatUserConfig } from 'hardhat/config'
const config: HardhatUserConfig = {
// All paths relative to ** this file **.
paths: {
tests: '../../test',
cache: '../temp/cache',
artifacts: '../temp/artifacts',
},
}
export default config
import fs from 'fs'
import path from 'path'
import chai = require('chai')
import Mocha from 'mocha'
import chaiAsPromised from 'chai-as-promised'
import { BigNumber } from 'ethers'
// Chai plugins go here.
chai.use(chaiAsPromised)
const should = chai.should()
const expect = chai.expect
const readMockData = () => {
const mockDataPath = path.join(__dirname, 'unit-tests', 'examples')
const paths = fs.readdirSync(mockDataPath)
const files = []
for (const filename of paths) {
// Skip non .txt files
if (!filename.endsWith('.txt')) {
continue
}
const filePath = path.join(mockDataPath, filename)
const file = fs.readFileSync(filePath)
const obj = JSON.parse(file.toString())
// Reserialize the BigNumbers
obj.input.extraData.prevTotalElements = BigNumber.from(
obj.input.extraData.prevTotalElements
)
obj.input.extraData.batchIndex = BigNumber.from(
obj.input.extraData.batchIndex
)
if (obj.input.event.args.length !== 3) {
throw new Error(`ABI mismatch`)
}
obj.input.event.args = obj.input.event.args.map(BigNumber.from)
obj.input.event.args._startingQueueIndex = obj.input.event.args[0]
obj.input.event.args._numQueueElements = obj.input.event.args[1]
obj.input.event.args._totalElements = obj.input.event.args[2]
obj.input.extraData.batchSize = BigNumber.from(
obj.input.extraData.batchSize
)
files.push(obj)
}
return files
}
export { should, expect, Mocha, readMockData }
// Corresponds to tx:
// https://etherscan.io/tx/0x6effe006836b841205ace4d99d7ae1b74ee96aac499a3f358b97fccd32ee9af2
export const l1TransactionData =
'0xd0f893440000011fcd00006500000400001900000000603e47620000b67be400000c00000200603e4aef0000b67c2d00003c00000000603e4c020000b67c4a00000200000000603e4f870000b67c9700006301d8914345750baf3939d57f2502c481154979e99b6049e5db5ae3d164376c2c543a5b75aabd10a41dd6305486b3b0a253b5cf247e0615370d7100c13f550d0ccd0189543f0000000000448700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00014300eabe2f5db7a62275de799b9aee03f55f01743563bbee17e722cd2efb0b1fbc1a246ba5723b499d41bd46f7c20c5eab5216e7b5adac96043c2d671de2b47588200089543f000000005ec2631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e49750000000000000000000000000000000000000000000000000000000000000001534e58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000013ee5cec085b3800000006301f1fc332c1562c12b6224e441f25a10d661e009adabce0a29e41896a85323bdaf6afac2438882c57aaa9e5f137e0365ecc27d27b3a73a108957c6ed4ee5bff8530189543f00000000000d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063017f8f5cdac49108109ad45484957fb00202937447785218f4674332cf42fc6f3e458acda19fa8bd5f14b3209dd235667096cadc57c2c65330b20c6f6db7806a470189543f0000000000318700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000143008d4e5a4defd67ce253e0232e10a592857b6d04b0be36abe1fe4cefa24987df124b96b5840e08d70c536c1a49e28d767289c388b6cfade572989e74a4f28b36660089543f000000005ec3631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e49d40000000000000000000000000000000000000000000000000000000000000001534e580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000141371c5e0331000000006301bcd1432ea3305683f046d921354633808c8a9175fc9b248146b721f4261749d962c7bd3c3b7412d3f8714faf932cd9b44da135b645e8c1d2bb3c44ddbde358910189543f0000000000798700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630169a529e91ff78552b8289c3e42e42a4b69ed57e87e0d3a8739058b34ad1423eb68545b3c1a89683f0b110c66fe0771e0701d24ccdb31de1fef9307b9b3fb7d3401493e0000000000007a4a16a42407aa491564643e1dfc1fd50af29794efd294f093000063013172648acf5ea4cff6ea4893d30aa73983f67773a9ce7084a40e07dccd61cecd0833133b648af4df5ba168234583bc1214eca2687901e6ee71e23fbc42059ad00089543f00000000029c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630195447e28a93f2dd4cbb2e00f71011ac7e938d280909be4a029054521bc348aae7c226ab01aaec7c2a00880e57ffb25197d05bf0be7c314a91383844824373c6c0089543f0000000000028700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063008070b45c42792670949cbae7e60278d2cd809c9347f26b976a3fc21b3757331904afa0cda74e27122ee02098e26504173be953a608fa01e02b124020e5639d54007a11ff0000000000bdd85eafa37734e4ad237c3a3443d64dc94ae998e7af086c7e000083019cedf37f33c099a4b81e0dc68c4b8d23a1150624d5cca9b938d4aa3c21aaef8c17ff865e8605486a0ecec67b685f1ae38f3c0158e19ed23a986363ccd2ccdb0f0189543f00000000002a8700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000006f05b59d3b20000000006301165d2c69ba74b1b1ee893303b60c48797f883efbcb2019762feb26a9257bea9e7019c339f05e311e79d4799f2bfde302b7c0f82b53ef5b5c9e837a6441e877e00189543f0000000000188700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301d539deb29e2e26049c9168eca08fff60ecf63daf9cedaf27a01d95f6f8b47624185f18f727d156b6ecb4cc3dc75ad29756a1f0447d5c3ab638fd734dd090c9120089543f0000000000258700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630125e4deda3a4e62f65113248e1b3b8a834e6c2d291cedfb71a21e544c3c3217d06ed5d15f56cace11db5e0120e0d5f67a09ca9084cde7728991827851e753b14d0189543f0000000000108700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000143009d2d26177c80858fdba6683fa5d095dacb9634e9deb842286ec79c17d85f0d7b70c617d7878dded6bf0a7ca7fc1371e8bced146f6e7d0da215f67e9e0f92940c0189543f000000005ec4631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e4ae20000000000000000000000000000000000000000000000000000000000000001534e580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000143687087b5b40000000083018e22501ff1c962656f4bfbdab7479cc4ed8c91486c4b19150d03782d7f374467036d7cd2cdba105a4d916dfea70fb8cc2c60121f3baca85c6f2a48909c34d3780189543f00000000001a8700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000008ac7230489e8000000006301019753a47c457cd6cad98373b2092f11a1aea45e9bdd27ab1105609b4c8f87453121c425f6864b1b85910b4c0b82743287f61bc210a46de870b484c07b17cd310189543f0000000000038700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301d08818d883a43fc9f5f64623dca45128884b782fef41d513efa33d4d40139b467641ed4c17071fb9c2bd045023307ed0b23a3fcbbb2f71e81b37e3a7aba1c6560089543f0000000000058700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301c90fa6273070d9ae5af8eea1f687ae507410a258c7861e726d2cf943e00b456f2b3bf75861fe705039179eda8db6ea2781480afd89a98e133f1fe53e379e1eda0189543f00000000000b8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063018b5913d83c37ddc2564db791692072efd3dc8e5fe10584ed96451778789131a14efe012befc05c4ad479a46ea57c7e48f7f64dd7b7c175a48dd445215b2ffa170189543f0000000000418700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000083019d241891335c51ad2cc632ed85dd94ab8eee00da71cd812ea1c40fbcb9c441f56e2f5a7f83e9aa02ae614ee89a4abd10c4332be95a1335be44d5cff6356a903a0089543f0000000000148700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000013f306a2409fc0000000063018dc9bf7f0c853dc0884f0070f8a0413a83f0d9d4d8e5f6f1325deb65684f3485152071dfdeef0a933691bafc850265ccd3624ed00671f68e9cea9bb5b2575c6f01493e000000000000064a16a42407aa491564643e1dfc1fd50af29794efd294f0930000830148b78f666b6d633554897e96f80bb797908467619895cd83eaef7f10b84415d3060967fb0afd49eec5d6759aa9f92c4a5a356369b518210b95c93a0a89ca81e60189543f0000000000088700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000068155a43676e000000000630122d546e170dee47a7952b7c158aacbc38b035f7e183e90a74e7ab91746dbf2bc5aaee131243eedd4781b970b3dd7cc40f6a2bb5fa15684d91e61bf38002c7e9d0189543f0000000000358700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000083016f747cffb6ad3bfaec6d17404d49502ec4bbace678deceb0a6d288dfe353e22b794bb94612e7016f8abf6d15186a3d737536d84f7b43be2ce552a4316280154a0089543f0000000000258700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000c40eb7864285bedec00000630124b164e81e73327bab1cfa0ec9b73e6dc492b4078dcfafb66c11a01134705a9f0d0da528387e2ad59cb192ac51fa9c7b6ebee791e30b27fbf05b956d1c5bc4f20089543f0000000000158700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063011226070ed0b7b181dce226d2ac772265b9831fb462e54a040bd2644ea5092dda6e468fff3a6701d19b413086fb37919617e6bff18a0d9b0791f4bf373d1fbce10089543f0000000000108700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301db43b348b8b2008a01869382c9a91d50ece8adbc0f3a7b2e20b63d55f5db1088624cbc9eb64bcee9508a2352e8e7b03d226c7f06752bccc322bf3af7bc6b68d40189543f0000000000428700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063011a496ca06b19d3b6673c701ae9633e0a986edab74d9fb5b90c9840d129c0ab0967a424474451adb879fe40f49db3ff3505201a5136f88d93a486dffc95a8bbfb0089543f00000000000c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00008301fbc6c4f4c92ca11aa750c91cd1ee111d69eee808eae4283931a27860599be53344f9763d439d3054e0dbf90667cb801320e802e74ac409610e25c1516caec4ae0089543f0000000000128700daec35af8ff88c16bdf0418774cb3d7599b48a29001400000000000000000000000000000000000000000000001043561a882930000000006301fdeb20b52896ceb6392d4d2f4514cbc187587990d736539a07ca5f59ee697ba84443d3b91894442e03741b67a76afe25ff709e0d318276b404abfc499071b3810189543f0000000000068700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301ebd8f5fda3ce757e953430c5d8c1ad689f2785c7c32f3b607410bd2a1955c3c1228a6bb7dced4e85680ab1d9e2a228c9b0a75114ff636aacd0dbae447e113eb90189543f00000000003f8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301837692935ae091a54c121aaf663f22e86b5c757c1c769ca2be3120f8d20f035a5d6ea340bf095f6a60c36e1e38fb975ad6d895723999abebe38b429637be73e00089543f0000000000208700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00014300bb50bf8b5c9c56a8b515110b42c4013f4e99cf9fcf6874c32e881554562265fa13a1539b789d0364d1e82e7587aad4adb894f4f3320d3517bd90bb08394d42640089543f000000005ec5631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e4cfa0000000000000000000000000000000000000000000000000000000000000001534e580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000143bdb4672850000000006301f96d41806e43f65d8570be44e855870bd2d9b78b512e61264b17105a22267e6338984c7bee80abb6a93f1c4680ae79bc8306cf8598e86d1d25126ce53866c1a10089543f0000000000ac8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630131b960ebca035ef79df2a1e63acdcd5d6e27789b2eb538f9f8232169165473216c51e7febcefc39ca0ed2ab56a316676bff81e5a051651767e0c1fb23588829a00493e0000000000000f4a16a42407aa491564643e1dfc1fd50af29794efd294f093000063015643222807be2acb62072275ed4652806827ecbe9780cf68af1d79a075135c7308230547dd63b8fee254f0a48fae06c7f74b9b1b7c9efa4b6c6801c8d3e98b940089543f0000000000368700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301ee5b0c65075609b827be37d236859f440e182f6c56b3e10736e04f26fab8bf993d3dee5160eb8e6845cbd7198f2afac45c41e93476f9e5b2b401bd1fd6085ad40089543f0000000000018700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301d13c1423797c1e5541f4e730ba701bb6e46bdaa1985cefca4129cb2928baae6c24634a3d4d58d705e65cb6eba169e82ef765a396cc808c273dd4cfa74e37ea680089543f0000000000108700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063019af7bc274b54c2434832f9714315c03020e4465bd283a4b3b25236fa308b5132447f528bae32614e2526c060df7b1139b67fd461b8c0fb688d0517fdc23826ed0189543f00000000000c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630166b80cd6e820bd1ed6cb92d5a4c0bf178d4078666c1c1a6576f7bdeca1dd3ff40fdbf763431560cf1490d900aff9a42a7b1b0c53c16f93b56039dbc4059de7980089543f0000000000148700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063019eb6f4205fb5ee8009209763b2ee373fcad19b04e9b4411ec7393403e2a815d43b5453d8ff70c0b60ef45917c4789c6afc45c8955cd59432abc2b32bc2c37b5d01493e000000000000024a16a42407aa491564643e1dfc1fd50af29794efd294f09300008301d22b7b46b935bbe378ac97d58bbe84f8b57d9b240f56e9335ed8134214a480c032b15645d64f624cd707628971ece2c06366fd2bbff50cbb40891fe1cf0de2250189543f0000000000098700daec35af8ff88c16bdf0418774cb3d7599b48a29001400000000000000000000000000000000000000000000001043561a882930000000008301f3ecf58a1113fb3387131db1ea3feeb9e2f46e3e48ee1755a1171d3ebcde8ce12d35af50f607a050a62b614074a70d3101211de5f0e40bccdbb49a7206d049ad0089543f0000000000018700daec35af8ff88c16bdf0418774cb3d7599b4295da87d0000000000000000000000000000000000000000000000008ac7230489e800000000630188270db66b963f84c9707597b0c8b7b353042bf61585be66b653a97ead1bbf6a4c373e6831c72dae73ac1a9a6a4e20133f92f57815f0d5a3673d31ae8e7aa0030089543f0000000000168700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301bf64ddfdac64b87045ee2b616ff27224598cd3f74704abdf729f11a5c196436e43b5e9e7fca7b0fa3c3fe149ebf561f14342cfc7c08781a3c85b6cdfa574e0a40089543f0000000000208700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063010beb607a64dcab788fe6c6ad52674b82d41eb785fab9c3c7de72ad96472e9b5c094b2c6b1fe1ff3a4e03a0ed1f0a1a56c9fd10776c61062aa80352c9bc524a240189543f0000000000218700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630152df9af9c07cec85267c8d281bc35087dec67370356cfb4fdcf2afeb322082ad33376cb37f77411963298ba7d127a04236a3d00fe1feb8270c3a42fb090bbe210089543f00000000001b8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00008301244318f4f681328908180b145ca3467846f5cdc40fe6e9ada54f2e7dba843ee414b22618c62cd57adbbc86c5ac079bc69f7e45e87108c53f22ff619501a773be0089543f0000000000108700daec35af8ff88c16bdf0418774cb3d7599b48a29001400000000000000000000000000000000000000000000000340aad21b3b700000000063016715ac2a06087215c0d619320bbbe1f25e9643ec2b7a4dad9813875832360b33592babf1ed961d1c20fd6c9334fc35214ccd20a168c5d53df0b3cfcd40ce46f40089543f0000000000168700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301e3f78d50a6720d493515f979f3242514d8de70a93d4d00caa62b3991fc48e1811c7000a1cec9a13cb59a3bd639a346d6a5834f8f6bfc0b4896eb9f12f52a67750089543f00000000001d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063010d7a3ee143952287c195738b1a238e88fe1cd7c721339f21a2c9fdab99352d862ad25cb00e9d28afad4de9a1c21d8bf16fbc09d47483c928343bd12d90cf0e940089543f0000000000458700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301675ad48eb9a9cb237be82eca75f0f058a53fc42e5952a1009e51523e3e7403ca2bc2086ee05a853fe839bfe3c8123c59373066885185f052c9d6835a6c20d9770189543f0000000000188700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301d104e95267f9651e269ef7ea3a5f0a99c044a0be82bf883c536102918dffed0e38e0e7635046abf595ad5fb1860dc3a13e858bbe1e053436ebf694b7d478f1c00189543f00000000000f8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630170935022f25655a71178c3439cdb3cdc7c2f9857fb65ee2c3af721997e849c55584d650cb4fb8b15f56b708828470224802f1ec9d7fd1afe2f0ad399c5e38ea40189543f00000000004e8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301c5bbd5a56cf2621d47a4acfe87b993fe625218e9b331ab25ea7b07005a40520d70ef48e060693a69c1dcb23300d407658551bcdc270da2a8cdacc91d5cfe25760089543f00000000001d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00014300ff75b419c212ab48019e879247e2aad3a917dc6000e4c0a788af865637b2d8ac422cd67f43ce27c0d0d46c32cdc80409a74029d00c41ab9579adf760671cfd370089543f000000005ec6631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e4e9c0000000000000000000000000000000000000000000000000000000000000001534e58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000013f1070b03f0180000000630194b0adafa5ed2c77b5b500b13df63ba79d971f383e6716b58df912738766e33f01c11739fd39045d7f2ca3a6ff582391d4094c8910f683a4525b087f5fc5d49a0189543f0000000000a28700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063013a0f2024f85cd3038d8f565ef3e3a7f1ed920bf2340621f1c961c3cf2551a21d234a8d69ab9127dffc3193d3a6af98e5d5f70e166121094ad51d91c93e9e938d0089543f0000000000288700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006300f322135adf6c6959bc012fe5ffb14bb8da6443a80f4e85799ca645ffc369eb09155f05bd65a64741cf089427597a1c35130428c2ecef60dad2b3f1f6da63e3c0007a11ff0000000000bed85eafa37734e4ad237c3a3443d64dc94ae998e7af086c7e00008301ff3ecc7ecf5a169c2b1c4360c63dcf9a8d786d804f400f1bd4bb0fcb9401644f618ca9780aae23bab8a688cf6bf40258e0c44b8089e6bf0989d5aafe9236d3fa0089543f0000000000158700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000001bc16d674ec800000000630142e1b6d027dd1fb9cf43dbb834aaa03547ec356a65a693502546416bd8596250570285bcce68e72ce1bbd7fd3f1b3f8713d06e2b854eb718cafd1fbe3b2ea8ba0089543f0000000000138700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301f343a551e25a7254de23e818c649c30cf974b4f1e7feef4c30eab697f62e71a809006682008e5c25b1816228ce36d77e503e6ce1bdfe3027c6d29787be3ae1330089543f0000000000138700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301bf45e708b0d48a939480d9a16994e7f9b325b55d3a1faa989abca22ec877c799269be034bb0d4dc36a2f684295057e1f1a7de8577114fc660bbe37edbf16f84c0089543f00000000001a8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00008301f4cc4215e75d412821ef74ff9c20918c421227217f7c135f9b56a5bab8411f7061a8b99a11ea37c5346f62e70521f60ed7e664fb4b0f82e4c6a1d3e62c33607c0089543f0000000000048700daec35af8ff88c16bdf0418774cb3d7599b48a290014000000000000000000000000000000000000000000000001158e460913d00000000063013bd03efc62589e24d9d53547a31e18a18e24cb4c820ddbdf2cb3c20323d19ea01ebc043698a7a80ddc632682461ff9cadb95b19ec520f7528dea5bc57017d6df0089543f00000000001a8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301ab3f7535024aaf1bbadd31d33aeedfaf3325d617e8b3b7814080802dd4fe79d55b40120b9f68ece14e45bb347841bd2bad35c0e93f43da8ccf8c0857e4eb1fee0189543f0000000000208700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630145d68cab27031deb7b55e2e6df0df2629c3f07a60a09f433bbd45cd3594855f8508c5ab1201189091b8197d082a7d62abc6e4a28570ef28cfab8bcaf42679a8b0089543f0000000000058700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063013615d80a47143ea2f0102f196e6a2feda49b3be3352c88bb2dec2a9a567e22632fb5097f949412b398ca857b2f105b0f032a69be0f8bd401174a29ae67a8da5a0189543f0000000000148700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630128b5fef41c92369c84e8e742df1253f6eb624cfe5c807de5cb49869a7eaf573416ba55a9f86034b35a11f0c158af9ef07640e71cbcb093cb03a38b75d427ca360089543f0000000000058700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301fc2fc4d368f9f3ef73d9a4114c4a61116f7fd6259c4f172599511882a874993b437960110af8633fd8727372a880194569d295862c321dd1cfd961394e6c21eb0089543f0000000000b48700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301b2c09b2cda9dca2a6c7441d40e24d2a7b481ff76b7759d2a2aebdd7b1b2b3e9d551638ba91ae1511d03be6c90e13d8a931cb46ba48edf1f3c972c2bc6c4b30a50189543f0000000000428700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301ea34d378265585e18e8bac13f67c9733a4055886ddabbd6e467396d964c3dea6361f61256ec3eb9658c88fb1333ae4e74343622e18573a1545ca38af763a4ec40089543f0000000000348700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301563eed133b104dc9d7430fe5082fa1b722b14236053ba0003bff64fb52f073ae6c0c3985fc59c43bc799868fc2222410131a5f08b01dca6bdfc33e9df01c48900089543f0000000000028700daec35af8ff88c16bdf0418774cb3d7599b49741fb2200006301f8773e89f4266cb5afc584dc3e10b4a52b7fefa672ca7f627f758810ed1577ec56cd9dc44571c53fae24ecccd19907791eee25d5e0dd058e5444b02ad91836300089543f0000000000078700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063019ac5f89805eb37c5089ceb5f22bcba2abe7e5da0085adb520d0c2ee4efdf3cab3533f854c08f23f4071c42270b9a074b14bba44a2027e382b2dc0377621fd31100493e000000000000034a16a42407aa491564643e1dfc1fd50af29794efd294f0930000630135234c82daed5f5bb355d7f3f81edc0f5f9f0d4d11365d4e948bd2c02e7031cc291ca1f5c76d8ebe5c1a930a9375acf73f2343cc860aa0633643aecf7420eeb00089543f00000000001d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0001430051884bdaf68fe6c64943a5f3160b5fcd7ef9140463937fc8d1ecdb3c916d7096354237eb3c63430116601c5b887dc44a4d4bce5639be1fa36b91243bb76e2b350189543f000000005ec7631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e508c0000000000000000000000000000000000000000000000000000000000000001534e58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000013cf0dfffc45f0000000063016e3845ed8e5ae218069cfa7ca98d3ffd86505d0383a3655f8cd46c557bc399c50c81cdfef32719125e7b84b1fd4e7c7535bf5a464a23a86fd9801b146b700d8f0189543f00000000000d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063014c626ef869f07ea4cee4520d5bd24f2c2772a42ce289dfaedaed255446b03c0d2abca85ccfe8233061169c0fcd12d740750f0b65dd707dfe665d4d9cfaf757230189543f00000000000c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301a0d59339cea6846e08148f7d9daba52402649546b5875c594f2f81209b36794a3e310fa64c0563d53334ae6fe87762426142dfa75b31f1021dd4092f4be895e80089543f00000000002f8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063014bd68c44229ea3cd8c4ff073a9cedbb5ff4387b257b29ce0e18ace691cd97677406b528411c6cd65df52a670dccc257bf290abd1805ba551f151fbd6355bdb980189543f0000000000068700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063016e7f6fe17cb0d1ed5970c49cc0745e3cae90e8e5a2004217472db6a1a1b9ab1124797d03a271feb38e66e690453904f568e61e6623900299f3b8079027fa15570189543f00000000002e8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063015702ed8c76f782970f0579f74a02931fb3e9dca6f3ba750d97d0df7ae60d3d10221ad66f898704bc9987b54d1109da9735c8d7acf2e4a25668134d641ac2d9120089543f00000000000e8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630129bd1bc30f7bfeb956ae7725bcd7144bc42291caf0628d7c8fb75a103f57fd6d4ce006acba63dd5593603368a0b154197d0826b8f9c3d23b93d72dc830c48a0e0189543f00000000001d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000083019cbadc64fb19e3e912ffc2492bf42130d21fb67e4654c11551f3c3dd28e4ded951eae21b5b7f98a7f1e27fdf30fce409363823c97ba03643cbd7e65b964432a90089543f0000000000008700daec35af8ff88c16bdf0418774cb3d7599b48a29001400000000000000000000000000000000000000000000000b38b3bb4459dc00000000630198e2a81ec1c025dc5bcec824708cc1f264134aa6121d90e7c284c77907ee0d0845b88fd9c030bc20002dcbf5a6709af1c42ba4c8ad6e84a55e3e48a1c79974160089543f00000000005b8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063014cc92f396e2790dd5ab926c2e4c1be43000b1098fe7eb2822e0b49ea5ea076f01fb68a84640524d5c525bf989c4a9aff9a02750a81c535c031a8452aed300f420189543f0000000000218700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063019af9087760f24d93e0fbde3bd0e2f2e64fb8d8c36d024fd6cb4a58c34000b2e810e39efa69c5310be3580a4d94bbf069487d65172dc8d39d9bcb75261fd841560189543f0000000000458700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063015402786088ba83e4a68e5f05a7e9c8089c862bfd9c134b9a9ee38aed4a39a08d12d4bf29039655440937a990e14d699101d883246a29b62977aeb9a0e8252f6d0089543f0000000000178700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301bca73ae8381f33c7ec9e0f6a5b518f12854b0061e63a805b3d5941c1516128ce0775f6819e673108d4654a2018289aa951f9340dbee582b9c6b548ec6450149e0189543f0000000000828700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063011bcab33ce1cd156607d9c5a4cefa0d67b82c89462b0d3d4cfd79fa7b2945ee0c14103722c3702f1e247190a3baf4329fd0867d9ceff2bf212e9ff633a89915010089543f00000000001c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301b9ca39e85b19a0f5f03b067f2bca5be6dc27294107dd4e9ffa9c1af204b6feb235c7e21a385cc3fad8ddfa3aefe7951e2494d63f0a9f057923b39a73008e2a250189543f0000000000388700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063013656758d6e0c9219bb9de64106bb2dcaaec309bc54c0e53993cd37cd9cd03bb15b5102cbef23c0f2e60fcd16e8729059bc6f01546f8dd2c94ee4c9cdae79a0040089543f00000000003f8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630157b7af6659df3a6f211042201fefbaf98780f3b2945398494147dd0a08add01a37fb4cdbccd4df5949381357110e41daf963859999bde4cee42291dadb4fd31b0089543f00000000000d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063012a0b8faa1536ed92b8c2eef51fd0e8a5a82d4a7ada7f2a46ed8e6b6ec29e9a263bd1866f30953a0ceefd68c4a931975bf487ff35fed2b6b3837cc83f0b5486510189543f0000000000218700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000083012aa213c2eaa5a3f0887ba7ab48498bd289bc54a9e59fbff3a8a2a13e13eb9598399c58f16b07789e8c36268985d107091b01fe0afa3faa6fd71baaeef68afb340089543f00000000001f8700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000056bc75e2d6310000000006301da0581ee658208bd90e7e1cc34873f56a3395523a5bdb7dcc1b3245b3cf2b4ad2fd8f550641f2ec4700865d040583e8ff7297dc21f06821332bd5e1aa01ba2770089543f0000000000468700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630114adc64102322203225ec04c16480aa2d7b73e323f4ca68256753726fb81e4450f4318ca276d297a402371994ced771b50eb7409744c8f9182d01599e9c5c6ba0189543f0000000000158700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e'
export const l1StateBatchData =
'0x8ca5cbb900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000017700000000000000000000000000000000000000000000000000000000000000020a16153e7140bd0ac8d077f9a594a5925cd4a16568b27aa8d32132c4f2a0a635685a0371881368047f8ede795d2bbbb05bd86ec4320b115fb22f2fed76db145614a33c9f99602c9a8e8e59929bcdb08d7c607bb26e437a3ef0eaf68672329b1cf2b82861d1ad7b6516ed1552060320224b714cc21f6161f012a5c9f0dfeea60560cc1da3c77cb05da136bc06464958e782f204eb05301de18968cdf0741f6e839fe942abb90acda53506d90772739c1b40bbebdb2340830c82e9fbd4a06af280556426bbec3b6b87131c273400e3b61898d344f59a502e94c7305b9790cb1d7d6967629fd6cd3c82c32342085bd341779df8560220fde3c9dbd26ba4d4c3f1e607984bf3e2c7accadcaf9ea36baaa4b2a1a6f472275e6ca8039d42e478befd6ee4fadbb11ea993409038facf929041e0ceb49559a911b770dd7f2424dee237bcd8524607668aaa247fd4460e09b95b307d42a9d11bed428019f9270e3778d476cd70fe54f255b01aff36232638e1bc94a43c83ccd6958d7220f41abdec3e9c9947989fa5f27a954252b21d3251aa9dddac02e744a8c34aeeadb1e2c92cab32326f3734cd279ade7d37c980e8a83bf11e51fd18f993b6489b97f928ee8647bf0e4b3786404a5a867b784683a0b58ba56174ed6ae18191ed7a1bd7251d33efb8fe68fc788d28d694270a0d28ac258bbb273320cd0a7744490e9120a3a43286988e790abcee4607e715f1ea4da9e32a5fb68908361d84868af6ae3237c165776ee4f76d4ccfcf154202a6a64b830af533b743568f499fbebe0b2e8c8ed2ca2ed67aceef9dbd7d62cb74c180d9ce282b65f91df7399719eef5654f003576664443380a1bf9d02ef7774d624ffc0adee05053a3c62f70c0e0362f17b590eb58cf972ebf4893c102f1a746050e56cf7e27496aa56eb84b778ea284770729dd1d22b0bdbd7522c1ead561a7e090c94712bd58a6fe79db7b6fbbb165486e812a53f7c0ffab7156c29ce53736ed7e31b1ffc952c72324b4fe8e385fbbba398621552dfac3aba74f739740e4f9200af858ca49fc2bbe80e5f14ae48bf5dbeebf3812e3df5d92dcc7d50b384b171f544fa56953be19269dcfcf206fe715b69c0709ea5b84768fb41125a46658d9e5d95203a7589d8053a648e7f8f92a7338c03b72ca2b2b9204b9b69bb902785fc8d3956f10783d98b604d349787a9ad08e54a35d4acce6309b6072744590e4826f7cd9fb6d22a347e2e345446fb42878c19d2f1149cfe8cbf720db9cd0bceb0370046d5ca6f0d5d09bd8685c80b0727298ac24080ede293b40421020de00b59762133b1b01688e106d945d08693bff910a70c9277ddd725dcdaf224191951b7aa710f731bae22430cb9e06a66a9ed951d6cd26cce2af53331071c90c23768f77c97feee69f61a8e172776a2fdfb056c95d4ed37599376260918c10796533b687e33fb9c785b504e64c27ddef60adecbdeffe3827365b934f8868439fe8738ac2cd37a257771d945080de5acaa4e95e4319afcc8039a90ed0fe86c22a204bf3a6c057f31cfb2866cdd5bf479e4fbd8e64260947504b256ec586ec00b1354c006d5d8e78a94a221912293aa65d86f84e48e1b927ace182bd343fe5a924926fa0dc363c2721d8febaee00cfffc3b9af2fbdf0726a608c9339fd374ed9ed62b899ac52e53f5d26493e893af0e73ccd08b7dcb35fa0bcf456f9641186eb46d34fc82725fa226d1644ab075dc73b2263be7e0fc258283ac168128d24fbe6ce3d8b3d5686dfcb79620419ab74cddaeb1a70bd371d7c476ad1f17887dfba85f2a65c85ed7aa78050da9edb9c952db9e16ee106726c3de0dd104c0363a545d0a82d4b86f90ae22bebb26d0249c6e1df5304218b99d9c1831aa8f090889d522ff4dfc85abaaa636fb8786344cf9b8291069b17894a4e8cd68d2213e6ae8cb2195168a71f9b448e9660bbeba976612a67d64e4e241b066f5dc0fe41cac710ba8e6cb23cafa75686fc2d695d46cc95acff9b996744dcf2b11ee31d43d41bb61b089cbdbf88515c2cbdfd434132b72e5fb6e49f50ed4ddd67f65d4836a1a747578a860ec1846e9bc0fa020b9ddfb98089f566c9b803d066279b022b1a847c27e8ddffa7fe15e9e369b88e67aa4458fc77e3c9a73228a45d3f8299a94f55e38a58a572873d5af33afbea7f48bc8013ad5a87871a6d37e082bff20582205f3dc991f6bad09c5811637b4b276ea9715525f92f17c1ded1779d0cc7166a290477fb6c3a15e3d4ff80f1635393a8c6fe713f92732e161442912f2d814379a5488f006ae3c52685667d3d7a4afdfff46c5ad852766b6836b8e4f626760569edc482f9871869d9a6313049de0337beb5bd526ed898348291d7f44fec862f9f12b0d1d91e62939cc9a41fc6387da440c912174c480e0ba34d97fab8f5813215657d4d4292facc682adea5d9d09042c793e0acb678f18287a4f5e1467188c3e4f8159b65ed0f95fcbd132159b4b43c935b15bf85d5e2dfc7952449822ced4608a4474faf0476792de248d17eb261800179f32e8be2385c405b0bd56b5cf9884d360e8e9b83b0bbb3acbf4adb515a29fc33e15209053abe04bf6d52e159434ecaab27c6c492418c5c1761a066f5f8d20f401bcdb091cf273c803205f1bfbd79b8e3fb34bae9cadc299226f9bf0c6be48ff36d8f803e7be8b7e9878645d88eca445ce0f71472433e9629fa6d645fbdfb93266a791dadd59f10a766b05b9c3bdfc7580cdd282ae2e1f416f1ef3f06f5570fc85a18da8070cf53c3f0004d5075c7506ebb776a5c46b47b923620478770348ea4bbd47491af32f24aeca6e065a6070f7fdd9a93052c7293745b033544d191cfdf5506a8834a8a8fbc07ca6342b452cfb96ed91ad1413ff4ee74ab4189f56996c486dba3f0dfc9973095578cc2452d6ff170ffadca25917df624b70873951c3ecdde9e9ca68cc78d5982916a9fc41fafa2d62230ea3ade2ce0158fd4d43c687aaba89662872106874ae586fc5b93e1b45b13d6348fb56be4cc86a1e6bc6dd8e43e1c83b825a5cec533c5306372371b987d02f9822d20a7cde7d4bc158684da5ec67407d4482766effca434dde31fd0fbfa0ee21e7adf3c6c5d535049141705a3344fb7ac2ed5cf51f617edde0fdd6591f8dc590f93652ff078a0536153fea67681318367da75853b0611476b4bea70b596a9618ca5d11046a6f098d38f0d087dc6c39d0920cdca671a349c887cada0c0a2e27bfe7cd3316963779e4a0f5c73b44e400a0b157c61994bf2c64715ddc80c4d2e8b66f88b4f1c2ee254e3cbfdbd6469bb8b615f292ff64f5fb27eb1c5feda880e742537107a1d5839f43fda963899931d3ea2755e0fa3957a103b3fa75436008f1bd84fb2fe20b648fc7cbadb18d9f1eda61544d6d1b058a6ff04462ed2d8fd8ab1a1a58aa1da379b6bd2f861b7e0cf5a62f8eced5dcbb426f14a97ef40922685e2a09ea3e2a66f6d82ee4a76fe3a8c5ca777ed0af104226a1dc7c7c4136536723cb569d2f7611d355b565835612eb4525d592c987eefb07da40c1b6484d448fd7cbec4bfb5803d58c5cd5c50dcae685fe9c8a141a95c26c773a0585016859766293181b0a25c1fcfd2d77bc55fc179def0f71bc4f7b9cad46feabfb62ac4ffcc07ac9288f42786f4707afd3097b7dc4ef42b23090eaa5219623c61372fe37eb58589824edbc0bc4120dbe9e96bed63daec62fe43fc8433b9ccdd416b1f7b8a780c9a0ba91b6e0cbce791e21c0956318d23679a2507993081687b7c8addc14589afda0e9f4a5f35f0ca2b125aeff40dc978f0d62a69f17e8d76cc588f4048a9df1f1dfd7a3672980135a3fbce0db9ec0e519cc011ec4f43fa0158ac6b5b58340a13d56d5b8f50fe281731d1dd4b39b6cc2db4c89de1f8185c9e59c9d65224e026530087e051b016954b2ccefc1f23daddce49cd204e5cf09c142ca8678831c961d4eefeb203ac3e315e792e946842fb8c0106d5a66adba6e2ef7e8a9e3772afe0b7dec69a3dd60988e51ca2fe4d12df05b54ee18e60665c691b4708dab59d2098d6e05fb11c151455d85554bed473ca6b7c5f0a7c3a133d76b8e5acb8fa770f29efb9842be21452f1c9e18d4cc18aad97121ab0587c3d749069bbc5f2eb49f886b7d23dfdf31f49c46d07e2313cc9cda5ec1a936b99b89e4a76b8ef6d24d606f543e3e801556a8f712add4f21c1c8cf4525947c64b4456e469312ad79638ea6d1bcbff764682bbae7df8febf67893712e4026632aaf27b02ef6185725f37eaadadfe1ab79169a63c280d71ab342bfb2ec9d1ef6857a275c5eeda5354185c334647f4c8cfb3b83ea28831ef08c5e72cfc8b0f4646509da56dc7935d58e65e67eb362cd802f957735a86018b987e082379c0fcdc08fb5a1b3e48e8f2e7ba7193503ccfed77563895d603b4c5223f8b49b9a0ba6e6b2471038a6fdb8e4b47a37d3952308502453902ef5dc8c80cdc32741ffe2c3ec2ae3523f6e29f0e9e7e7f8206dc9e3167f33f60b634cbf298634c0b96a328bc50ad2e6b2746c4a2023022161318ae826fdd85b0a89ccc22539968f052a90ec93d8553ed538f333ceddf56a3bcc6a05a3160d2897a15e1cca338e5f1e6630a04113980bf67c855293daef37636e0e6b7c5398cba8a0aa75925deafa0d75918f16290b7ea10832b7950b9f079548d13ad12500ffdd56330ef7bf6a5c71e2eabd5c5b0f42b9531abc4b49df57a77e0e9833039738c013381477e18bc21017080ae1efd8d94470e766510a694b56156d149fb51a9b2d46bfc2690be2485215647c6e6286bb071e715646543c74831bfc2c9a5f5a662933db13351bb5824d59eeaac5d81ea83ec739d9a9df3da4d0ca95acb374bf041c082b9780a716bd07331d3bf3c9f9fff8183a26e8d4541a0440b626737444d708ae57c35f494922e5cd6a4867a194426eda599a0eac5ce980a6bcd6ad6d69f3881dabbd315db4dca1492ec847fb210b19a5240f9086e15cf9fe054735c02e2c9c7316b057228ddf6a20aa3a091cd5d5632c57cd3620d8119f70957c27e4362a24e45447cedca6edb8a2ef759c6dc7012b83b5a6afc58ed4c1bd02ac7c8f63e59417ce8533caf42c967a0b60cb11f2328f0441612b9cf296199245f20f69df79896885522b7b19d7e3b734c908e2fa3772426106267ea638892d93bf252a652cd0e34659d4a1ebbc25a7a5fa2d4d72bcfca7c9af760e415c375553336375715690c5c9fdfbfe213834f6c133b55d52491a9aee7ff76941497e9507f3f0dcd697f0240e98005ef9121500529bb3bc6cd769e700ba13dbdbfd1483357aa6800bb909ac7252e4cbc85e06402720770d0423b78a2cdffbadbb99a6c4b59c017b3a4166afdf5c4255bc9b3057366ed081fbec9f2d613a86ae9e69f5905faf86ebb4289e777807f88ba92a18ef2594249f4115e45f8689356c73bd615fb61ac52ca79e98e84ca300a5e435f5087b01c2d7017c9d30cd13f11651b56b42c99024ef9aec4304b55305f1520ab8fc0f40ebb62dd4c2ea357f989ce4ddd3917ea869934a652911f8da911e0f85f3fa0a28be725992a4d3d8ef2acc1b3112be96daf3865474808a2067d1befb1843784a9fa1a696cd886167b0280b1e9d83d73c37b3188d478ea7ed62da9883c491ee2241255038a007d120294d3b222694c4ca3603c4364482fec79f8c5b6cfb2f9f48ea20b39b8681f1233e5e1a63eb806fa7894177e5fa346cd5d2c54e2c6e8d9433d12445d9d7e490e727e701c8cf725f297a6f4670dde599b2b97079614922a88d57cf6ab4116970c8256bad6a1b7625eb2bf62a03c9675a9c042f367ec27771db0a3a37fe93261b1c2d8b71dceffe79a44196a45d548bd42d2fb02b71e9cbcd7a82a9f043094496f2f9b11c982c1d07ff6596c17d188b9712e0aee992349a9673ed6190f6d6c448296133f6f1bea685d41284a6004af9284ff2dbe1c5c0bbeb3d80904a49a1b42a63eecba80c69b745bfe50b72669d82bce1f3cdaa84202ea87903387ea377c226441e0252a723fd072f4d42865069e02225e6c37cfd88306d6a496e4105013c70cb275a786acfd484c3c27704544eafa8e3ede4044ae9a5e1753a82089af7ef7d692664eff896753fde9d6a2559d6f939069400e1570d0351bff9ca5d87a17b2bda70d5a3d1b70a49571d24ea7bb86cf66bb1626f23cdccc30d9eb8ec4472551e74428750ab642affca47e4f175a571db2d1f513637df004d0ae0562e3f1696354c0596f5af449399cfebf237af08e47c6c7359d5d9e0b405eaf2c9ec8829f9fee7f83de4ae01e4f9ae2055b8e0c6f854ff4e05f2d0b5be441635b6a214b6d9fde9d547e7e7bf9546680b9a841b37d32b97ba710f7ef5a5bce28cc23bfc23f83d08e09273b3141ed139e91a30aaaffe8a007b899671c9d7d31e65ebf516d8f51bbd57c944bfdbdfd066e18ec3e7cccdd46e75a3d11372e2c5e3b1f383c8fa97b77bba8ede2c0d114d5718ff5562e6efed6bb0f97fc558719ab2a146ea883bd4803a5c2e431fc68d11c18cdc0834b5aee2ba0c89cdb5e436c4e1a9bad0975c63d86c957a2afce6c6bb49cf997660622d2adad757707bd992456a6293fccb1814f47ddc3c758d42cdf6391c77c24e8b9fd6c1b065531215f6afefe4fe343b2d571a5c8bb4997d8ab1dadf65ae381edd1c6ef7300882663bb44e210814ffc6cf20faacee48a0216419b82fb5db363c3bc2f1b4358fd139e6d8c1341483964049e9c04fca6cf9c0703c94979a7509f2fee5bf7f0fb548e04cc606e479325ae12673d0e2a5723941997f78890f65a9d4daa710ab5688f8a1529fdb5417ba102aec98798b63aae78d6328bdfe912874c80c28ae15c025be0fc291a4d7888844a6ca951f07314ad23ef9445d2c50907887253c104e38cc97dde20be744c424553783ea4ff87f3fbe3a70a9285ec09b199669d1ed2e91aa19306d9fbfdf2e505312b719963058ca41d499655796f678209fa31308a38f530a126ec71bbeaf609ce77a288ac650de5e38adcd3a35ca966bcef063646ca5c6ede34ab764700083bc2fd85de8e2e7bd5281d2f7e50ab8fa456797131dbd92e6fcd5d076a94bfb090e862269d401839d1c7ebf8e0259503a6bbfe092497671e329f3b04a2f378c2716c1aec5c9a76dd7a14a119c94e18aff3a946d55469c696fef11db9596645964e3f3c4f5b001d717b9f806979bd67bb6a09ea800ecd3f1f2dd23196a9b954dc8b36a328f397bb7e1c9c24730d193923c6edd757cff06649eda0ae8ac5fa79a03685f796ac35885d9e254a441091756a4bb4febb188b44cd31b3dda73e8f98815ea5c187483527305971518c095130af522ddd0bb72ff897b0c631e8fa07dd61da7d276043ad22cbe334fb28bf2ff926758572a4a852a4275d98f56c2b2671dd7bc2b0444d5da6a42b9fa5339853f3f62c1cdc81f5cf8d318b8e622de861a642c42c6ca8cf87b25cfd9b15f966fb42903fb05584f70eccc7bba96e89f70cf1f7a4e6afcaf44c738e5fd1f9dbac961d5775f2fc9acb8d3ee0db5d1a21d600f25c20f5c92eaab06166d3ab1e2c96b024bb087dd917559ea30034e781b0a2e546cf48ff93677d071f7197c5055e7102eb3caab9494e1cd951e89625e30c3b3cdf22ebff9d0074d16a5f15a8256f3d094fc3fded20c052e3b61de7a6cf3e784edc344a3ccc2c3101de110d9ba4a1cff5ec20a7fc7524041955565e05dacfe3134eaf8ed5c87ee3af43b06dc3f465034bf2ab822d1fc2878d8c80ae09769c1c120b9dfc734db72a82bf8d625aead7cdc073f9da2d2dc1f7fdbdeb20078f22f5b13581ff8d8485f181f347cad3aaa19e32abbe18a50ec3fea633465298735f11819fe380421480dfaac513bc766be29f5bf9363db935b96d0d57c7a342b53fd4fedcbc012a03992883453801591b110f7407331dd37ff003826006491118c09a38c5ba5ad11060b4f456b98fed3bf08fbe056d8e34eed716c3d04773012cee8cb0706a1d0cdcf7fea82d3431ee4f30231b5a82d4baa107368bb2b382d48c61360a1b6c4cdc22de348677913011422679133586aa0fa045b4431e1cc31752c1abc17218f0300b41350c1bb00be1045bfbf933707c54010124b883b7013254ffe24b21795763608e53538e2a4a29bd8c652395a8bf2ea0dc958f003c62d10152b39a120309de4342e2fdb0dcea8d28659cfc09bb9b839894dcc27112612bf238b7d59eee89c1e1b4a43ecfdc415428e17ca1b8af7cd0556f5a8e56261ee4c27a9ccbcf1ffea388e42127ea0e2b68516b21ec79adb3fcbe36336d93b03168705c467746d466f74bc9bea78473caf6c658bdb2d960652c399b640a58cfa9c3942049f56a0f2058a03e36ec1c60cc758e2f2ba0e624ee7a5d0dbd7067d7c6ba6b0e0aa4037b5fd414c64ab44e0a4621a24a8c104a444a8b3ea21830ebaa79adfe08463c0d0e49e4af43dc28e5e75a1fa338903bdc0c414a5b22d799370a0f125b018b9b4d9065a0882892665372e63d20a7b8031c5146a049149af30b084ee8788a47698220e264a86c04dddb9f392b6beef7bcf0de1f2e4a5a9caf43cf5c842d77d9eba3d95cda5d71e0e8e55a1785c45840eb1a8df044a6ab7c406719cbca2a6babd8655234f0b4bae9a55002abba8c80db9a414ccbb46e4679d778c49e641e6c4a623ba909d3d915f5f0203ebcc5359a002e163bae0a1fed8f5ea6f530f98fb3c1d608519a0490e4d8bc9856a52bfbab79bf752862ea7d6d448c2dca45438234360dff43c4de4a2690daa91fb347be6fbca9eb8220296a2101c88f0c6e19f2a9172db66dd36a2a6a962f740d66c46ea1558a201fdeba7b383b26a6afc87d676287d682e585d54a69c34eeab6b0e49cbe4d9e3e9eb6d33b09cab6589ed29e5496d15fd8d37b22d057212a7aafbc705eaba0cbb246126601c59165654099d50c4ca59796a1b36c61eb425e05df556dccd41e701209422b65e538a452a0f52885593181ddb82e15dc07afd564914b6032a68b9439500fe3b4ed679ff68b6e704c97e6927959f42da1b39f29d77f7aa6cd30415586991c7ed42ed0998746998ae28b3df1f8951c2c32f5c89a35afb9e954ae68c90dc532ac23bef98a066a6e681a8ab0a32f2ef523b28276d80b467992a070fe4ffbe8ee09eed45b829661a2a8a152c158ddb90728f9d512f38c42ac0f8efeed5ab805a557524b30901c324e69af0e1649992831b3cc5de52f1766341394fb5df556d74d4812826cac417b22b09f46ba0e380e47146623ca445c88fd0d9bc93a4517413eed4cba273b12f6ae874a4a6bc9b8f7ad91e70027d16f0ea38c1d72b2f05d610e1f94b26643b3dedeed1729db27ece245fc56a460ed983ae2db9eb0d2762d87fb12a45da1de4860ab6076d4ea96f4e330b24876abd780fcf1c3088670f4e42e4460ab1b184fef13a3230c82f0669846131f5fde3c209f09a3c3bbc683b1e3b2ef00f5904e90aa90cee102ffd081cd11fc74d3f8c4ae7a7b6a25dee1fa2221b30be6ad5657e21c8e8d2d3963a6b958724fb9bfbb70a61a26d7087fa7cc7be36a2b141fd42b8daaf64f8c7e7021d890d8fe3d52b795b6e6c549acd76dbda2e03cad4102113fb4bac8e42951d92016e1106292f135e4f034902781e34975d231bd017c3e7d4e3c30359fbbe79b9130ca3eb0c836d54556217460700a96846649300bb0f8528b29127ed01b647cf82d72f42470cc5a2500bad79f5761226fcdf25145d6c9f2baea49fc802dcb9612fbbe7420d0512aebdbc897db0e64855c828cdf0773298c36f49f8ba66e249c93533d704dcd3843caf3d45c832c153c493097eef8acda5da5ce753df21d078e866672ebae4211aaaddea7b0e0997cde9df247f2dca2501698751a8e0fa0a7461ff3d4d2d259a414ae78b4014051deddd464d6d77fcff171ecbde719d781a623e9f2ba319b07fb217b54e66b9fd54e0335ac6d0d634cd039e175d92704575f2a69a9d54de18143495fa9a819300848b45fc8759702d1fa38d2c40989d718a63cb89c65001b1c5e39ac5360525384f3b2ab6e9a26d16d05b7ceefb540f675cc2c912bd55845132a196964c3eeb4608510c697a56496c00e7ccbf6281fb861318d8339cf6aeb1a16e4754b4a865990824101f4d31f978d0087d4e2699ad51057ca10d907f286010f8b6191f86878797d6098b40d8aacae68477f18f19eb78d5c228903eb747ee141801d00bddd0c101d21e7d6fdba072b6079fbf838e52e33c424417f33edbff7def59cdea33e1de9d2ec4b4a9cf9a44b9066a9bbd073672da75e93f71b3e1aa9fd69ad0c45c000ba17f4d1c64c80d877dbe424c4c81504f6de7d599a2f060169195f59ee2df00ff87510d53c7cc9c7b1da8ff1e6fad04dafb135add30bd8b32a4ef96a6b0e6c49ed8bb81563812f48fd94b38060fa40219d559646132fe9c3f0deb86e81e0f46fe2ad40c1d4b47b330ea7c2e4d70a781e976bdd86fa9f2e889d604954334307f642154b38cd48392f2350d13e56186e4106881eb3e4dd7e6eaacac7b704a3e12af8335ec08e723f6c90951cc68da5e720ed58933e650f93aa78019006d4d10a5146d6ee412a56ca687771fb7b8ad1fe9bf368117abba071c87b62a81cbf0c1465036c3c840b534ddb9d2e210cfb83767f125012b697f3e7131f7b93d8ec5cdf8b53423fa2d612941d280b929104490fd9f9aafd2d1473f11a5b53954f2eeabbff6cd85d5dc4307026e6e90cd60d9211bbadb3f091a4368a6c3218ab1e9d64defbe6004e5d2463ba48e0b43dbdd138be9009e699b243d128be4ea5bd28b24540f3e9701f5f8d9f043a411c541d9d15027ededcfa2bbfe634fd0d5b5bbf2cc5584cef21cce73ab4eb84983bb79b78208153132e784f044d5042c1b8cfe06c4587d2b30627bfb0fdfd1db6925100583c661bb1632907f3f3e522332dbaeb6e7dffbe4eca8b5db76d42e5ebdd2c8cdf90adcde0079a2429d79f6c1309bc55e354c2a87ec2d3f366f91a5cced192e193c5fd1cf2c7b1fab9ff8ad8b32118737e23416a2e57af5f2b2ca3b041ac99420f9f98a9b6e09bcdbd58ec23bdf60ddbba1afd551b02d94c9cb4e6cc59c12683881226381dd382f1f13776837acb4939f0bb73f6b20b064e51e4b7e00be53cc51ee80039dd15988a2683ce7ce25e8ce0031eb604cf0cafca9c4698d0cf02c5770d94304e0ceb9a86b750ee6da7179389dafd8f4aa4fd63551c08c6f72d2ae851357f42e801abe7ce54325fb4500a0211e11ce85d12dc661f568217093c7530e624b1205e4c111b6c62698d90d617c5e9ee8624a74ad37e64210c89207e184fc1ec339deb37d7e6bb676cfaed806dc09a804f1f4e4f4e8aac3a118fd1ac33e2da4c48e77e1c151fc6a3351d2c90c80ee385efcf27ff1d0cc29d5e77016f20fa9fb524970e549ec606d571eb46e320fd7b1706b80491ddea7a307521bfea93baea892434dcdbed9a0a862798d119dd4a33df9e19f45ac06a125cbe1f35668977c706877dcc7afe87df3908b410dfeb83dcb5be35b2edfbaacc02e18689deb6e075dffe214d88b6f91f41232207d99de54eb304cd1781721a47c7f5bab1051a8e82ad9973ca6172ba48f0bc3acbfe2547d15f28ecbcbecf40fe3691f27757343764d9e36f568946e7ad5846c09edca85fed685107916efa60bf380f3abdbc6864506ae2745230763909d2a4028200914e719478b979fa8aff9d8611f149c0f7e38216b582e5ac3d42d75450f05e935aacf4aeab539a4636fcd3ca9823f5a4498e8a908f59edbfa4d18d8d0f9af5a513d213f88594f9bbd60169d9439f65751cf768fdc9591939067dd648410f59a9e51969cd1e96f957853710978fa5ee5e0faa2cdc3c84e688f6efa013f076d95a0aedf1922c4a3d8b3780c7a8881dae23bce8ad71f2c250e024f55a0fc3b752a50aea95668f8861b9456b1b351378af1cc9bdfdccb892775def1f501ccfc596bbe864ff68d30b3cf1064bc31397e61fed2858593c12858b40acb6b8b2a64cd5f0b0b7949079220f4e36cef17d3bb7c4de84a5434820861f8ec35cf3625b5639ddd983f97c49fa7e8a231e40e91a39088c22a95431d926eed60b94f22f24c87e0e9eced837c4e5f2ab86859625e0ebdd5ab138a0459f6b6fd542928fecd7e30b07dd5d6459206cbc0874a4f7452d365945fefe7d80f50365bc40d7be27ddc1df0b168bfff690d1f12cf5c2f060640afd0ff39fac543ffb5c992c86369b5d7265a55205dd3b6e36899d13a4454a3144688b0c59fa2df98f3066fae4689e36c6b0e818ee25ba5c725278f059026000ca2540706c4da56e4d34aec9c244d38108d696653e0106f0b93e83fc16af3510c2f3707512b5992e9de13f9635e751c5b6325ca8536de726534be66934139af510a8cba7f9e2b036f62908c79d92c148fa7d8d0fc6e71b69593eaa99976a20d6f54d3378635ca9e19d84c19af82e6b35f87ccd7ce267a8d5f7fee18d1f99f4a0632653796c7596b41da964651e682ce34ea1e8625c3da93f3bc5d45666442ac5595942477dd6e55dad6312299f3afdcb05e0c5d7b08feca5c6dec0e4ad2d6230e57e397ba8cc5676e889d454707cf1bc522fb0e422f486909dc5b2c256c5493a8bb686431ad54525733eeeda77f97a977ba64c82d83c23f87859b19570d015a6d4ed7c397f1f4c6fc585c4e35ebb5fd69240eb8b74c84724eeb6a7af6c58f2a88212d927d8fd3cdde85e190f87baca9cca14875ad520ecda4d178edff3ce2b9dd77d61b56df04781cee624755e494408fda0610253387473c1cae4ea866eb7aa474a76d4dc8e1591d6dd087aa670e0058ddf032b7e2d6f16facc02cad068fc9782788899619fd558f0e69527d81bb2e5eb9182254ed56977f740e38b58630cac01dd809c3f1e3db7f2080b0cac37180164d00aec0f07f535abb11c51cf1b5bec776d5d62867933e25af17810c7e8e639ad6d0bd5b2a0b206786258f40a736cdd9e6f3e6a865df69d42c12e32378f738811cecf71cb040d9aab3af61097f8b6feb739ff62c73f6ad39c4a9badc717449ec893d7e6464d3ea64a417b391244f054fe22430d08e7e8c987f01a9a765785150b052e5382a20a14f693d31395e4c7049f81b5556f8629530a4260c0ca08f9e78eaca2e6aabeb80077b366d0d20ff20ef3cbb31a7b50c4adaa1c66f390beab2f30c1bd914230449ac3aeca590f64ac81f755e201faa97a858d14eeaa54eb1f59ccf345130bed5fd4e8b82254bb2934379a7e4f17d29954fb423a354212aca42fe3b2e15f3442a991787099c176e7d7489f2bcfb23e6cd17ef795b8598b50b1e7eeb734bd33631af8ed49e0943f552b95b18c437e7c6393d05952924272edec5fecb41a20582a1c8c1f76569eb42c649fadb6858853c32f2a3786a28423681b416e0bfd0aabd53ce81a7c8ac45655c50f9f47d465759cc99094530b8c2dabe878133f3931d19c7114b0e57d81b29213ac674b0a9e33ebf404d666a77ceaf31d5cf4ca1b64226c1012daab937605dfb4fe1fbdea0a95c8e6563adeba3ce403b1d6574d3d4e2300aea451b142ba46fc6a337645e6939193edd21211029b97de00ed70ea365d8637fa2a4fff058fb055680360a947c4db4de722bf47bfaf1f4b3be4cf31052d2384bc3915d2dcf7b7d236cf4355b1b632b13be897ab0b50d5f4ab3f81287468f5676165ad651c1e138c10b81e1b446c2fc213eb08415040bd9bfd5585887b33f6639d2056019a802f1fc44ccd93feaea5efa2f7783c96058a62e632cbb79adac30b7c8d25e22f2f5dc2ba6f091b7f74993e6fc3e61c73600474bb0bc172a7f40f7fc13d84f648ec8b5dc0f8beaa0a7dd70ff94eaef25223e532c9ad95536db3e3c839588dfe8b2342ec9d7d398e6a0b5afc95edc9c7ddb8a10de4b0d5c54559b6d471625e7b12ca38145cf21a17cca0931ba8f97c9d795aa70448722db456bc8a229d56e31298b745ce72e8edef52f73ec305b6d57bc7fbe63891c21ee80d93346dcb7ccf5d4ef12d046449ef448436229b87fd300072f9697e4e6634683823062916b709da8bf18c5780cdf20d57548d509ddd8debeb96bb119311a0dbaf436f872bb021b20bb7e896c02a80d84d81a898fe96ff4ac60c9d5872c3c1047795e2c51feeff22722e87743b99e5679152d0050facad7a4c07aac273397918c6afdc179ae29b4958560fef7a2c7cc096de7aa91227a2fb35d7e7a9bb123e9759e7f0ff5f4a58639ae86eb3139747828d570d900ba51755bb09fa8ec4526a6c42bc02cf011c0083ee0eb2767f6e33449499ecc08b2082b2b01c1d39b5cf2051e6af0b01a5ac6b5dd996f03e6bc035d10f18bddb6551defc3d76aec606128fbcbedd75fd72e8aaea286ac2239e11c6cd58ef3ffc6a04b9667263e81e4de10eef57d9c00a52e7fbb2517a8d2103da19d5301664519a204f7084df24c69b9e430987b8fc85a7e1fbe5a65e8d75aa47f0a2378c4cdc88a148598ae43270dc46789e01fb07a582adff706b5098c4c55e7c34c57d024fb8e24ffcbded7aecb4e1d19b784913dbe091ac901a0ec03df4583e6d4860e71257044b2705f278533e734e997948fa894448576027b078ac9e68d0ab998f4c523cd6b094d3ca22313e4ecc674298dce027df4c9b4501f2a24cd41a02e5ebafbff6f04fe4cc2b12ae3175f3af36ff6949b3266c5e29b8815a24340188f33b3bdd43cab16421e592b35a0ef5cf197c49ea4cdacf04becf9b09135be23b8229217e01a813478202d0ef37f4f808016af5b8ab98342b3f556c79f8c6a8c43a265f646a1d411cb453d13c24325ade8d436cdfff23b723d4eb448306640d3852688a841b209f26c97af9c9851c099a1d00da8167b1920ed0dd2784ea9f1998d97e475604ee0eb989e456890e127f1d25a347a148721f0a4f601f6d151c57c8d43fb5dcf099a4efd7e4724c974d5f486a0cd797a618477552951a0071b7be1c25334eeac803ef6d154c336fefd0eaabcdb4a2ec4132273f4c5a5ff704242098ad06959301382988fd2dff00958b0298b2113f2424cd70372f96fb013e1982c1277ab599c2f11aaf76d182fba0a81aa861a7968c5cb208e05ce53678cb800d138987a89779b06e518c9277e21c7b7b29890a2a4ef71778c6f3f58a7d943c3bd6f0551646b503c4851b270cde08aed3c6d3580f5c55af81424a0f73670a334da76370521f95e0fef6670ead27f238040bb594e880736517f60b27e1df83f71b5371e8734a08ef1fa7f1fe3dc93d2785bec84ce5d6929cb1da1d486578f663a99e0fc27cbd3c4224b5671f3cf0879a6e912b356dd1c344763d2c519357ba96e7829a82834325862f5f64369978702861469259e86fcdddaa305e5b64e2544e40de03847b0df47c4b246e649c7552044bd993200b3e07136c72db8eee00b4d08371748378ce456dbacc247ca793bf452cf0fff6e7ce733776c8c9da4bf6a6847221f9488bf1065728ef53ee07d7cc17d089c08484861c43451416004a3da9e8436606031cac6d9b953426158331888b04bc308b13097ea7150d89966def2dbbbebb3ecbedf2edb41596f9e111f11b9f6438d5980b1efb1e9df0398839799869eec4b9b14311949981688318b819dbdad59401606c9aacfb743bd2233c05c16e2aeca7c1ed4c98c4aaf094c01d1b96290d1152dff4277bcc881bb7727e1bc12e364ac298eb707ed628f1fe55b55c85118b28e7895c422a7c7b0b2cea4b63218d7aa155dbbc4c916db3bfb521008df4ea8590fb04e9fef23781ca5b9336f98aa99a815cd950131c8aff8f698458695bc03b359cb513183ead3096767e6f38796ba3e0dc2930f66cf1d2fcc8de4068fa3b33e22435cb6a39ccc7a5a92a4b560e4caa34fb4bd18dd4a8940d6748fbd309b95ed98955ad4535f6a8599d80b413937b4e36b92e8b7e8251315ba9e64744ee51a83e6d5b99bdb2cb210aa7dbfc79daf0c43ad8aa6d25219f252169f55821f8de3a65a63eff1dba22f34a8c47cf0739e31f1773e36d06e1228d5ba75b50a5835768af32654a11e77832805ffc6113c9c6b4e67766400fa5ca6141eb115577d2d518577bee7f2d51c3b1906e51ba80587f755ebc962f5e5ef3aed056e5c9f59097a1d82b4f3c0e2f22caa1770d5b3ed5bce07025370939dfde5664dad1460cb12b8f43ae50fb3b2787e2811c59ee46706f9caa19aa173e1c70242b694285cc02f6925b1783af50c8dfa0848c7faaf30ca9476f9ff37b27970064a952dbf1c98882099907ab89a8ecb6cfbb3ca6422a6680da7e4f24a1c50cbfe6362f12eb0ad014d1715e4bbb810ca8892ea40c2e03096586657035a866c0b6d098fdf55c679e1b83af9e182e2226040455ba705b4c01bb1ae97189257ae197ac25e9ea70f52a72ee65dba4134da88e0460c367b8d5e85f0a7e43deb703c01609cc383aca31c9feaf71c8ed198210751c3133a60778abb78c1fa8ea459ae5a844d7e845a5a4ab149a3dd83b51014385b638847174dd82db684d8dedef336c7faa07c46de87a29aa8c7e8dd8117d25126be83fb314369ac9de5d2eff765ed8477da939dc26d68abbf15914a5deef9253b42a515a79c71d0e42e43958383cb6fc1048374f4bef04547779cf0c432602ca21322f15cf0df5cd2d59ecd09d1516f9d95f2a9343947027ab3ba27e48166ef56fdbd4f74a93ffeadd34cbc9049515360399f153c35d976fffc3d1b63d581b952935a37306930ab741c9e1eb704a25aa3df3a7e47da597fab699ad64c8cdb56b055e2bbf11a9a0dd9a54268d447a7c97374556122191c8662ec1b926e83d21ea7ba9aec5e9a852e81083e74184cba738e74cd9c3fc54efbea1f0626c7c02cd65c90b529a10a7e39138a19e992736ad2eb359c5744c5feb42285ef79dc941401c97a9bb9db7b91d63ac1d753b58413860be7a964cdc7f4b396e0a3f2525af55be7bcfda6df089e66a7ca872ceee23fe7428b84ec7a24aecdfec24f4160959a5f66b68509881c742df2a42aa2dd087ae77822dbb906d262dd2b612f7b19293ebb4c2f29822da6703451b99736db1fd6957a9614e5044f8074f37ca57128c040dac22d029e862c4d89dc8b88f25df0f4b2a46d77f78b8fbaebba74b0c40718814c9bf4d2bedb7ba91fce687cba745c2c62d9f8a633d6972b60f17ce6b3c370c8502608df3e3c065ae61e44d255f36b924736d57040cf481df3100f8258acf4f4914c6b56a1cd8cb77d22d88b33146686d1329812b19fa39bb3fa1afc6ecb2668d148eae2bcda295a62e3f65dfc1cc61812e56fb6ed258d3aa29d8bc5242c60ba45cb77d73df2e41e3eb875c27818494f223b759ce96387cdbf2ac653bfcfd9c9d6091d8fd098332edd406a9f001b1cc6039d30d0bc486645c005acbcec0cb17197f77c610cd3e8ea50757ed5f9339bddf00ffd45d1bc6ea19abfcfa59ad91300879a44c7e5a7fc8363628090303f42c7e698634b8ef2c6560ee6f7615d6fc819a7c77002a6951c49df051911f61fa7ab45afc1be87627c9f5dc3636934ea29df5a4504b3174b55b4a442b780655a2f1f41c67f05d0c4990d45f2d1e2bf059371a70865c8b6ecd880044eeda9fa253d175d591d751b7071285f50efb8bd15c4a4d4a98b36066f2888a0f0588ad72783efaa621c1aa5d0739aad11dbe574c75784643bbf5b734732787ca8e7b42d6401591103fbc9c8939bd807f2d3ff496a11125bd9770363891ba34ce32b750eef9b45c0d48f409bd8f29d1f40025956df43ca7fc4c77725a6d584c50b8fa982b748e234f335f0ff46b806829096b23952a901482a8245569fda4c22ea2cacf0088d4613690cae88d56b1fee3c5af69e753d26c2ab41acd37436325f924ae29527c75502d6e25ea4d77ba7048a34e93d1fab02bdfd13096a48af79cd43d2ce3bda5f4f810503d3c28509217649ec0da8d513fa7f7b8956ba2056e15f2fc4761f37a805ba7c6930512a53ea6ec9f7c351a41603c7e6a1d6d44741eef58aea127fde141de6d1aab743d1d1bc91c3637409f407704291162dde7c1411ce25b7513757ddada345ddeaa933fe31d14cd47cf725d348d5b5a704caf6e4ad45e74d11785b3c3670cd5cfccad8a97c3847085460f1dfeca096eb0395e4dbac857618c9779a89d76695e32eddfa63df28dc81454ef5a6667221a2b246e5108711b17bc665551410e36c482acee2055c33d16de02e521cd445c9a8dfe911c392d7c8968106297bacc424ab00873412982a96d5df52f73a4371d8d189ee8362790cfcdb240ac7fee45621351f6de354e6d480998dc702b168e016a025969745606391409fcfd34964a884f30380a326c785428cd23c2b1e37a3e80c54e1485db990015b77b9d38c86d1aa3b424cb50c1411060d116d2708439a4d2e6861854d86ea2c7d8eae3bf9690638c7ed5ef1c5f41b80ad150eae93096def040a3d6c785e0e300b02e8d4ec1fd7ac4bcb13b897e306af58b29d8bed962e7b6d8caa6637848f51686ce85e793bdf4b0635264a0914aea5c620049ba2fd1f75b344fca8328d1ef893b549277e83bc5b5d665473aeaf5d4d1fafe6d167408278dc0a1cf7e430857d67e67445aa96eaa29482ad5f6a01bc9eb212a560d401cd1c845ec1d8f998e366a0b3c45e674e190ced2e0f50f249b628bef881a395153d9055e7b63bda19b44b472f9f29ba028415124e90eb30ff870401b272bfea8945f358cfda3dc65e076526b060e97997da19b29a89acf4f54620de667f59f2dc1c1b144b18bd34e40e93b057ed6b719082be2d847498a2702ceab39157139cecfeea46866add60f470c25adc00ef6dbfce69e359326ea725fcad726cb1a022f6e506c0f0f9744e22cf6427c392afc6861b97eb863bfe62cdbf25bb71b2d7d0805760e18098c0fcf7d2de8170b1f7dd2b0ffa333daaa7b2bfcc0c09dc228cce55d6c4b0dd36d8b1f1c18860cc6027667e6dbede6ddfab2786c7626bb721e8e9ce08f991fcb0392dffccdb4aa9bf49077442e81e82b47f80cc005f6acfdaff7a81c702e0e0b3ff128d9a52e815aae267af33cb1389088467ce4d6d13f5d2ae5d58492dadedd5630fc9db3448b5a7392676f8c7b2478a5a2e54e3d46d816b8675fdcb8d0370b41bc3e5071cb7114ebfc9cafcdf6dbc49ca72642207149970d96c1960c3e43d27b8ca8901912d83705e204251a341b6660b95e8ed74a701de0bac6476e47be479546feebee3a9b790b005414403e12987f8e422e8112ca256b20a47baec8b1ccab46d2118ec7af172e5551f24b27142334442b6bace845ffeee3aee6fce8eff54d7feb01b288e95fd4c195609d7df1935773325ab9fa24111206ae7096b4e84fd6d4ce9060608d03eeaec851cccb5d94a2c067ce9ae20df4405e4590ba2606f823e60e74555474f11c3c4bf4ef71077d348a90cb070a2700e74c18fc25683c2fdbb67c48f496296476e19d192731b79f1992b1725efa498a479cb6ae718f069b7e871112c0a53679919508f98d24f5a3b9d7ca75b9d89cd20970ff2aa1e2b9b08242609111a16ec523f6276451d41a3e7d8407487a24d0204fa7e0992517f77e7fda9ee5323fb96d61b4cf68e2c83d1d70317e75c05a895daf1c59268d99ac8951bd35b0236030df61074018ff83e75357fee6a8abed1291392cbdbf89013bce4343a2fb36a67e206c3505eab47384fcb8535ce2a410c6f19670a0583524fae7ce63232aa539d0f8623f81448c0be56f8557512ef0ccbefcf811ec47b76d0e54c7e3d337029e21709c6f1a72d76c290fd55c654feeb41cd0eee0ebb1555ee2b109cfaf4213abefaf859557ccdd79e0a4112c6705dee7ce16dd6fff4497816dbc3aa94955aa84ff84ffd6e13c872281f72556d32fa2b3a38d76e4bc98b70c0fefe34c1f26c4f43e7f756095d88118a59ef54ceb7bd0df02bf751c79ab097fd289b155aaf423979f73f4fbed7450572183c689c8e6a7e2a7fc6957e75ea45c3a0c174e1dcdcab75642ab4b0d146af194d4efebb4af17c41c9a26ce2dbe01b9774e61212ac5228eea727b913859ebe890c681f5384811a39355fa787210247b9e385a839697d6ba40e0d4238b6fae73171f50972de6e8b9c476308b98782b81c0bc8bb0cc42e018854ca2693a83b3deac0af3395e78ca2d01ea6912676a61fb82a87fa42befadaa2bedefb66e88ebf05028b7f7bc16104f742c98821ceb9841ec3c239e4746ab8c5472cc0bc3b3247cdbb18b96a2279d0d1af1b954ff2c1525900714fe12ddbec5ccb9aa5baa1dbae2b629b312a374b20d86503bbfe474d9bf1db4c4990ff235cf777910325119f4c39884e1a3d74e5e3c58dbf8376f737fd8d28ad454c8491b0a1473ea936bf5139a51aafe344709f322e3618a94a34af278a6c380456b080688a11e21a8ac31ca07bed843ec16f010a68bc2d441dbc5b9c0b66b79af22ef9a742d07b17c91f6aeae44eaa9609e6c946785479696877e943b7eb2de6817de47dd6e3f3ce5676c1be10769e133a8074b61aaaa28f226bbf66bbf1f49a7fe2d78bab994dca022c5d29c4296600f0b9061623b82b0fe2ad39e04fec095aafcea9a914bda8c17c5fff0dea3a15e04282519fa20c851736802e54aae80080cde21c822d96e7fa743ef098247537cfec314201f3d550659c072def81258928fedc53303e077cf77f20bb40d408201c9e69d856bf6d3aec26d64edf80a5d6f56ea47d160a9ec57d800c38103277a986a1d24685bf02043d81d1fc8ed084af6c249a74417acc7fbf1ffc658beee6fa387247b3e2327cdd8ae7504df9eed1fe2bc5d8ebd15366967950689b30e5ebf5e5b7313385c2b7f53da33598d3730cf92a8f45825c35c5596b40200d823f3cdd5be14e5b3cb4af1ee524490ee3432e5d7a46992949ff61c39bd42e88104282846d86a8df44e00019cdce26158f504da5037a5a448e1591e834fd8ca871ce99d496b9f4300cce3c8cd92485b07a7d196ece563bfbc905e76a9488e606aa9c2458b3233161a1cc94cf4d5f1ef37f461c3024c5d5bcd095ec3e9d520421feb8f9d081e1222e3b02b7f2fa58c6550172a4fa451b7735039ca334279d2c5db3bb89095d761ae33623f48ac6964fb073584d71a44fc5c0c6e956a1f338794dc9e1c06845613ead6871e75270e95a16bb9fa49f7b14d95fe1f4194200236cf26df1b11507d1c79249f2b9f9e022640b0b87747f7a50e4f7608d1dbc1e3487f7c3dcade90a79ba2cd64cb2a22b8fdd9b4edf185a380930a51a36798441280ff148b2f7c01ea01fdc2ce5dc0d4177839dd7a2292124df62c56c0c4df4d9d2eaa6058603e385857e75e678f096d121b247f43aa685edb0e311d03b9f78b6425f4ec4410ac58ff3f5de2cd2c5273808a9868431776ff0fb40e7aa5aad58dc9f52f567b1df41263d7613375e8e5294f16ae8c3476b43c59ef47191dd02532b2c12fd1584d72766651660dcfcdb11844ff1d51937a280096519143b29df97a4744e0081cf1d70937a2fc934971c6c86f56d21831f12849797882fbe8b28ed9f7388a6c7e6ddcd2b07673934d2ef33ef6e7800208304fb9e65f37a4ed9c4b618c759e4029ddb86bd116717883e959c0a803edbe4b477bb5ecd59352e78d8a187c0468014861805ea1d0cbe0c8f4a566c98d7193465ba3cbc5fa25d756277aa5202b14daee87368746e59b20f8efc9d60e183341fc5f80a7d9a9ac74eac6dfe3d14da27280150e9c9197b210a2c73ff400a64ecbb67da91e073c022b5f42dfe0fe1d67fbe2fe1cb53f2f1326fbb9457a27bfa9adcf85e2c3fd4c43bec70f4b3bc1018d99236ad3aebc08e2b05fbf71ea607de74cb9cd938cd389261eb5876a6105c899d1f09434dfbf8e40469c06ab2dcfe43f2509b139271198e106697a2cb21ffd954beffad87e3f5cb7c3b6b5cdc245f1525573fc5bfd9d0efae1e8b1349a7920f353a394d3f3d701947b4102c4b40c88702b5f4d761c10a75b6758efb4549e7ab6ee0ea5e3e93a17dc9bcff5ed2cfca8f29c81858d0c110161d15d7fdff4696ef9f4f1d7fde62d6c498d08fc70389e866c18ccd84aa73a0913e11981247baeed0183400f772eea5ac023ea1fdaff399330cca59f8812dfb51a5b1dbd02714f7641b9581706ef5fed59b1a6ff2694d8896a996fcbfb7aa4d46cae110987ed5d327113204e1184153cd0d8c0c24e7c9bea8ab66e1082a2d42fb6e717068bb0400bc19d1da4a4976d7a9d7589421df3e5d4566ea7de9a0a0879f707caa418a8c091a0cc5d402274a34857fb1cbe626f51d53ffa0a227717c71bc75762427e5edc33ea8aec891c9397a986579c303db541c653b85151e45a1f721733b7a8dfb37c86ba5f5f68e757d78547bc74eb7f55d1722dc812b7117ff89b59d1aa22bb9cd6a7d46ad23e570e4a9beaac59a896f9b7773b6d4f5ef14c8620de3849118c8fcce6c4ba21a5cab125d524a69b20d625c30f97b67a81c5feb6b52c9699995372ac6904b29a4695cf6d12ad3e13aa05ef9ae3420c2aacdf08aa4a5bf92dceb7ccd08621db2fa659d37a2ca3adec26c30eb996a9555e17e5831ddbbccd89ce8b6e59ec51d31e4025661c6aa8367f1313d37ccb4b48186e5c3f2d707f046f17c00a48ac3ad86b5e1166cdb37f13311e99cb4c47ff97495a9897063605f24678011f4f96c64cde8d3db3d8ef45ccb45f973aa6c509aebf9277c1c233a3a54f8611018cd5e17a9990b581db6c2e5640da07bcf5557c95df2978f1cb0d558bac5652a8213ecc52fcfbb5972d00a96d34623f4a0a733eaee9722c24fd87e2fea8af653bf03148a0116bd739732b0a93cfe8e4860c011302a4b41dcbe06f2b825c06b51629fb4152e8d80a3a83c109d67f288842a340088ecac2afe0f8c8a0f05d6617e8b5da2c24a1a1827a2c15a060473dbf66729c24ac7ee293388e0ba24ffeb2604a79db32f5e6c8f9e44afa995517d79323d4c9ef2e874dbe5c1325da5e1d9a8a8ebf8f352485382dbfaec6e4ae8bc322dc6517edd916c12c67d9c0e115a10730d833d6aebaf0e2f8358f0435153c2c3873dc2d74fcc7f2882c4b11dcb93f9fa9fde6227174354057a8f77c88558de90cb5fdea44dc518e89473c056008e27904ec4161a81e9368a9b26b5af381444832b9f64b37b987d85f89e3d39bd353b75e3b0f92abaa2b9ce963bce06dcba9545000085bfe4fdf978ac3a6363186b2efaf411e740be28e156cf708568bd3e4744cf481f33bbfcf7a95d4d3b180c4ddd43d2931ad351bc2e86ba155449708923953412450f3381f6a022544739bc5b24a8d242628c37d6f87d3d56aa3e1962d06b36bf52cd8524c0130807fdd49734f67f3c901a2f3b924b1ae5c9c570ba56c6808fbb54eec9c4ac0f8d9c4988838fc97998de9ae1cecf83f4c2f4c31c0e218fd26be2eb3a0dca4859c28bd81ba57c816e81c73ba76d0299116e940bf4777363a2cb7ed9f6424e821f3ba0910ba86c93db44c8f598e29cddbd1c3a059895b22f7b684e8bb4014b2a2048405405817c313ef1c95ae9ae05037506a508e4df6d012f90018145fa9577a605f9b9afbb3b'
This source diff could not be displayed because it is too large. You can view the blob instead.
import { BigNumber, ethers } from 'ethers'
import { sequencerBatch, add0x, BatchType } from '@eth-optimism/core-utils'
const compressBatchWithZlib = (calldata: string): string => {
const batch = sequencerBatch.decode(calldata)
batch.type = BatchType.ZLIB
const encoded = sequencerBatch.encode(batch)
return add0x(encoded)
}
/* Imports: Internal */
import { expect, readMockData } from '../../../../setup'
import { handleEventsSequencerBatchAppended } from '../../../../../src/services/l1-ingestion/handlers/sequencer-batch-appended'
import { SequencerBatchAppendedExtraData } from '../../../../../src/types'
describe('Event Handlers: CanonicalTransactionChain.SequencerBatchAppended', () => {
const mockData = readMockData()
describe('handleEventsSequencerBatchAppended.parseEvent', () => {
// This tests the behavior of parsing a real mainnet transaction,
// so it will break if the encoding scheme changes.
// Transaction and extra data from
// https://etherscan.io/tx/0x6effe006836b841205ace4d99d7ae1b74ee96aac499a3f358b97fccd32ee9af2
const exampleExtraData = {
timestamp: 1614862375,
blockNumber: 11969713,
submitter: '0xfd7d4de366850c08ee2cba32d851385a3071ec8d',
l1TransactionHash:
'0x6effe006836b841205ace4d99d7ae1b74ee96aac499a3f358b97fccd32ee9af2',
gasLimit: '548976',
prevTotalElements: BigNumber.from(73677),
batchIndex: BigNumber.from(743),
batchSize: BigNumber.from(101),
batchRoot:
'10B99425FB53AD7D40A939205C0F7B35CBB89AB4D67E7AE64BDAC5F1073943B4',
batchExtraData: '',
}
it('should error on malformed transaction data', async () => {
const input1: [any, SequencerBatchAppendedExtraData, number] = [
{
args: {
_startingQueueIndex: ethers.constants.Zero,
_numQueueElements: ethers.constants.Zero,
_totalElements: ethers.constants.Zero,
},
},
{
l1TransactionData: '0x00000',
...exampleExtraData,
},
0,
]
expect(() => {
handleEventsSequencerBatchAppended.parseEvent(...input1)
}).to.throw(
`Block ${input1[1].blockNumber} transaction data is too small: ${input1[1].l1TransactionData.length}`
)
})
describe('mainnet transactions', () => {
for (const mock of mockData) {
const { input, output } = mock
const { event, extraData, l2ChainId } = input
const hash = mock.input.extraData.l1TransactionHash
it(`uncompressed: ${hash}`, () => {
// Set the type to be legacy
output.transactionBatchEntry.type = BatchType[BatchType.LEGACY]
const res = handleEventsSequencerBatchAppended.parseEvent(
event,
extraData,
l2ChainId
)
// Check all of the transaction entries individually
for (const [i, got] of res.transactionEntries.entries()) {
const expected = output.transactionEntries[i]
expect(got).to.deep.eq(expected, `case ${i}`)
}
expect(res).to.deep.eq(output)
})
it(`compressed: ${hash}`, () => {
// Set the type to be zlib
output.transactionBatchEntry.type = BatchType[BatchType.ZLIB]
const compressed = compressBatchWithZlib(
input.extraData.l1TransactionData
)
const copy = { ...extraData }
copy.l1TransactionData = compressed
const res = handleEventsSequencerBatchAppended.parseEvent(
event,
copy,
l2ChainId
)
expect(res).to.deep.eq(output)
})
}
})
})
})
/* Imports: External */
import { BigNumber } from 'ethers'
/* Imports: Internal */
import { expect } from '../../../../setup'
import { handleEventsStateBatchAppended } from '../../../../../src/services/l1-ingestion/handlers/state-batch-appended'
import { StateBatchAppendedExtraData } from '../../../../../src/types'
import { l1StateBatchData } from '../../../examples/l1-data'
describe('Event Handlers: CanonicalTransactionChain.StateBatchAppended', () => {
describe('getExtraData', () => {
it('should return event block and transaction', async () => {
// Source: https://etherscan.io/tx/0x4ca72484e93cdb50fe1089984db152258c2bbffc2534dcafbfe032b596bd5b49
const l1Transaction = {
hash: '0x4ca72484e93cdb50fe1089984db152258c2bbffc2534dcafbfe032b596bd5b49',
from: '0xfd7d4de366850c08ee2cba32d851385a3071ec8d',
data: l1StateBatchData,
}
// Source: https://etherscan.io/block/12106615
const eventBlock = {
timestamp: 1616680530,
number: 12106615,
hash: '0x9c40310e19e943ad38e170329465c4489f6aba5895e9cacdac236be181aea31f',
parentHash:
'0xc7707a04c287a22ff4e43e5d9316e45ab342dcd405e7e0284eb51ce71a3a29ac',
miner: '0xea674fdde714fd979de3edf0f56aa9716b898ec8',
nonce: '0x40e6174f521a7cd8',
difficulty: 5990647962682594,
gasLimit: BigNumber.from(548976),
gasUsed: BigNumber.from(12495850),
extraData: '0x65746865726d696e652d6575726f70652d7765737433',
transactions: [l1Transaction.hash],
}
const input1: [any] = [
{
getBlock: () => eventBlock,
getTransaction: () => l1Transaction,
},
]
const output1 = await handleEventsStateBatchAppended.getExtraData(
...input1
)
expect(output1.timestamp).to.equal(eventBlock.timestamp)
expect(output1.blockNumber).to.equal(eventBlock.number)
expect(output1.submitter).to.equal(l1Transaction.from)
expect(output1.l1TransactionHash).to.equal(l1Transaction.hash)
expect(output1.l1TransactionData).to.equal(l1Transaction.data)
})
})
describe('parseEvent', () => {
it('should have a ctcIndex equal to null', () => {
// Source: https://etherscan.io/tx/0x4ca72484e93cdb50fe1089984db152258c2bbffc2534dcafbfe032b596bd5b49#eventlog
const event = {
args: {
_batchIndex: BigNumber.from(144),
_batchRoot:
'AD2039C6E9A8EE58817252CF16AB720BF3ED20CC4B53184F5B11DE09639AA123',
_batchSize: BigNumber.from(522),
_prevTotalElements: BigNumber.from(96000),
_extraData:
'00000000000000000000000000000000000000000000000000000000605C33E2000000000000000000000000FD7D4DE366850C08EE2CBA32D851385A3071EC8D',
},
}
const extraData: StateBatchAppendedExtraData = {
l1TransactionData: l1StateBatchData,
timestamp: 1616680530,
blockNumber: 12106615,
submitter: '0xfd7d4de366850c08ee2cba32d851385a3071ec8d',
l1TransactionHash:
'0x4ca72484e93cdb50fe1089984db152258c2bbffc2534dcafbfe032b596bd5b49',
}
const input1: [any, StateBatchAppendedExtraData, number] = [
event,
extraData,
0,
]
const output1 = handleEventsStateBatchAppended.parseEvent(...input1)
expect(output1.stateRootEntries.length).to.eq(
event.args._batchSize.toNumber()
)
output1.stateRootEntries.forEach((entry, i) => {
expect(entry.index).to.eq(
event.args._prevTotalElements.add(BigNumber.from(i)).toNumber()
)
expect(entry.batchIndex).to.eq(event.args._batchIndex.toNumber())
expect(entry.confirmed).to.be.true
})
const batchEntry = output1.stateRootBatchEntry
expect(batchEntry.index).to.eq(event.args._batchIndex.toNumber())
expect(batchEntry.blockNumber).to.eq(extraData.blockNumber)
expect(batchEntry.timestamp).to.eq(extraData.timestamp)
expect(batchEntry.submitter).to.eq(extraData.submitter)
expect(batchEntry.size).to.eq(event.args._batchSize.toNumber())
expect(batchEntry.root).to.eq(event.args._batchRoot)
expect(batchEntry.prevTotalElements).to.eq(
event.args._prevTotalElements.toNumber()
)
expect(batchEntry.extraData).to.eq(event.args._extraData)
expect(batchEntry.l1TransactionHash).to.eq(extraData.l1TransactionHash)
})
})
})
/* Imports: External */
import { ethers, BigNumber } from 'ethers'
/* Imports: Internal */
import { expect } from '../../../../setup'
import { handleEventsTransactionEnqueued } from '../../../../../src/services/l1-ingestion/handlers/transaction-enqueued'
const MAX_ITERATIONS = 128
describe('Event Handlers: CanonicalTransactionChain.TransactionEnqueued', () => {
describe('getExtraData', () => {
it('should return null', async () => {
const output1 = await handleEventsTransactionEnqueued.getExtraData()
const expected1 = null
expect(output1).to.equal(expected1)
})
})
describe('parseEvent', () => {
// TODO: Honestly this is the simplest `parseEvent` function we have and there isn't much logic
// to test. We could add a lot more tests that guarantee the correctness of the provided input,
// but it's probably better to get wider test coverage first.
it('should have a ctcIndex equal to null', () => {
const input1: [any, any, number] = [
{
blockNumber: 0,
args: {
_queueIndex: ethers.constants.Zero,
_gasLimit: ethers.constants.Zero,
_timestamp: ethers.constants.Zero,
},
},
null,
0,
]
const output1 = handleEventsTransactionEnqueued.parseEvent(...input1)
const expected1 = null
expect(output1).to.have.property('ctcIndex', expected1)
})
it('should have a blockNumber equal to the integer value of the blockNumber parameter', () => {
for (
let i = 0;
i < Number.MAX_SAFE_INTEGER;
i += Math.floor(Number.MAX_SAFE_INTEGER / MAX_ITERATIONS)
) {
const input1: [any, any, number] = [
{
blockNumber: i,
args: {
_queueIndex: ethers.constants.Zero,
_gasLimit: ethers.constants.Zero,
_timestamp: ethers.constants.Zero,
},
},
null,
0,
]
const output1 = handleEventsTransactionEnqueued.parseEvent(...input1)
const expected1 = BigNumber.from(i).toNumber()
expect(output1).to.have.property('blockNumber', expected1)
}
})
it('should have an index equal to the integer value of the _queueIndex argument', () => {
for (
let i = 0;
i < Number.MAX_SAFE_INTEGER;
i += Math.floor(Number.MAX_SAFE_INTEGER / MAX_ITERATIONS)
) {
const input1: [any, any, number] = [
{
blockNumber: 0,
args: {
_queueIndex: BigNumber.from(i),
_gasLimit: ethers.constants.Zero,
_timestamp: ethers.constants.Zero,
},
},
null,
0,
]
const output1 = handleEventsTransactionEnqueued.parseEvent(...input1)
const expected1 = BigNumber.from(i).toNumber()
expect(output1).to.have.property('index', expected1)
}
})
it('should have a gasLimit equal to the string value of the _gasLimit argument', () => {
for (
let i = 0;
i < Number.MAX_SAFE_INTEGER;
i += Math.floor(Number.MAX_SAFE_INTEGER / MAX_ITERATIONS)
) {
const input1: [any, any, number] = [
{
blockNumber: 0,
args: {
_queueIndex: ethers.constants.Zero,
_gasLimit: BigNumber.from(i),
_timestamp: ethers.constants.Zero,
},
},
null,
0,
]
const output1 = handleEventsTransactionEnqueued.parseEvent(...input1)
const expected1 = BigNumber.from(i).toString()
expect(output1).to.have.property('gasLimit', expected1)
}
})
it('should have a timestamp equal to the integer value of the _timestamp argument', () => {
for (
let i = 0;
i < Number.MAX_SAFE_INTEGER;
i += Math.floor(Number.MAX_SAFE_INTEGER / MAX_ITERATIONS)
) {
const input1: [any, any, number] = [
{
blockNumber: 0,
args: {
_queueIndex: ethers.constants.Zero,
_gasLimit: ethers.constants.Zero,
_timestamp: BigNumber.from(i),
},
},
null,
0,
]
const output1 = handleEventsTransactionEnqueued.parseEvent(...input1)
const expected1 = BigNumber.from(i).toNumber()
expect(output1).to.have.property('timestamp', expected1)
}
})
})
describe.skip('storeEvent', () => {
// TODO: I don't know the best way to test this, plus it's just a single line. Going to ignore
// it for now.
})
})
/* Imports: Internal */
import { expect } from '../../../../setup'
import { l2Block } from '../../../examples/l2-data'
import { handleSequencerBlock } from '../../../../../src/services/l2-ingestion/handlers/transaction'
describe('Handlers: handleSequencerBlock', () => {
describe('parseBlock', () => {
it('should correctly extract key fields from an L2 mainnet transaction', async () => {
const input1: [any, number] = [l2Block, 10]
const output1 = await handleSequencerBlock.parseBlock(...input1)
expect(output1.stateRootEntry.value).to.equal(l2Block.stateRoot)
expect(output1.transactionEntry.decoded.data).to.equal(
l2Block.transactions[0].input
)
})
})
})
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*"
]
}
ignores: [
"@babel/eslint-parser",
"@typescript-eslint/parser",
"eslint-plugin-import",
"eslint-plugin-unicorn",
"eslint-plugin-jsdoc",
"eslint-plugin-prefer-arrow",
"eslint-plugin-react",
"@typescript-eslint/eslint-plugin",
"eslint-config-prettier",
"eslint-plugin-prettier",
"chai"
]
# URL pointing to an L1 RPC provider
MESSAGE_RELAYER__L1_RPC_PROVIDER=
# URL pointing to an L2 RPC provider
MESSAGE_RELAYER__L2_RPC_PROVIDER=
# Private key for a wallet with ETH on L1
MESSAGE_RELAYER__L1_WALLET=
# Optional, L2 block height to start relaying messages from (default is 0)
MESSAGE_RELAYER__FROM_L2_TRANSACTION_INDEX=
module.exports = {
extends: '../../.eslintrc.js',
}
module.exports = {
...require('../../.prettierrc.js'),
};
\ No newline at end of file
# @eth-optimism/message-relayer
## 0.5.34
### Patch Changes
- Updated dependencies [a1b7ff9e3]
- Updated dependencies [8133872ed]
- Updated dependencies [afc2ab8c9]
- Updated dependencies [5063a69fb]
- Updated dependencies [aa854bdd8]
- @eth-optimism/sdk@2.1.0
## 0.5.33
### Patch Changes
- dbe5eb308: Empty patch release to re-release packages that failed to be released by a bug in the release process.
- Updated dependencies [be3315689]
- @eth-optimism/sdk@2.0.2
## 0.5.32
### Patch Changes
- Updated dependencies [fecd42d67]
- Updated dependencies [66cafc00a]
- @eth-optimism/common-ts@0.8.1
- @eth-optimism/sdk@2.0.1
## 0.5.31
### Patch Changes
- Updated dependencies [cb19e2f9c]
- @eth-optimism/sdk@2.0.0
## 0.5.30
### Patch Changes
- @eth-optimism/sdk@1.10.4
## 0.5.29
### Patch Changes
- @eth-optimism/sdk@1.10.3
## 0.5.28
### Patch Changes
- Updated dependencies [0e179781b]
- Updated dependencies [5372c9f5b]
- Updated dependencies [4ae94b412]
- @eth-optimism/common-ts@0.8.0
- @eth-optimism/sdk@1.10.2
## 0.5.27
### Patch Changes
- Updated dependencies [f04e5db2d]
- @eth-optimism/common-ts@0.7.1
- @eth-optimism/sdk@1.10.1
## 0.5.26
### Patch Changes
- Updated dependencies [3f4b3c328]
- @eth-optimism/sdk@1.10.0
## 0.5.25
### Patch Changes
- @eth-optimism/sdk@1.9.1
## 0.5.24
### Patch Changes
- Updated dependencies [e23f60f63]
- Updated dependencies [ab8ec365c]
- Updated dependencies [ba8b94a60]
- Updated dependencies [9b2891852]
- Updated dependencies [d1f9098f9]
- Updated dependencies [c6c9c7dbf]
- Updated dependencies [ffcee1013]
- Updated dependencies [eceb0de1d]
- @eth-optimism/common-ts@0.7.0
- @eth-optimism/sdk@1.9.0
## 0.5.23
### Patch Changes
- 1d3c749a2: Bumps the version of ts-node used
- Updated dependencies [767585b07]
- Updated dependencies [c975c9620]
- Updated dependencies [136ea1785]
- @eth-optimism/sdk@1.8.0
- @eth-optimism/core-utils@0.12.0
- @eth-optimism/common-ts@0.6.8
## 0.5.22
### Patch Changes
- Updated dependencies [1bfe79f20]
- @eth-optimism/sdk@1.7.0
## 0.5.21
### Patch Changes
- 97b5f578c: Fixes how versions are imported for BaseServiceV2 services
- @eth-optimism/sdk@1.6.11
## 0.5.20
### Patch Changes
- Updated dependencies [1e76cdb86]
- @eth-optimism/core-utils@0.11.0
- @eth-optimism/common-ts@0.6.7
- @eth-optimism/sdk@1.6.10
## 0.5.19
### Patch Changes
- @eth-optimism/sdk@1.6.9
## 0.5.18
### Patch Changes
- @eth-optimism/sdk@1.6.8
## 0.5.17
### Patch Changes
- Updated dependencies [b40913b1]
- Updated dependencies [a5e715c3]
- @eth-optimism/sdk@1.6.7
## 0.5.16
### Patch Changes
- Updated dependencies [ce7da914]
- @eth-optimism/common-ts@0.6.6
- @eth-optimism/sdk@1.6.6
## 0.5.15
### Patch Changes
- Updated dependencies [e2faaa8b]
- @eth-optimism/sdk@1.6.5
## 0.5.14
### Patch Changes
- 7215f4ce: Bump ethers to 5.7.0 globally
- 4f0cc4a4: Small patch release meant to test a fix for a bug in our canary release process for this package.
- Updated dependencies [7215f4ce]
- Updated dependencies [206f6033]
- Updated dependencies [d7679ca4]
- @eth-optimism/common-ts@0.6.5
- @eth-optimism/core-utils@0.10.1
- @eth-optimism/sdk@1.6.4
## 0.5.13
### Patch Changes
- @eth-optimism/sdk@1.6.3
## 0.5.12
### Patch Changes
- Updated dependencies [cfa81f88]
- @eth-optimism/sdk@1.6.2
## 0.5.11
### Patch Changes
- Updated dependencies [b27d0fa7]
- Updated dependencies [dbfea116]
- @eth-optimism/sdk@1.6.1
- @eth-optimism/core-utils@0.10.0
- @eth-optimism/common-ts@0.6.4
## 0.5.10
### Patch Changes
- Updated dependencies [3df66a9a]
- Updated dependencies [8323407f]
- Updated dependencies [3af9c7a9]
- Updated dependencies [aa2949ef]
- Updated dependencies [a1a73e64]
- Updated dependencies [f53c30b9]
- @eth-optimism/sdk@1.6.0
## 0.5.9
### Patch Changes
- Updated dependencies [dcd715a6]
- @eth-optimism/sdk@1.5.0
## 0.5.8
### Patch Changes
- Updated dependencies [0df744f6]
- Updated dependencies [8ae39154]
- Updated dependencies [f05ab6b6]
- Updated dependencies [dac4a9f0]
- @eth-optimism/core-utils@0.9.3
- @eth-optimism/sdk@1.4.0
- @eth-optimism/common-ts@0.6.3
## 0.5.7
### Patch Changes
- Updated dependencies [0bf3b9b4]
- Updated dependencies [680714c1]
- Updated dependencies [8d26459b]
- Updated dependencies [4477fe9f]
- Updated dependencies [29830750]
- @eth-optimism/core-utils@0.9.2
- @eth-optimism/sdk@1.3.1
- @eth-optimism/common-ts@0.6.2
## 0.5.6
### Patch Changes
- Updated dependencies [032f7214]
- @eth-optimism/sdk@1.3.0
## 0.5.5
### Patch Changes
- Updated dependencies [f9fee446]
- @eth-optimism/core-utils@0.9.1
- @eth-optimism/sdk@1.2.1
- @eth-optimism/common-ts@0.6.1
## 0.5.4
### Patch Changes
- 977493bc: Update SDK version and usage to account for new constructor
- Updated dependencies [977493bc]
- Updated dependencies [700dcbb0]
- Updated dependencies [3d1cb720]
- @eth-optimism/sdk@1.2.0
- @eth-optimism/core-utils@0.9.0
- @eth-optimism/common-ts@0.6.0
## 0.5.3
### Patch Changes
- Updated dependencies [cb71fcde]
- Updated dependencies [10e41522]
- @eth-optimism/common-ts@0.5.0
## 0.5.2
### Patch Changes
- 29ff7462: Revert es target back to 2017
- Updated dependencies [c201f3f1]
- Updated dependencies [29ff7462]
- Updated dependencies [52b26878]
- @eth-optimism/common-ts@0.4.0
- @eth-optimism/core-utils@0.8.7
- @eth-optimism/sdk@1.1.9
## 0.5.1
### Patch Changes
- Updated dependencies [9ba869a7]
- Updated dependencies [050859fd]
- @eth-optimism/common-ts@0.3.1
## 0.5.0
### Minor Changes
- 84a8934c: BaseServiceV2 exposes service name and version as standard synthetic metric
### Patch Changes
- Updated dependencies [d9e39931]
- Updated dependencies [84a8934c]
- @eth-optimism/common-ts@0.3.0
## 0.4.13
### Patch Changes
- Updated dependencies [9ecbf3e5]
- @eth-optimism/common-ts@0.2.10
- @eth-optimism/sdk@1.1.8
## 0.4.12
### Patch Changes
- Updated dependencies [17962ca9]
- @eth-optimism/core-utils@0.8.6
- @eth-optimism/common-ts@0.2.9
- @eth-optimism/sdk@1.1.7
## 0.4.11
### Patch Changes
- d18ae135: Updates all ethers versions in response to BN.js bug
- Updated dependencies [f16383f2]
- Updated dependencies [d18ae135]
- @eth-optimism/common-ts@0.2.8
- @eth-optimism/core-utils@0.8.5
- @eth-optimism/sdk@1.1.6
## 0.4.10
### Patch Changes
- Updated dependencies [86901552]
- @eth-optimism/sdk@1.1.5
## 0.4.9
### Patch Changes
- @eth-optimism/sdk@1.1.4
## 0.4.8
### Patch Changes
- 412688d5: Replace calls to getNetwork() with getChainId util
- @eth-optimism/sdk@1.1.3
## 0.4.7
### Patch Changes
- Updated dependencies [5cb3a5f7]
- Updated dependencies [6b9fc055]
- @eth-optimism/core-utils@0.8.4
- @eth-optimism/sdk@1.1.2
- @eth-optimism/common-ts@0.2.7
## 0.4.6
### Patch Changes
- Updated dependencies [1338135c]
- @eth-optimism/sdk@1.1.1
## 0.4.5
### Patch Changes
- Updated dependencies [a9f8e577]
- @eth-optimism/sdk@1.1.0
## 0.4.4
### Patch Changes
- b57014d1: Update to typescript@4.6.2
- Updated dependencies [b57014d1]
- @eth-optimism/common-ts@0.2.6
- @eth-optimism/core-utils@0.8.3
- @eth-optimism/sdk@1.0.4
## 0.4.3
### Patch Changes
- c1957126: Update Dockerfile to use Alpine
- d9a51154: Bump to hardhat@2.9.1
- Updated dependencies [e36b085c]
- Updated dependencies [c1957126]
- Updated dependencies [51673b90]
- Updated dependencies [7a179003]
- Updated dependencies [d9a51154]
- @eth-optimism/common-ts@0.2.5
- @eth-optimism/core-utils@0.8.2
- @eth-optimism/sdk@1.0.3
## 0.4.2
### Patch Changes
- Updated dependencies [f981b8da]
- @eth-optimism/common-ts@0.2.4
## 0.4.1
### Patch Changes
- 88601cb7: Refactored Dockerfiles
- 5cd1e996: Have BaseServiceV2 add spaces to environment variable names
- Updated dependencies [f7761058]
- Updated dependencies [5ae15042]
- Updated dependencies [5cd1e996]
- Updated dependencies [d49feca1]
- @eth-optimism/common-ts@0.2.3
- @eth-optimism/sdk@1.0.2
## 0.4.0
### Minor Changes
- 860fef46: Rewrites the message-relayer to use the BaseServiceV2.
### Patch Changes
- Updated dependencies [b3f9bdef]
- Updated dependencies [7ae1c67f]
- Updated dependencies [e53b5783]
- Updated dependencies [47e5d118]
- @eth-optimism/common-ts@0.2.2
- @eth-optimism/sdk@1.0.1
## 0.3.2
### Patch Changes
- Updated dependencies [42227d69]
- Updated dependencies [84f63c49]
- @eth-optimism/sdk@1.0.0
## 0.3.1
### Patch Changes
- Updated dependencies [b66e3131]
- Updated dependencies [5a6f539c]
- Updated dependencies [27d8942e]
- @eth-optimism/sdk@0.2.5
- @eth-optimism/core-utils@0.8.1
## 0.3.0
### Minor Changes
- dcdcc757: Removes message relaying utilities from the Message Relayer, to be replaced by the SDK
### Patch Changes
- 189f63be: Update message relayer to log sent tx hashes
- Updated dependencies [f37c283c]
- Updated dependencies [3f4d3c13]
- Updated dependencies [0b4453f7]
- Updated dependencies [0c54e60e]
- @eth-optimism/sdk@0.2.3
- @eth-optimism/core-utils@0.8.0
## 0.2.18
### Patch Changes
- 31108ed0: Fixes a bug that prevented the relayer from running correctly.
- Updated dependencies [b4165299]
- Updated dependencies [3c2acd91]
- @eth-optimism/core-utils@0.7.7
- @eth-optimism/contracts@0.5.14
## 0.2.17
### Patch Changes
- Updated dependencies [438bc78a]
- @eth-optimism/contracts@0.5.13
## 0.2.16
### Patch Changes
- 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.2.15
### Patch Changes
- Updated dependencies [e631c39c]
- @eth-optimism/contracts@0.5.11
## 0.2.14
### Patch Changes
- Updated dependencies [ad94b9d1]
- @eth-optimism/core-utils@0.7.5
- @eth-optimism/contracts@0.5.10
## 0.2.13
### Patch Changes
- Updated dependencies [ba96a455]
- Updated dependencies [c3e85fef]
- @eth-optimism/core-utils@0.7.4
- @eth-optimism/contracts@0.5.9
## 0.2.12
### Patch Changes
- e7c65ade: Fix docker build
- Updated dependencies [b3efb8b7]
- Updated dependencies [279603e5]
- Updated dependencies [b6040bb3]
- @eth-optimism/contracts@0.5.8
## 0.2.11
### Patch Changes
- 3a673322: Removed old node.js service running script
## 0.2.10
### Patch Changes
- Updated dependencies [b6f89fad]
- @eth-optimism/contracts@0.5.7
## 0.2.9
### Patch Changes
- Updated dependencies [bbd42e03]
- Updated dependencies [453f0774]
- @eth-optimism/contracts@0.5.6
## 0.2.8
### Patch Changes
- 57d5b8f9: Build docker images with node.js version 16
## 0.2.7
### Patch Changes
- 0ab37fc9: Update to node.js version 16
## 0.2.6
### Patch Changes
- 6b73a94a: Fix dockerfile
## 0.2.5
### Patch Changes
- Updated dependencies [584cbc25]
- @eth-optimism/core-utils@0.7.3
- @eth-optimism/contracts@0.5.5
## 0.2.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.2.3
### Patch Changes
- Updated dependencies [b9049406]
- Updated dependencies [a8b14a7d]
- @eth-optimism/contracts@0.5.3
## 0.2.2
### Patch Changes
- 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.2.1
### Patch Changes
- Updated dependencies [c0fc7fee]
- @eth-optimism/contracts@0.5.1
## 0.2.0
### Minor Changes
- 3f590e33: Remove the "OVM" Prefix from contract names
- 81ccd6e4: `regenesis/0.5.0` release
### Patch Changes
- 0a147680: Add legacy proof generation to message-relayer
- 222a3eef: Add 'User-Agent' to the http headers for ethers providers
- b70ee70c: upgraded to solidity 0.8.9
- a98a1884: Fixes dependencies instead of using caret constraints
- 8f4cb337: Removes the onlyRelayer modifier from the L1CrossDomainMessenger
- beb6c977: Remove obsoleted contract code. Improve events usability by indexing helpful params. Switch using encoded message and use decoded message from event
- 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.1.14
### 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.1.13
### Patch Changes
- e504bb47: added coverage for getMerkleTreeProof
- Updated dependencies [e0be02e1]
- Updated dependencies [7f7f35c3]
- Updated dependencies [8da04505]
- @eth-optimism/core-utils@0.6.0
- @eth-optimism/contracts@0.4.13
## 0.1.12
### Patch Changes
- 21b17edd: Added coverage for packages
- 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.1.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.1.10
### Patch Changes
- ecc2f8c1: Patch so contracts package will correctly use the browser-compatible contract artifacts import
- Updated dependencies [ecc2f8c1]
- @eth-optimism/contracts@0.4.9
## 0.1.9
### Patch Changes
- e4fea5e0: Use latest contracts package for browser compatibility support
- Updated dependencies [e4fea5e0]
- @eth-optimism/contracts@0.4.8
## 0.1.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.1.7
### Patch Changes
- fd86043a: Adds a new l2 to l1 message relaying utility within the message relayer package
- Updated dependencies [0313794b]
- Updated dependencies [049200f4]
- Updated dependencies [21e47e1f]
- @eth-optimism/contracts@0.4.2
- @eth-optimism/core-utils@0.5.0
## 0.1.6
### Patch Changes
- 735cd78f: Update relayer package JSON to correctly export all files in dist
- 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.1.5
### Patch Changes
- baa3b761: Improve Sentry support, initializing as needed and ensuring ERROR logs route to Sentry
- Updated dependencies [baa3b761]
- @eth-optimism/common-ts@0.1.3
## 0.1.4
### Patch Changes
- 9d39121b: Adds a README and cleans up the interface for generating messages and proofs
- 86708bb5: Adds a new set of tools for generating messages to be relayed and their proofs
- 064c03af: Removes spreadsheet mode from the message relayer
- 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.1.3
### Patch Changes
- e3b138b: Fix to avoid getting OOM killed when the relayer runs for a long period of time
- Updated dependencies [5e5d4a1]
- @eth-optimism/contracts@0.3.3
## 0.1.2
### Patch Changes
- 96a586e: Migrate bcfg interface to core-utils
- fa4898a: Explicitly log error messages so that they do not show as empty objects
- 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.1.1
### Patch Changes
- aedf931: Add updated config parsing in a backwards compatible way
- d723b2a: Don't log the config options at startup because it contains secrets
## 0.1.0
### Minor Changes
- b799caa: Updates to use RLP encoded transactions in batches for the `v0.3.0` release
### Patch Changes
- 33fcd84: Add a check for `OVM_L2MessageRelayer` in the AddressManager before attempting to relay messages to help surface errors more quickly
- 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.0.5
### Patch Changes
- 28dc442: move metrics, logger, and base-service to new common-ts package
- 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.0.4
### Patch Changes
- Updated dependencies [91460d9]
- Updated dependencies [a0a7956]
- Updated dependencies [0497d7d]
- @eth-optimism/core-utils@0.3.0
- @eth-optimism/contracts@0.2.5
## 0.0.3
### Patch Changes
- 3b00b7c: bump private package versions to try triggering a tag
## 0.0.2
### Patch Changes
- Updated dependencies [6cbc54d]
- @eth-optimism/core-utils@0.2.0
- @eth-optimism/contracts@0.2.2
(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.
# @eth-optimism/message-relayer
[![codecov](https://codecov.io/gh/ethereum-optimism/optimism/branch/develop/graph/badge.svg?token=0VTG7PG7YR&flag=message-relayer-tests)](https://codecov.io/gh/ethereum-optimism/optimism)
`message-relayer` is a service that automatically finalizes ("relays") messages sent from Optimism to Ethereum.
This package is meant to be used during local development and should NOT be used on a production network.
## Installation
Clone, install, and build the Optimism monorepo:
```
git clone https://github.com/ethereum-optimism/optimism.git
yarn install
yarn build
```
## Running the relayer (Docker)
The `message-relayer` can be included as part of the [local Optimism development environment](https://community.optimism.io/docs/developers/build/dev-node/).
Although the `message-relayer` is not turned on by default, it can be enabled by [changing this line in docker-compose.yml](https://github.com/ethereum-optimism/optimism/blob/51a527b8e3fe69940fb8c0f5e4aa2e0ae8ee294c/ops/docker-compose.yml#L129) to:
```
replicas: 1
```
## Running the relayer (manual)
The `message-relayer` can also be run manually.
Copy `.env.example` into a new file named `.env`, then set the environment variables listed there.
Once your environment variables have been set, run the relayer via:
```
yarn start
```
import { HardhatUserConfig } from 'hardhat/config'
import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
const config: HardhatUserConfig = {
paths: {
sources: './test/test-contracts',
},
solidity: {
version: '0.8.9',
settings: {
optimizer: { enabled: true, runs: 200 },
metadata: {
bytecodeHash: 'none',
},
outputSelection: {
'*': {
'*': ['metadata', 'storageLayout'],
},
},
},
},
}
export default config
{
"private": true,
"name": "@eth-optimism/message-relayer",
"version": "0.5.34",
"description": "[Optimism] Service for automatically relaying L2 to L1 transactions",
"main": "dist/index",
"types": "dist/index",
"files": [
"dist/*"
],
"scripts": {
"start": "ts-node ./src/service.ts",
"test:coverage": "echo 'No tests defined.'",
"build": "tsc -p ./tsconfig.json",
"clean": "rimraf dist/ ./tsconfig.tsbuildinfo",
"lint": "yarn lint:fix && yarn lint:check",
"pre-commit": "lint-staged",
"lint:fix": "yarn lint:check --fix",
"lint:check": "eslint . --max-warnings=0"
},
"keywords": [
"optimism",
"ethereum",
"relayer"
],
"homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/packages/message-relayer#readme",
"license": "MIT",
"author": "Optimism PBC",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@eth-optimism/common-ts": "0.8.1",
"@eth-optimism/core-utils": "0.12.0",
"@eth-optimism/sdk": "2.1.0",
"ethers": "^5.7.0"
},
"devDependencies": {
"@ethersproject/abstract-provider": "^5.7.0",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"ethereum-waffle": "^3.3.0",
"hardhat": "^2.9.6",
"ts-node": "^10.9.1"
}
}
/* Imports: External */
import { Signer } from 'ethers'
import { getChainId, sleep } from '@eth-optimism/core-utils'
import {
BaseServiceV2,
validators,
Gauge,
Counter,
} from '@eth-optimism/common-ts'
import { CrossChainMessenger, MessageStatus } from '@eth-optimism/sdk'
import { Provider } from '@ethersproject/abstract-provider'
import { version } from '../package.json'
type MessageRelayerOptions = {
l1RpcProvider: Provider
l2RpcProvider: Provider
l1Wallet: Signer
fromL2TransactionIndex?: number
}
type MessageRelayerMetrics = {
highestCheckedL2Tx: Gauge
highestKnownL2Tx: Gauge
numRelayedMessages: Counter
}
type MessageRelayerState = {
wallet: Signer
messenger: CrossChainMessenger
highestCheckedL2Tx: number
highestKnownL2Tx: number
}
export class MessageRelayerService extends BaseServiceV2<
MessageRelayerOptions,
MessageRelayerMetrics,
MessageRelayerState
> {
constructor(options?: Partial<MessageRelayerOptions>) {
super({
version,
name: 'message-relayer',
options,
optionsSpec: {
l1RpcProvider: {
validator: validators.provider,
desc: 'Provider for interacting with L1.',
},
l2RpcProvider: {
validator: validators.provider,
desc: 'Provider for interacting with L2.',
},
l1Wallet: {
validator: validators.wallet,
desc: 'Wallet used to interact with L1.',
},
fromL2TransactionIndex: {
validator: validators.num,
desc: 'Index of the first L2 transaction to start processing from.',
default: 0,
public: true,
},
},
metricsSpec: {
highestCheckedL2Tx: {
type: Gauge,
desc: 'Highest L2 tx that has been scanned for messages',
},
highestKnownL2Tx: {
type: Gauge,
desc: 'Highest known L2 transaction',
},
numRelayedMessages: {
type: Counter,
desc: 'Number of messages relayed by the service',
},
},
})
}
protected async init(): Promise<void> {
this.state.wallet = this.options.l1Wallet.connect(
this.options.l1RpcProvider
)
this.state.messenger = new CrossChainMessenger({
l1SignerOrProvider: this.state.wallet,
l2SignerOrProvider: this.options.l2RpcProvider,
l1ChainId: await getChainId(this.state.wallet.provider),
l2ChainId: await getChainId(this.options.l2RpcProvider),
})
this.state.highestCheckedL2Tx = this.options.fromL2TransactionIndex || 1
this.state.highestKnownL2Tx =
await this.state.messenger.l2Provider.getBlockNumber()
}
protected async main(): Promise<void> {
// Update metrics
this.metrics.highestCheckedL2Tx.set(this.state.highestCheckedL2Tx)
this.metrics.highestKnownL2Tx.set(this.state.highestKnownL2Tx)
// If we're already at the tip, then update the latest tip and loop again.
if (this.state.highestCheckedL2Tx > this.state.highestKnownL2Tx) {
this.state.highestKnownL2Tx =
await this.state.messenger.l2Provider.getBlockNumber()
// Sleeping for 1000ms is good enough since this is meant for development and not for live
// networks where we might want to restrict the number of requests per second.
await sleep(1000)
return
}
this.logger.info(`checking L2 block ${this.state.highestCheckedL2Tx}`)
const block =
await this.state.messenger.l2Provider.getBlockWithTransactions(
this.state.highestCheckedL2Tx
)
// Should never happen.
if (block.transactions.length !== 1) {
throw new Error(
`got an unexpected number of transactions in block: ${block.number}`
)
}
const messages = await this.state.messenger.getMessagesByTransaction(
block.transactions[0].hash
)
// No messages in this transaction so we can move on to the next one.
if (messages.length === 0) {
this.state.highestCheckedL2Tx++
return
}
// Make sure that all messages sent within the transaction are finalized. If any messages
// are not finalized, then we're going to break the loop which will trigger the sleep and
// wait for a few seconds before we check again to see if this transaction is finalized.
let isFinalized = true
for (const message of messages) {
const status = await this.state.messenger.getMessageStatus(message)
if (
status === MessageStatus.IN_CHALLENGE_PERIOD ||
status === MessageStatus.STATE_ROOT_NOT_PUBLISHED
) {
isFinalized = false
}
}
if (!isFinalized) {
this.logger.info(
`tx not yet finalized, waiting: ${this.state.highestCheckedL2Tx}`
)
return
} else {
this.logger.info(
`tx is finalized, relaying: ${this.state.highestCheckedL2Tx}`
)
}
// If we got here then all messages in the transaction are finalized. Now we can relay
// each message to L1.
for (const message of messages) {
try {
const tx = await this.state.messenger.finalizeMessage(message)
this.logger.info(`relayer sent tx: ${tx.hash}`)
this.metrics.numRelayedMessages.inc()
} catch (err) {
if (err.message.includes('message has already been received')) {
// It's fine, the message was relayed by someone else
} else {
throw err
}
}
await this.state.messenger.waitForMessageReceipt(message)
}
// All messages have been relayed so we can move on to the next block.
this.state.highestCheckedL2Tx++
}
}
if (require.main === module) {
const service = new MessageRelayerService()
service.run()
}
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"package.json",
"src/**/*"
]
}
......@@ -2325,11 +2325,6 @@
protobufjs "^7.0.0"
yargs "^16.2.0"
"@hapi/bourne@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.0.0.tgz#5bb2193eb685c0007540ca61d166d4e1edaf918d"
integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==
"@humanwhocodes/config-array@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
......@@ -3894,7 +3889,7 @@
"@sentry/utils" "5.30.0"
tslib "^1.9.3"
"@sentry/tracing@6.11.0", "@sentry/tracing@^6.3.1":
"@sentry/tracing@6.11.0":
version "6.11.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.11.0.tgz#9bd9287addea1ebc12c75b226f71c7713c0fac4f"
integrity sha512-9VA1/SY++WeoMQI4K6n/sYgIdRtCu9NLWqmGqu/5kbOtESYFgAt1DqSyqGCr00ZjQiC2s7tkDkTNZb38K6KytQ==
......@@ -4341,24 +4336,11 @@
dependencies:
"@types/node" "*"
"@types/cors@^2.8.9":
version "2.8.12"
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==
"@types/dateformat@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-5.0.0.tgz#17ce64b0318f3f36d1c830c58a7a915445f1f93d"
integrity sha512-SZg4JdHIWHQGEokbYGZSDvo5wA4TLYPXaqhigs/wH+REDOejcJzgH+qyY+HtEUtWOZxEUkbhbdYPqQDiEgrXeA==
"@types/encoding-down@*":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/encoding-down/-/encoding-down-5.0.1.tgz#5527b8656395b274bf0100a312c1c620a7880a24"
integrity sha512-1O8CyIC67hMR+VuxLtjlJvXwNn5cNnJ7YiaBR4p+ONGijlmIZjbXzsPuJm146p9eQZwzZRGgmRwWtCqXs5wwSw==
dependencies:
"@types/abstract-leveldown" "*"
"@types/level-codec" "*"
"@types/express-serve-static-core@^4.17.18":
version "4.17.24"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07"
......@@ -4368,7 +4350,7 @@
"@types/qs" "*"
"@types/range-parser" "*"
"@types/express@^4.17.12", "@types/express@^4.17.13":
"@types/express@^4.17.13":
version "4.17.13"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034"
integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
......@@ -4410,34 +4392,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/level-codec@*":
version "9.0.1"
resolved "https://registry.yarnpkg.com/@types/level-codec/-/level-codec-9.0.1.tgz#b135e0b3da81185b6f27655334c5f1da89b2b0af"
integrity sha512-6z7DSlBsmbax3I/bV1Q6jT1nKquDjFl95LURVThdKtwILkRawLYtXdINW19xM95N5kqN2detWb2iGrbUlPwNyw==
"@types/level-errors@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/level-errors/-/level-errors-3.0.0.tgz#15c1f4915a5ef763b51651b15e90f6dc081b96a8"
integrity sha512-/lMtoq/Cf/2DVOm6zE6ORyOM+3ZVm/BvzEZVxUhf6bgh8ZHglXlBqxbxSlJeVp8FCbD3IVvk/VbsaNmDjrQvqQ==
"@types/level@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@types/level/-/level-6.0.1.tgz#6642c048dbae4638721105cd5f8255a2829bd5d6"
integrity sha512-4RK6vl9/CNCe5SyDCGaTgTOYEPNh8/wUaUY8UDN8AXon2D7hGPGIlx0t9jCds9wiKBqicsUyeAA0W4rUZeqYBQ==
dependencies:
"@types/abstract-leveldown" "*"
"@types/encoding-down" "*"
"@types/levelup" "*"
"@types/levelup@*":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@types/levelup/-/levelup-5.1.2.tgz#3b987fe66c95871dc97e74f0bce5b2eb91140782"
integrity sha512-JhCKONvFg2rEbsyyCCRkiPF03tMV1NyBXER4iXKBemgjwS4+SF6HGNuL4cfq6ueM6Yc+4ZbwJnU/5v6q3ZziUQ==
dependencies:
"@types/abstract-leveldown" "*"
"@types/level-errors" "*"
"@types/node" "*"
"@types/levelup@^4.3.0":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@types/levelup/-/levelup-4.3.3.tgz#4dc2b77db079b1cf855562ad52321aa4241b8ef4"
......@@ -5476,7 +5435,7 @@ abstract-leveldown@~2.6.0:
dependencies:
xtend "~4.0.0"
abstract-leveldown@~6.2.1, abstract-leveldown@~6.2.3:
abstract-leveldown@~6.2.1:
version "6.2.3"
resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz#036543d87e3710f2528e47040bc3261b77a9a8eb"
integrity sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==
......@@ -5794,16 +5753,6 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
args@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/args/-/args-5.0.1.tgz#4bf298df90a4799a09521362c579278cc2fdd761"
integrity sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==
dependencies:
camelcase "5.0.0"
chalk "2.4.2"
leven "2.1.0"
mri "1.1.4"
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
......@@ -6710,13 +6659,6 @@ basic-auth@~2.0.1:
dependencies:
safe-buffer "5.1.2"
bcfg@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/bcfg/-/bcfg-0.1.6.tgz#f77a6323bddef14f3886222e7ef8ccc0bc2143ec"
integrity sha512-BR2vwQZwu24aRm588XHOnPVjjQtbK8sF0RopRFgMuke63/REJMWnePTa2YHKDBefuBYiVdgkowuB1/e4K7Ue3g==
dependencies:
bsert "~0.0.10"
bcfg@^0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/bcfg/-/bcfg-0.1.7.tgz#610198a67a56160305fdc1f54b5b5c90b52530d7"
......@@ -6748,16 +6690,6 @@ better-path-resolve@1.0.0:
dependencies:
is-windows "^1.0.0"
bfj@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2"
integrity sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==
dependencies:
bluebird "^3.5.5"
check-types "^11.1.1"
hoopy "^0.1.4"
tryer "^1.0.1"
bigint-buffer@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442"
......@@ -6836,7 +6768,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.5.0, bluebird@^3.5.2, bluebird@^3.5.3:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
......@@ -7334,11 +7266,6 @@ camelcase-keys@^6.2.2:
map-obj "^4.0.0"
quick-lru "^4.0.1"
camelcase@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
......@@ -7449,15 +7376,6 @@ chai@^4.3.7:
pathval "^1.1.1"
type-detect "^4.0.5"
chalk@2.4.2, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
......@@ -7469,6 +7387,15 @@ chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
......@@ -7530,11 +7457,6 @@ check-error@^1.0.2:
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
check-types@^11.1.1:
version "11.1.2"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f"
integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==
checkpoint-store@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/checkpoint-store/-/checkpoint-store-1.1.0.tgz#04e4cb516b91433893581e6d4601a78e9552ea06"
......@@ -8213,7 +8135,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cors@^2.8.1, cors@^2.8.5:
cors@^2.8.1:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
......@@ -10315,7 +10237,7 @@ ethereum-waffle@^3.0.0, ethereum-waffle@^3.4.4:
"@ethereum-waffle/provider" "^3.4.4"
ethers "^5.0.1"
ethereum-waffle@^3.3.0, ethereum-waffle@^3.4.0:
ethereum-waffle@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/ethereum-waffle/-/ethereum-waffle-3.4.0.tgz#990b3c6c26db9c2dd943bf26750a496f60c04720"
integrity sha512-ADBqZCkoSA5Isk486ntKJVjFEawIiC+3HxNqpJqONvh3YXBTNiRfXvJtGuAFLXPG91QaqkGqILEHANAo7j/olQ==
......@@ -10838,7 +10760,7 @@ expand-template@^2.0.3:
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
express-prom-bundle@^6.3.6, express-prom-bundle@^6.4.1:
express-prom-bundle@^6.4.1:
version "6.4.1"
resolved "https://registry.yarnpkg.com/express-prom-bundle/-/express-prom-bundle-6.4.1.tgz#a688050b9e090f6969825c33143106d3e0e5a70e"
integrity sha512-Sg0svLQe/SS5z1tHDTVfZVjNumobiDlXM0jmemt5Dm9K6BX8z9yCwEr93zbko6fNMR4zKav77iPfxUWi6gAjNA==
......@@ -12359,11 +12281,6 @@ home-or-tmp@^2.0.0:
os-homedir "^1.0.0"
os-tmpdir "^1.0.1"
hoopy@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==
hosted-git-info@^2.1.4, hosted-git-info@^2.6.0:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
......@@ -13414,16 +13331,6 @@ jayson@^3.4.4:
uuid "^8.3.2"
ws "^7.4.5"
jmespath@^0.15.0:
version "0.15.0"
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
joycon@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/joycon/-/joycon-2.2.5.tgz#8d4cf4cbb2544d7b7583c216fcdfec19f6be1615"
integrity sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ==
joycon@^3.0.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
......@@ -13933,16 +13840,6 @@ level-iterator-stream@~4.0.0:
readable-stream "^3.4.0"
xtend "^4.0.2"
level-js@^5.0.0:
version "5.0.2"
resolved "https://registry.yarnpkg.com/level-js/-/level-js-5.0.2.tgz#5e280b8f93abd9ef3a305b13faf0b5397c969b55"
integrity sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==
dependencies:
abstract-leveldown "~6.2.3"
buffer "^5.5.0"
inherits "^2.0.3"
ltgt "^2.1.2"
level-mem@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/level-mem/-/level-mem-3.0.1.tgz#7ce8cf256eac40f716eb6489654726247f5a89e5"
......@@ -13959,7 +13856,7 @@ level-mem@^5.0.1:
level-packager "^5.0.3"
memdown "^5.0.0"
level-packager@^5.0.3, level-packager@^5.1.0:
level-packager@^5.0.3:
version "5.1.1"
resolved "https://registry.yarnpkg.com/level-packager/-/level-packager-5.1.1.tgz#323ec842d6babe7336f70299c14df2e329c18939"
integrity sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==
......@@ -14031,24 +13928,6 @@ level-ws@^2.0.0:
readable-stream "^3.1.0"
xtend "^4.0.1"
"level6@npm:level@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/level/-/level-6.0.1.tgz#dc34c5edb81846a6de5079eac15706334b0d7cd6"
integrity sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==
dependencies:
level-js "^5.0.0"
level-packager "^5.1.0"
leveldown "^5.4.0"
leveldown@^5.4.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/leveldown/-/leveldown-5.6.0.tgz#16ba937bb2991c6094e13ac5a6898ee66d3eee98"
integrity sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==
dependencies:
abstract-leveldown "~6.2.1"
napi-macros "~2.0.0"
node-gyp-build "~4.1.0"
levelup@3.1.1, levelup@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/levelup/-/levelup-3.1.1.tgz#c2c0b3be2b4dc316647c53b42e2f559e232d2189"
......@@ -14072,7 +13951,7 @@ levelup@^1.2.1:
semver "~5.4.1"
xtend "~4.0.0"
levelup@^4.3.2, levelup@^4.4.0:
levelup@^4.3.2:
version "4.4.0"
resolved "https://registry.yarnpkg.com/levelup/-/levelup-4.4.0.tgz#f89da3a228c38deb49c48f88a70fb71f01cafed6"
integrity sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==
......@@ -14083,11 +13962,6 @@ levelup@^4.3.2, levelup@^4.4.0:
level-supports "~1.0.0"
xtend "~4.0.0"
leven@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA=
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
......@@ -15560,11 +15434,6 @@ motion@10.15.5:
"@motionone/utils" "^10.15.1"
"@motionone/vue" "^10.15.5"
mri@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a"
integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
......@@ -15731,11 +15600,6 @@ napi-build-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
napi-macros@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==
natural-compare-lite@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4"
......@@ -15852,11 +15716,6 @@ node-gyp-build@^4.2.0:
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==
node-gyp-build@~4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.1.1.tgz#d7270b5d86717068d114cc57fff352f96d745feb"
integrity sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==
node-gyp@^5.0.2:
version "5.1.1"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e"
......@@ -17020,24 +16879,6 @@ pino-multi-stream@^5.3.0:
dependencies:
pino "^6.0.0"
pino-pretty@^4.7.1:
version "4.8.0"
resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-4.8.0.tgz#f2f3055bf222456217b14ffb04d8be0a0cc17fce"
integrity sha512-mhQfHG4rw5ZFpWL44m0Utjo4GC2+HMfdNvxyA8lLw0sIqn6fCf7uQe6dPckUcW/obly+OQHD7B/MTso6LNizYw==
dependencies:
"@hapi/bourne" "^2.0.0"
args "^5.0.1"
chalk "^4.0.0"
dateformat "^4.5.1"
fast-safe-stringify "^2.0.7"
jmespath "^0.15.0"
joycon "^2.2.5"
pump "^3.0.0"
readable-stream "^3.6.0"
rfdc "^1.3.0"
split2 "^3.1.1"
strip-json-comments "^3.1.1"
pino-sentry@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/pino-sentry/-/pino-sentry-0.7.0.tgz#087717d2787ec437627e97454238ca7ce0562a91"
......@@ -18455,11 +18296,6 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rfdc@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
rimraf@2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
......@@ -20381,11 +20217,6 @@ trough@^1.0.0:
resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf"
integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==
tryer@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
ts-command-line-args@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.3.1.tgz#b6188e42efc6cf7a8898e438a873fbb15505ddd6"
......
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