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

Merge branch 'develop' into protocol-versions-contract

parents fc87cd14 0bf53e73
---
'@eth-optimism/sdk': patch
---
Adds Sepolia & OP Sepolia support to SDK
---
'@eth-optimism/contracts-bedrock': patch
---
bumps sdk version to have access to sepolia deployments
......@@ -887,6 +887,14 @@ jobs:
name: Build
command: make indexer
working_directory: indexer
- run:
name: Install tygo
command: go install github.com/gzuidhof/tygo@latest
working_directory: indexer/api-ts
- run:
name: Check generated code
command: npm run generate && git diff --exit-code
working_directory: indexer/api-ts
devnet:
machine:
......
......@@ -56,7 +56,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -83,7 +83,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -110,7 +110,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -137,7 +137,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -164,7 +164,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -191,7 +191,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -229,7 +229,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......
......@@ -81,7 +81,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -118,7 +118,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -145,7 +145,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -172,7 +172,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -199,7 +199,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -226,7 +226,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......@@ -253,7 +253,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
......
......@@ -145,7 +145,6 @@ clean-node-modules:
rm -rf node_modules
rm -rf packages/**/node_modules
tag-bedrock-go-modules:
./ops/scripts/tag-bedrock-go-modules.sh $(BEDROCK_TAGS_REMOTE) $(VERSION)
.PHONY: tag-bedrock-go-modules
......
package mipsevm
import (
"bytes"
"compress/zlib"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"sync"
"github.com/ethereum/go-ethereum/crypto"
)
var zlibWriterPool = sync.Pool{
New: func() any {
var buf bytes.Buffer
return zlib.NewWriter(&buf)
},
}
type Page [PageSize]byte
func (p *Page) MarshalText() ([]byte, error) {
dst := make([]byte, hex.EncodedLen(len(p)))
hex.Encode(dst, p[:])
return dst, nil
func (p *Page) MarshalJSON() ([]byte, error) { // nosemgrep
var out bytes.Buffer
w := zlibWriterPool.Get().(*zlib.Writer)
defer zlibWriterPool.Put(w)
w.Reset(&out)
if _, err := w.Write(p[:]); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
return json.Marshal(out.Bytes())
}
func (p *Page) UnmarshalJSON(dat []byte) error {
// Strip off the `"` characters at the start & end.
dat = dat[1 : len(dat)-1]
// Decode b64 then decompress
r, err := zlib.NewReader(base64.NewDecoder(base64.StdEncoding, bytes.NewReader(dat)))
if err != nil {
return err
}
defer r.Close()
if n, err := r.Read(p[:]); n != PageSize {
return fmt.Errorf("epxeted %d bytes, but got %d", PageSize, n)
} else if err == io.EOF {
return nil
} else {
return err
}
}
func (p *Page) UnmarshalText(dat []byte) error {
......
......@@ -29,7 +29,8 @@ determine the root claim to use when creating the game. In simple cases, where t
arbitrary hash can be used for claim values. For more advanced cases [cannon can be used](./cannon.md) to generate a
trace, including the claim values to use at specific steps. Note that it is not valid to create a game that disputes an
output root, using the final hash from a trace that confirms the output root is valid. To dispute an output root
successfully, the trace must resolve that the disputed output root is invalid.
successfully, the trace must resolve that the disputed output root is invalid. This is indicated by the first byte of
the claim value being set to the invalid [VM status](../../specs/cannon-fault-proof-vm.md#state-hash) (`0x01`).
The game can then be created by calling the `create` method on the `DisputeGameFactory` contract. This requires three
parameters:
......
MIT License
Copyright (c) 2022 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.
Generated typescript types for https://github.com/ethereum-optimism/optimism/tree/develop/indexer
// Code generated by tygo. DO NOT EDIT.
//////////
// source: deposits.go
export interface DepositItem {
guid: string;
from: string;
to: string;
timestamp: number /* uint64 */;
L1TxHash: string;
L2TxHash: string;
Block: string;
amount: string;
l1Token: string;
l2Token: string;
}
export interface DepositResponse {
cursor: string;
hasNextPage: boolean;
items: DepositItem[];
}
//////////
// source: routes.go
export interface Routes {
Logger: any /* log.Logger */;
BridgeTransfersView: any /* database.BridgeTransfersView */;
Router?: any /* chi.Mux */;
}
//////////
// source: withdrawals.go
export interface WithdrawalItem {
guid: string;
from: string;
to: string;
transactionHash: string;
timestamp: number /* uint64 */;
l2BlockHash: string;
amount: string;
proof: string;
claim: string;
l1Token: string;
l2Token: string;
}
export interface WithdrawalResponse {
cursor: string;
hasNextPage: boolean;
items: WithdrawalItem[];
}
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// indexer.ts
var indexer_exports = {};
__export(indexer_exports, {
depositEndpoint: () => depositEndpoint,
withdrawalEndoint: () => withdrawalEndoint
});
module.exports = __toCommonJS(indexer_exports);
var createQueryString = ({ cursor, limit }) => {
if (cursor === void 0 && limit === void 0) {
return "";
}
const queries = [];
if (cursor) {
queries.push(`cursor=${cursor}`);
}
if (limit) {
queries.push(`limit=${limit}`);
}
return `?${queries.join("&")}`;
};
var depositEndpoint = ({ baseUrl = "", address, cursor, limit }) => {
return [baseUrl, "deposits", address, createQueryString({ cursor, limit })].join("/");
};
var withdrawalEndoint = ({ baseUrl = "", address, cursor, limit }) => {
return [baseUrl, "withdrawals", address, createQueryString({ cursor, limit })].join("/");
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
depositEndpoint,
withdrawalEndoint
});
//# sourceMappingURL=indexer.cjs.map
\ No newline at end of file
{"version":3,"sources":["indexer.ts"],"sourcesContent":["export * from './generated'\n\ntype PaginationOptions = {\n limit?: number\n cursor?: string\n}\n\ntype Options = {\n baseUrl?: string\n address: `0x${string}`\n} & PaginationOptions\n\nconst createQueryString = ({ cursor, limit }: PaginationOptions): string => {\n if (cursor === undefined && limit === undefined) {\n return ''\n }\n const queries: string[] = []\n if (cursor) {\n queries.push(`cursor=${cursor}`)\n }\n if (limit) {\n queries.push(`limit=${limit}`)\n }\n return `?${queries.join('&')}`\n}\n\nexport const depositEndpoint = ({ baseUrl = '', address, cursor, limit }: Options): string => {\n return [baseUrl, 'deposits', address, createQueryString({ cursor, limit })].join('/')\n}\n\nexport const withdrawalEndoint = ({ baseUrl = '', address, cursor, limit }: Options): string => {\n return [baseUrl, 'withdrawals', address, createQueryString({ cursor, limit })].join('/')\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA,IAAM,oBAAoB,CAAC,EAAE,QAAQ,MAAM,MAAiC;AAC1E,MAAI,WAAW,UAAa,UAAU,QAAW;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,UAAoB,CAAC;AAC3B,MAAI,QAAQ;AACV,YAAQ,KAAK,UAAU,MAAM,EAAE;AAAA,EACjC;AACA,MAAI,OAAO;AACT,YAAQ,KAAK,SAAS,KAAK,EAAE;AAAA,EAC/B;AACA,SAAO,IAAI,QAAQ,KAAK,GAAG,CAAC;AAC9B;AAEO,IAAM,kBAAkB,CAAC,EAAE,UAAU,IAAI,SAAS,QAAQ,MAAM,MAAuB;AAC5F,SAAO,CAAC,SAAS,YAAY,SAAS,kBAAkB,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,KAAK,GAAG;AACtF;AAEO,IAAM,oBAAoB,CAAC,EAAE,UAAU,IAAI,SAAS,QAAQ,MAAM,MAAuB;AAC9F,SAAO,CAAC,SAAS,eAAe,SAAS,kBAAkB,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,KAAK,GAAG;AACzF;","names":[]}
\ No newline at end of file
// indexer.ts
var createQueryString = ({ cursor, limit }) => {
if (cursor === void 0 && limit === void 0) {
return "";
}
const queries = [];
if (cursor) {
queries.push(`cursor=${cursor}`);
}
if (limit) {
queries.push(`limit=${limit}`);
}
return `?${queries.join("&")}`;
};
var depositEndpoint = ({ baseUrl = "", address, cursor, limit }) => {
return [baseUrl, "deposits", address, createQueryString({ cursor, limit })].join("/");
};
var withdrawalEndoint = ({ baseUrl = "", address, cursor, limit }) => {
return [baseUrl, "withdrawals", address, createQueryString({ cursor, limit })].join("/");
};
export {
depositEndpoint,
withdrawalEndoint
};
//# sourceMappingURL=indexer.js.map
\ No newline at end of file
{"version":3,"sources":["indexer.ts"],"sourcesContent":["export * from './generated'\n\ntype PaginationOptions = {\n limit?: number\n cursor?: string\n}\n\ntype Options = {\n baseUrl?: string\n address: `0x${string}`\n} & PaginationOptions\n\nconst createQueryString = ({ cursor, limit }: PaginationOptions): string => {\n if (cursor === undefined && limit === undefined) {\n return ''\n }\n const queries: string[] = []\n if (cursor) {\n queries.push(`cursor=${cursor}`)\n }\n if (limit) {\n queries.push(`limit=${limit}`)\n }\n return `?${queries.join('&')}`\n}\n\nexport const depositEndpoint = ({ baseUrl = '', address, cursor, limit }: Options): string => {\n return [baseUrl, 'deposits', address, createQueryString({ cursor, limit })].join('/')\n}\n\nexport const withdrawalEndoint = ({ baseUrl = '', address, cursor, limit }: Options): string => {\n return [baseUrl, 'withdrawals', address, createQueryString({ cursor, limit })].join('/')\n}\n\n"],"mappings":";AAYA,IAAM,oBAAoB,CAAC,EAAE,QAAQ,MAAM,MAAiC;AAC1E,MAAI,WAAW,UAAa,UAAU,QAAW;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,UAAoB,CAAC;AAC3B,MAAI,QAAQ;AACV,YAAQ,KAAK,UAAU,MAAM,EAAE;AAAA,EACjC;AACA,MAAI,OAAO;AACT,YAAQ,KAAK,SAAS,KAAK,EAAE;AAAA,EAC/B;AACA,SAAO,IAAI,QAAQ,KAAK,GAAG,CAAC;AAC9B;AAEO,IAAM,kBAAkB,CAAC,EAAE,UAAU,IAAI,SAAS,QAAQ,MAAM,MAAuB;AAC5F,SAAO,CAAC,SAAS,YAAY,SAAS,kBAAkB,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,KAAK,GAAG;AACtF;AAEO,IAAM,oBAAoB,CAAC,EAAE,UAAU,IAAI,SAAS,QAAQ,MAAM,MAAuB;AAC9F,SAAO,CAAC,SAAS,eAAe,SAAS,kBAAkB,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,KAAK,GAAG;AACzF;","names":[]}
\ No newline at end of file
export * from './generated'
type PaginationOptions = {
limit?: number
cursor?: string
}
type Options = {
baseUrl?: string
address: `0x${string}`
} & PaginationOptions
const createQueryString = ({ cursor, limit }: PaginationOptions): string => {
if (cursor === undefined && limit === undefined) {
return ''
}
const queries: string[] = []
if (cursor) {
queries.push(`cursor=${cursor}`)
}
if (limit) {
queries.push(`limit=${limit}`)
}
return `?${queries.join('&')}`
}
export const depositEndpoint = ({ baseUrl = '', address, cursor, limit }: Options): string => {
return [baseUrl, 'deposits', address, createQueryString({ cursor, limit })].join('/')
}
export const withdrawalEndoint = ({ baseUrl = '', address, cursor, limit }: Options): string => {
return [baseUrl, 'withdrawals', address, createQueryString({ cursor, limit })].join('/')
}
{
"name": "@eth-optimism/indexer-api",
"version": "0.0.1",
"description": "[Optimism] typescript types for the indexer service",
"main": "indexer.cjs",
"module": "indexer.js",
"types": "indexer.ts",
"type": "module",
"files": [
"*.ts",
"*.ts",
"*.js",
"*.js.map",
"*.cjs",
"*.cjs.map",
"LICENSE"
],
"scripts": {
"clean": "rm -rf generated.ts indexer.cjs indexer.js",
"generate": "npm run clean && tygo generate && mv ../api/routes/index.ts generated.ts && npx tsup"
},
"keywords": [
"optimism",
"ethereum",
"indexer"
],
"homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/indexer#readme",
"license": "MIT",
"author": "Optimism PBC",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git"
}}
{
"compilerOptions": {
"outDir": "dist",
"strict": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react",
"target": "ESNext",
"noEmit": true
},
"include": ["."]
}
import packageJson from './package.json'
export default {
name: packageJson.name,
entry: ['indexer.ts'],
outDir: '.',
format: ['esm', 'cjs'],
splitting: false,
sourcemap: true,
clean: false,
}
packages:
- path: "github.com/ethereum-optimism/optimism/indexer/api/routes"
package api
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/ethereum-optimism/optimism/indexer/api/routes"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// MockBridgeTransfersView mocks the BridgeTransfersView interface
......@@ -70,6 +73,8 @@ func (mbv *MockBridgeTransfersView) L1BridgeDepositsByAddress(address common.Add
{
L1BridgeDeposit: deposit,
L1TransactionHash: common.HexToHash("0x123"),
L2TransactionHash: common.HexToHash("0x555"),
L1BlockHash: common.HexToHash("0x456"),
},
},
}, nil
......@@ -79,8 +84,11 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.
return &database.L2BridgeWithdrawalsResponse{
Withdrawals: []database.L2BridgeWithdrawalWithTransactionHashes{
{
L2BridgeWithdrawal: withdrawal,
L2TransactionHash: common.HexToHash("0x789"),
L2BridgeWithdrawal: withdrawal,
L2TransactionHash: common.HexToHash("0x789"),
L2BlockHash: common.HexToHash("0x456"),
ProvenL1TransactionHash: common.HexToHash("0x123"),
FinalizedL1TransactionHash: common.HexToHash("0x123"),
},
},
}, nil
......@@ -107,6 +115,17 @@ func TestL1BridgeDepositsHandler(t *testing.T) {
api.Router.ServeHTTP(responseRecorder, request)
assert.Equal(t, http.StatusOK, responseRecorder.Code)
var resp routes.DepositResponse
err = json.Unmarshal(responseRecorder.Body.Bytes(), &resp)
assert.Nil(t, err)
require.Len(t, resp.Items, 1)
assert.Equal(t, resp.Items[0].L1BlockHash, common.HexToHash("0x456").String())
assert.Equal(t, resp.Items[0].L1TxHash, common.HexToHash("0x123").String())
assert.Equal(t, resp.Items[0].Timestamp, deposit.Tx.Timestamp)
assert.Equal(t, resp.Items[0].L2TxHash, common.HexToHash("555").String())
}
func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) {
......@@ -118,5 +137,22 @@ func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) {
responseRecorder := httptest.NewRecorder()
api.Router.ServeHTTP(responseRecorder, request)
assert.Equal(t, http.StatusOK, responseRecorder.Code)
var resp routes.WithdrawalResponse
err = json.Unmarshal(responseRecorder.Body.Bytes(), &resp)
assert.Nil(t, err)
require.Len(t, resp.Items, 1)
assert.Equal(t, resp.Items[0].Guid, withdrawal.TransactionWithdrawalHash.String())
assert.Equal(t, resp.Items[0].L2BlockHash, common.HexToHash("0x456").String())
assert.Equal(t, resp.Items[0].From, withdrawal.Tx.FromAddress.String())
assert.Equal(t, resp.Items[0].To, withdrawal.Tx.ToAddress.String())
assert.Equal(t, resp.Items[0].TransactionHash, common.HexToHash("0x789").String())
assert.Equal(t, resp.Items[0].Amount, withdrawal.Tx.Amount.String())
assert.Equal(t, resp.Items[0].ProofTransactionHash, common.HexToHash("0x123").String())
assert.Equal(t, resp.Items[0].ClaimTransactionHash, common.HexToHash("0x123").String())
assert.Equal(t, resp.Items[0].L1TokenAddress, withdrawal.TokenPair.RemoteTokenAddress.String())
assert.Equal(t, resp.Items[0].L2TokenAddress, withdrawal.TokenPair.LocalTokenAddress.String())
assert.Equal(t, resp.Items[0].Timestamp, withdrawal.Tx.Timestamp)
}
......@@ -7,32 +7,6 @@ import (
"github.com/ethereum/go-ethereum/log"
)
// lazily typing numbers fixme
type Transaction struct {
Timestamp uint64 `json:"timestamp"`
TransactionHash string `json:"transactionHash"`
}
type Block struct {
BlockNumber int64 `json:"number"`
BlockHash string `json:"hash"`
// ParentBlockHash string `json:"parentHash"`
}
type Extensions struct {
OptimismBridgeAddress string `json:"OptimismBridgeAddress"`
}
type TokenInfo struct {
// TODO lazily typing ints go through them all with fine tooth comb once api is up
ChainId int `json:"chainId"`
Address string `json:"address"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
Extensions Extensions `json:"extensions"`
}
func jsonResponse(w http.ResponseWriter, logger log.Logger, data interface{}, statusCode int) {
w.Header().Set("Content-Type", "application/json")
jsonData, err := json.Marshal(data)
......
......@@ -10,16 +10,16 @@ import (
)
type DepositItem struct {
Guid string `json:"guid"`
From string `json:"from"`
To string `json:"to"`
// TODO could consider OriginTx to be more generic to handling L2 to L2 deposits
// this seems more clear today though
Tx Transaction `json:"Tx"`
Block Block `json:"Block"`
Amount string `json:"amount"`
L1Token TokenInfo `json:"l1Token"`
L2Token TokenInfo `json:"l2Token"`
Guid string `json:"guid"`
From string `json:"from"`
To string `json:"to"`
Timestamp uint64 `json:"timestamp"`
L1TxHash string `json:"L1TxHash"`
L2TxHash string `json:"L2TxHash"`
L1BlockHash string `json:"Block"`
Amount string `json:"amount"`
L1TokenAddress string `json:"l1Token"`
L2TokenAddress string `json:"l2Token"`
}
type DepositResponse struct {
......@@ -28,47 +28,22 @@ type DepositResponse struct {
Items []DepositItem `json:"items"`
}
// TODO this is original spec but maybe include the l2 block info too for the relayed tx
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse
func newDepositResponse(deposits *database.L1BridgeDepositsResponse) DepositResponse {
items := make([]DepositItem, len(deposits.Deposits))
for _, deposit := range deposits.Deposits {
for i, deposit := range deposits.Deposits {
item := DepositItem{
Guid: deposit.L1BridgeDeposit.TransactionSourceHash.String(),
Block: Block{
BlockNumber: 420420, // TODO
BlockHash: "0x420", // TODO
},
Tx: Transaction{
TransactionHash: "0x420", // TODO
Timestamp: deposit.L1BridgeDeposit.Tx.Timestamp,
},
From: deposit.L1BridgeDeposit.Tx.FromAddress.String(),
To: deposit.L1BridgeDeposit.Tx.ToAddress.String(),
Amount: deposit.L1BridgeDeposit.Tx.Amount.String(),
L1Token: TokenInfo{
ChainId: 1,
Address: deposit.L1BridgeDeposit.TokenPair.LocalTokenAddress.String(),
Name: "TODO",
Symbol: "TODO",
Decimals: 420,
Extensions: Extensions{
OptimismBridgeAddress: "0x420", // TODO
},
},
L2Token: TokenInfo{
ChainId: 10,
Address: deposit.L1BridgeDeposit.TokenPair.RemoteTokenAddress.String(),
Name: "TODO",
Symbol: "TODO",
Decimals: 420,
Extensions: Extensions{
OptimismBridgeAddress: "0x420", // TODO
},
},
Guid: deposit.L1BridgeDeposit.TransactionSourceHash.String(),
L1BlockHash: deposit.L1BlockHash.String(),
Timestamp: deposit.L1BridgeDeposit.Tx.Timestamp,
L1TxHash: deposit.L1TransactionHash.String(),
L2TxHash: deposit.L2TransactionHash.String(),
From: deposit.L1BridgeDeposit.Tx.FromAddress.String(),
To: deposit.L1BridgeDeposit.Tx.ToAddress.String(),
Amount: deposit.L1BridgeDeposit.Tx.Amount.String(),
L1TokenAddress: deposit.L1BridgeDeposit.TokenPair.LocalTokenAddress.String(),
L2TokenAddress: deposit.L1BridgeDeposit.TokenPair.RemoteTokenAddress.String(),
}
items = append(items, item)
items[i] = item
}
return DepositResponse{
......
......@@ -9,31 +9,18 @@ import (
"github.com/go-chi/chi/v5"
)
type Proof struct {
TransactionHash string `json:"transactionHash"`
BlockTimestamp uint64 `json:"blockTimestamp"`
BlockNumber int `json:"blockNumber"`
}
type Claim struct {
TransactionHash string `json:"transactionHash"`
BlockTimestamp uint64 `json:"blockTimestamp"`
BlockNumber int `json:"blockNumber"`
}
type WithdrawalItem struct {
Guid string `json:"guid"`
Tx Transaction `json:"Tx"`
Block Block `json:"Block"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
Amount string `json:"amount"`
Proof Proof `json:"proof"`
Claim Claim `json:"claim"`
WithdrawalState string `json:"withdrawalState"`
L1Token TokenInfo `json:"l1Token"`
L2Token TokenInfo `json:"l2Token"`
Guid string `json:"guid"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
Timestamp uint64 `json:"timestamp"`
L2BlockHash string `json:"l2BlockHash"`
Amount string `json:"amount"`
ProofTransactionHash string `json:"proof"`
ClaimTransactionHash string `json:"claim"`
L1TokenAddress string `json:"l1Token"`
L2TokenAddress string `json:"l2Token"`
}
type WithdrawalResponse struct {
......@@ -45,55 +32,20 @@ type WithdrawalResponse struct {
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse
func newWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse {
items := make([]WithdrawalItem, len(withdrawals.Withdrawals))
for _, withdrawal := range withdrawals.Withdrawals {
for i, withdrawal := range withdrawals.Withdrawals {
item := WithdrawalItem{
Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(),
Block: Block{
BlockNumber: 420420, // TODO
BlockHash: "0x420", // TODO
},
Tx: Transaction{
TransactionHash: "0x420", // TODO
Timestamp: withdrawal.L2BridgeWithdrawal.Tx.Timestamp,
},
From: withdrawal.L2BridgeWithdrawal.Tx.FromAddress.String(),
To: withdrawal.L2BridgeWithdrawal.Tx.ToAddress.String(),
TransactionHash: withdrawal.L2TransactionHash.String(),
Amount: withdrawal.L2BridgeWithdrawal.Tx.Amount.String(),
Proof: Proof{
TransactionHash: withdrawal.ProvenL1TransactionHash.String(),
BlockTimestamp: withdrawal.L2BridgeWithdrawal.Tx.Timestamp,
BlockNumber: 420, // TODO Block struct instead
},
Claim: Claim{
TransactionHash: withdrawal.FinalizedL1TransactionHash.String(),
BlockTimestamp: withdrawal.L2BridgeWithdrawal.Tx.Timestamp, // Using L2 timestamp for now, might need adjustment
BlockNumber: 420, // TODO block struct
},
WithdrawalState: "COMPLETE", // TODO
L1Token: TokenInfo{
ChainId: 1,
Address: withdrawal.L2BridgeWithdrawal.TokenPair.RemoteTokenAddress.String(),
Name: "Example", // TODO
Symbol: "EXAMPLE", // TODO
Decimals: 18, // TODO
Extensions: Extensions{
OptimismBridgeAddress: "0x636Af16bf2f682dD3109e60102b8E1A089FedAa8",
},
},
L2Token: TokenInfo{
ChainId: 10,
Address: withdrawal.L2BridgeWithdrawal.TokenPair.LocalTokenAddress.String(),
Name: "Example", // TODO
Symbol: "EXAMPLE", // TODO
Decimals: 18, // TODO
Extensions: Extensions{
OptimismBridgeAddress: "0x36Af16bf2f682dD3109e60102b8E1A089FedAa86",
},
},
Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(),
L2BlockHash: withdrawal.L2BlockHash.String(),
From: withdrawal.L2BridgeWithdrawal.Tx.FromAddress.String(),
To: withdrawal.L2BridgeWithdrawal.Tx.ToAddress.String(),
TransactionHash: withdrawal.L2TransactionHash.String(),
Amount: withdrawal.L2BridgeWithdrawal.Tx.Amount.String(),
ProofTransactionHash: withdrawal.ProvenL1TransactionHash.String(),
ClaimTransactionHash: withdrawal.FinalizedL1TransactionHash.String(),
L1TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.RemoteTokenAddress.String(),
L2TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.LocalTokenAddress.String(),
}
items = append(items, item)
items[i] = item
}
return WithdrawalResponse{
......
......@@ -6,6 +6,7 @@ import (
"reflect"
"github.com/BurntSushi/toml"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
......@@ -16,7 +17,7 @@ const (
defaultHeaderBufferSize = 500
)
// in future presets can just be onchain config and fetched on initialization
// In the future, presets can just be onchain config and fetched on initialization
// Config represents the `indexer.toml` file used to configure the indexer
type Config struct {
......@@ -27,50 +28,80 @@ type Config struct {
MetricsServer ServerConfig `toml:"metrics"`
}
// fetch this via onchain config from RPCsConfig and remove from config in future
// L1Contracts configures deployed contracts
type L1Contracts struct {
OptimismPortalProxy common.Address `toml:"optimism-portal"`
L2OutputOracleProxy common.Address `toml:"l2-output-oracle"`
// administrative
AddressManager common.Address `toml:"address-manager"`
SystemConfigProxy common.Address `toml:"system-config"`
// rollup state
OptimismPortalProxy common.Address `toml:"optimism-portal"`
L2OutputOracleProxy common.Address `toml:"l2-output-oracle"`
// bridging
L1CrossDomainMessengerProxy common.Address `toml:"l1-cross-domain-messenger"`
L1StandardBridgeProxy common.Address `toml:"l1-standard-bridge"`
L1ERC721BridgeProxy common.Address `toml:"l1-erc721-bridge"`
// IGNORE: legacy contracts (only settable via presets)
LegacyCanonicalTransactionChain common.Address `toml:"-"`
LegacyStateCommitmentChain common.Address `toml:"-"`
}
// Pre-Bedrock Legacy Contracts
LegacyCanonicalTransactionChain common.Address `toml:"l1-canonical-transaction-chain"`
func (c L1Contracts) ForEach(cb func(string, common.Address) error) error {
contracts := reflect.ValueOf(c)
fields := reflect.VisibleFields(reflect.TypeOf(c))
for _, field := range fields {
// ruleid: unsafe-reflect-by-name
addr := (contracts.FieldByName(field.Name).Interface()).(common.Address)
if err := cb(field.Name, addr); err != nil {
return err
}
}
// Some more contracts -- L1ERC721Bridge, ProxyAdmin, SystemConfig, etc
// Ignore the auxiliary contracts?
return nil
}
// Legacy contracts? We'll add this in to index the legacy chain.
// Remove afterwards?
// L2Contracts configures core predeploy contracts. We explicitly specify
// fields until we can detect and backfill new addresses
type L2Contracts struct {
L2ToL1MessagePasser common.Address
L2CrossDomainMessenger common.Address
L2StandardBridge common.Address
L2ERC721Bridge common.Address
}
// converts struct of to a slice of addresses for easy iteration
// also validates that all fields are addresses
func (c *L1Contracts) AsSlice() ([]common.Address, error) {
clone := *c
contractValue := reflect.ValueOf(clone)
fields := reflect.VisibleFields(reflect.TypeOf(clone))
l1Contracts := make([]common.Address, len(fields))
for i, field := range fields {
func L2ContractsFromPredeploys() L2Contracts {
return L2Contracts{
L2ToL1MessagePasser: predeploys.L2ToL1MessagePasserAddr,
L2CrossDomainMessenger: predeploys.L2CrossDomainMessengerAddr,
L2StandardBridge: predeploys.L2StandardBridgeAddr,
L2ERC721Bridge: predeploys.L2ERC721BridgeAddr,
}
}
func (c L2Contracts) ForEach(cb func(string, common.Address) error) error {
contracts := reflect.ValueOf(c)
fields := reflect.VisibleFields(reflect.TypeOf(c))
for _, field := range fields {
// ruleid: unsafe-reflect-by-name
addr, ok := (contractValue.FieldByName(field.Name).Interface()).(common.Address)
if !ok {
return nil, fmt.Errorf("non-address found in L1Contracts: %s", field.Name)
addr := (contracts.FieldByName(field.Name).Interface()).(common.Address)
if err := cb(field.Name, addr); err != nil {
return err
}
l1Contracts[i] = addr
}
return l1Contracts, nil
return nil
}
// ChainConfig configures of the chain being indexed
type ChainConfig struct {
// Configure known chains with the l2 chain id
Preset int
Preset int
L1StartingHeight uint `toml:"l1-starting-height"`
L1Contracts L1Contracts `toml:"l1-contracts"`
L1StartingHeight uint `toml:"l1-starting-height"`
L1Contracts L1Contracts `toml:"l1-contracts"`
L2Contracts L2Contracts `toml:"-"`
// Bedrock starting heights only applicable for OP-Mainnet & OP-Goerli
L1BedrockStartingHeight uint `toml:"-"`
......@@ -127,18 +158,24 @@ func LoadConfig(log log.Logger, path string) (Config, error) {
}
if conf.Chain.Preset != 0 {
knownPreset, ok := presetConfigs[conf.Chain.Preset]
preset, ok := Presets[conf.Chain.Preset]
if !ok {
return conf, fmt.Errorf("unknown preset: %d", conf.Chain.Preset)
}
conf.Chain.L1Contracts = knownPreset.L1Contracts
conf.Chain.L1StartingHeight = knownPreset.L1StartingHeight
conf.Chain.L1BedrockStartingHeight = knownPreset.L1BedrockStartingHeight
conf.Chain.L2BedrockStartingHeight = knownPreset.L1BedrockStartingHeight
log.Info("detected preset", "preset", conf.Chain.Preset, "name", preset.Name)
log.Info("setting L1 information from preset")
conf.Chain.L1Contracts = preset.ChainConfig.L1Contracts
conf.Chain.L1StartingHeight = preset.ChainConfig.L1StartingHeight
conf.Chain.L1BedrockStartingHeight = preset.ChainConfig.L1BedrockStartingHeight
conf.Chain.L2BedrockStartingHeight = preset.ChainConfig.L1BedrockStartingHeight
}
// Set polling defaults if not set
// Setup L2Contracts from predeploys
conf.Chain.L2Contracts = L2ContractsFromPredeploys()
// Setup defaults for some unset options
if conf.Chain.L1PollingInterval == 0 {
log.Info("setting default L1 polling interval", "interval", defaultLoopInterval)
conf.Chain.L1PollingInterval = defaultLoopInterval
......
......@@ -54,10 +54,10 @@ func TestLoadConfig(t *testing.T) {
require.NoError(t, err)
require.Equal(t, conf.Chain.Preset, 420)
require.Equal(t, conf.Chain.L1Contracts.OptimismPortalProxy.String(), presetConfigs[420].L1Contracts.OptimismPortalProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessengerProxy.String(), presetConfigs[420].L1Contracts.L1CrossDomainMessengerProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L1StandardBridgeProxy.String(), presetConfigs[420].L1Contracts.L1StandardBridgeProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L2OutputOracleProxy.String(), presetConfigs[420].L1Contracts.L2OutputOracleProxy.String())
require.Equal(t, conf.Chain.L1Contracts.OptimismPortalProxy.String(), Presets[420].ChainConfig.L1Contracts.OptimismPortalProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessengerProxy.String(), Presets[420].ChainConfig.L1Contracts.L1CrossDomainMessengerProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L1StandardBridgeProxy.String(), Presets[420].ChainConfig.L1Contracts.L1StandardBridgeProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L2OutputOracleProxy.String(), Presets[420].ChainConfig.L1Contracts.L2OutputOracleProxy.String())
require.Equal(t, conf.RPCs.L1RPC, "https://l1.example.com")
require.Equal(t, conf.RPCs.L2RPC, "https://l2.example.com")
require.Equal(t, conf.DB.Host, "127.0.0.1")
......@@ -71,7 +71,7 @@ func TestLoadConfig(t *testing.T) {
require.Equal(t, conf.MetricsServer.Port, 7300)
}
func TestLoadConfig_WithoutPreset(t *testing.T) {
func TestLoadConfigWithoutPreset(t *testing.T) {
tmpfile, err := os.CreateTemp("", "test_without_preset.toml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
......@@ -103,7 +103,6 @@ func TestLoadConfig_WithoutPreset(t *testing.T) {
conf, err := LoadConfig(logger, tmpfile.Name())
require.NoError(t, err)
// Enforce default values
require.Equal(t, conf.Chain.L1Contracts.OptimismPortalProxy.String(), common.HexToAddress("0x4205Fc579115071764c7423A4f12eDde41f106Ed").String())
require.Equal(t, conf.Chain.L1Contracts.L2OutputOracleProxy.String(), common.HexToAddress("0x42097868233d1aa22e815a266982f2cf17685a27").String())
require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessengerProxy.String(), common.HexToAddress("0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1").String())
......@@ -117,7 +116,7 @@ func TestLoadConfig_WithoutPreset(t *testing.T) {
require.Equal(t, conf.Chain.L2HeaderBufferSize, uint(500))
}
func TestLoadConfig_WithUnknownPreset(t *testing.T) {
func TestLoadConfigWithUnknownPreset(t *testing.T) {
tmpfile, err := os.CreateTemp("", "test_bad_preset.toml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
......@@ -148,7 +147,7 @@ func TestLoadConfig_WithUnknownPreset(t *testing.T) {
require.Equal(t, fmt.Sprintf("unknown preset: %d", faultyPreset), err.Error())
}
func Test_LoadConfig_PollingValues(t *testing.T) {
func TestLoadConfigPollingValues(t *testing.T) {
tmpfile, err := os.CreateTemp("", "test_user_values.toml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
......@@ -178,25 +177,3 @@ func Test_LoadConfig_PollingValues(t *testing.T) {
require.Equal(t, conf.Chain.L1HeaderBufferSize, uint(100))
require.Equal(t, conf.Chain.L2HeaderBufferSize, uint(105))
}
func Test_AsSliceSuccess(t *testing.T) {
// error cases are intentionally ignored for testing since they can only be
// generated when the L1Contracts struct is developer modified to hold a non-address var field
testCfg := &L1Contracts{
OptimismPortalProxy: common.HexToAddress("0x4205Fc579115071764c7423A4f12eDde41f106Ed"),
L2OutputOracleProxy: common.HexToAddress("0x42097868233d1aa22e815a266982f2cf17685a27"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1"),
L1StandardBridgeProxy: common.HexToAddress("0x4209fc46f92E8a1c0deC1b1747d010903E884bE1"),
}
slice, err := testCfg.AsSlice()
require.NoError(t, err)
require.Equal(t, len(slice), 5)
require.Equal(t, slice[0].String(), testCfg.OptimismPortalProxy.String())
require.Equal(t, slice[1].String(), testCfg.L2OutputOracleProxy.String())
require.Equal(t, slice[2].String(), testCfg.L1CrossDomainMessengerProxy.String())
require.Equal(t, slice[3].String(), testCfg.L1StandardBridgeProxy.String())
// LegacyCanonicalTransactionChain is the 4th slot
}
......@@ -4,73 +4,115 @@ import (
"github.com/ethereum/go-ethereum/common"
)
// in future presets can just be onchain config and fetched on initialization
// Mapping of l2 chain ids to their preset chain configurations
var presetConfigs = map[int]ChainConfig{
// OP Mainnet
type Preset struct {
Name string
ChainConfig ChainConfig
}
// In the future, presets can just be onchain config and fetched on initialization
// Mapping of L2 chain ids to their preset chain configurations
var Presets = map[int]Preset{
10: {
L1Contracts: L1Contracts{
OptimismPortalProxy: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"),
L2OutputOracleProxy: common.HexToAddress("0xdfe97868233d1aa22e815a266982f2cf17685a27"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1"),
L1StandardBridgeProxy: common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1"),
LegacyCanonicalTransactionChain: common.HexToAddress("0x5e4e65926ba27467555eb562121fac00d24e9dd2"),
Name: "Optimism",
ChainConfig: ChainConfig{
L1Contracts: L1Contracts{
AddressManager: common.HexToAddress("0xdE1FCfB0851916CA5101820A69b13a4E276bd81F"),
SystemConfigProxy: common.HexToAddress("0x229047fed2591dbec1eF1118d64F7aF3dB9EB290"),
OptimismPortalProxy: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"),
L2OutputOracleProxy: common.HexToAddress("0xdfe97868233d1aa22e815a266982f2cf17685a27"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1"),
L1StandardBridgeProxy: common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1"),
L1ERC721BridgeProxy: common.HexToAddress("0x5a7749f83b81B301cAb5f48EB8516B986DAef23D"),
// pre-bedrock
LegacyCanonicalTransactionChain: common.HexToAddress("0x5e4e65926ba27467555eb562121fac00d24e9dd2"),
LegacyStateCommitmentChain: common.HexToAddress("0xBe5dAb4A2e9cd0F27300dB4aB94BeE3A233AEB19"),
},
L1StartingHeight: 13596466,
L1BedrockStartingHeight: 17422590,
L2BedrockStartingHeight: 105235063,
},
L1StartingHeight: 13596466,
L1BedrockStartingHeight: 17422590,
L2BedrockStartingHeight: 105235063,
},
// OP Goerli
420: {
L1Contracts: L1Contracts{
OptimismPortalProxy: common.HexToAddress("0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"),
L2OutputOracleProxy: common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"),
L1StandardBridgeProxy: common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"),
LegacyCanonicalTransactionChain: common.HexToAddress("0x607F755149cFEB3a14E1Dc3A4E2450Cde7dfb04D"),
Name: "Optimism Goerli",
ChainConfig: ChainConfig{
L1Contracts: L1Contracts{
AddressManager: common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"),
SystemConfigProxy: common.HexToAddress("0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60"),
OptimismPortalProxy: common.HexToAddress("0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"),
L2OutputOracleProxy: common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"),
L1StandardBridgeProxy: common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"),
L1ERC721BridgeProxy: common.HexToAddress("0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9"),
// pre-bedrock
LegacyCanonicalTransactionChain: common.HexToAddress("0x607F755149cFEB3a14E1Dc3A4E2450Cde7dfb04D"),
LegacyStateCommitmentChain: common.HexToAddress("0x9c945aC97Baf48cB784AbBB61399beB71aF7A378"),
},
L1StartingHeight: 7017096,
L1BedrockStartingHeight: 8300214,
L2BedrockStartingHeight: 4061224,
},
L1StartingHeight: 7017096,
L1BedrockStartingHeight: 8300214,
L2BedrockStartingHeight: 4061224,
},
// Base Mainnet
8453: {
L1Contracts: L1Contracts{
OptimismPortalProxy: common.HexToAddress("0x49048044D57e1C92A77f79988d21Fa8fAF74E97e"),
L2OutputOracleProxy: common.HexToAddress("0x56315b90c40730925ec5485cf004d835058518A0"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x866E82a600A1414e583f7F13623F1aC5d58b0Afa"),
L1StandardBridgeProxy: common.HexToAddress("0x3154Cf16ccdb4C6d922629664174b904d80F2C35"),
Name: "Base",
ChainConfig: ChainConfig{
L1Contracts: L1Contracts{
AddressManager: common.HexToAddress("0x8EfB6B5c4767B09Dc9AA6Af4eAA89F749522BaE2"),
SystemConfigProxy: common.HexToAddress("0x73a79Fab69143498Ed3712e519A88a918e1f4072"),
OptimismPortalProxy: common.HexToAddress("0x49048044D57e1C92A77f79988d21Fa8fAF74E97e"),
L2OutputOracleProxy: common.HexToAddress("0x56315b90c40730925ec5485cf004d835058518A0"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x866E82a600A1414e583f7F13623F1aC5d58b0Afa"),
L1StandardBridgeProxy: common.HexToAddress("0x3154Cf16ccdb4C6d922629664174b904d80F2C35"),
L1ERC721BridgeProxy: common.HexToAddress("0x608d94945A64503E642E6370Ec598e519a2C1E53"),
},
L1StartingHeight: 17481768,
},
L1StartingHeight: 17481768,
},
// Base Goerli
84531: {
L1Contracts: L1Contracts{
OptimismPortalProxy: common.HexToAddress("0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA"),
L2OutputOracleProxy: common.HexToAddress("0x2A35891ff30313CcFa6CE88dcf3858bb075A2298"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x8e5693140eA606bcEB98761d9beB1BC87383706D"),
L1StandardBridgeProxy: common.HexToAddress("0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a"),
Name: "Base Goerli",
ChainConfig: ChainConfig{
L1Contracts: L1Contracts{
AddressManager: common.HexToAddress("0x4Cf6b56b14c6CFcB72A75611080514F94624c54e"),
SystemConfigProxy: common.HexToAddress("0xb15eea247eCE011C68a614e4a77AD648ff495bc1"),
OptimismPortalProxy: common.HexToAddress("0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA"),
L2OutputOracleProxy: common.HexToAddress("0x2A35891ff30313CcFa6CE88dcf3858bb075A2298"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x8e5693140eA606bcEB98761d9beB1BC87383706D"),
L1StandardBridgeProxy: common.HexToAddress("0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a"),
L1ERC721BridgeProxy: common.HexToAddress("0x5E0c967457347D5175bF82E8CCCC6480FCD7e568"),
},
L1StartingHeight: 8410981,
},
L1StartingHeight: 8410981,
},
// Zora mainnet
7777777: {
L1Contracts: L1Contracts{
OptimismPortalProxy: common.HexToAddress("0x1a0ad011913A150f69f6A19DF447A0CfD9551054"),
L2OutputOracleProxy: common.HexToAddress("0x9E6204F750cD866b299594e2aC9eA824E2e5f95c"),
L1CrossDomainMessengerProxy: common.HexToAddress("0xdC40a14d9abd6F410226f1E6de71aE03441ca506"),
L1StandardBridgeProxy: common.HexToAddress("0x3e2Ea9B92B7E48A52296fD261dc26fd995284631"),
Name: "Zora",
ChainConfig: ChainConfig{
L1Contracts: L1Contracts{
AddressManager: common.HexToAddress("0xEF8115F2733fb2033a7c756402Fc1deaa56550Ef"),
SystemConfigProxy: common.HexToAddress("0xA3cAB0126d5F504B071b81a3e8A2BBBF17930d86"),
OptimismPortalProxy: common.HexToAddress("0x1a0ad011913A150f69f6A19DF447A0CfD9551054"),
L2OutputOracleProxy: common.HexToAddress("0x9E6204F750cD866b299594e2aC9eA824E2e5f95c"),
L1CrossDomainMessengerProxy: common.HexToAddress("0xdC40a14d9abd6F410226f1E6de71aE03441ca506"),
L1StandardBridgeProxy: common.HexToAddress("0x3e2Ea9B92B7E48A52296fD261dc26fd995284631"),
L1ERC721BridgeProxy: common.HexToAddress("0x83A4521A3573Ca87f3a971B169C5A0E1d34481c3"),
},
L1StartingHeight: 17473923,
},
L1StartingHeight: 17473923,
},
// Zora goerli
999: {
L1Contracts: L1Contracts{
OptimismPortalProxy: common.HexToAddress("0xDb9F51790365e7dc196e7D072728df39Be958ACe"),
L2OutputOracleProxy: common.HexToAddress("0xdD292C9eEd00f6A32Ff5245d0BCd7f2a15f24e00"),
L1CrossDomainMessengerProxy: common.HexToAddress("0xD87342e16352D33170557A7dA1e5fB966a60FafC"),
L1StandardBridgeProxy: common.HexToAddress("0x7CC09AC2452D6555d5e0C213Ab9E2d44eFbFc956"),
Name: "Zora Goerli",
ChainConfig: ChainConfig{
L1Contracts: L1Contracts{
AddressManager: common.HexToAddress("0x54f4676203dEDA6C08E0D40557A119c602bFA246"),
SystemConfigProxy: common.HexToAddress("0xF66C9A5E4fE1A8a9bc44a4aF80505a4C3620Ee64"),
OptimismPortalProxy: common.HexToAddress("0xDb9F51790365e7dc196e7D072728df39Be958ACe"),
L2OutputOracleProxy: common.HexToAddress("0xdD292C9eEd00f6A32Ff5245d0BCd7f2a15f24e00"),
L1CrossDomainMessengerProxy: common.HexToAddress("0xD87342e16352D33170557A7dA1e5fB966a60FafC"),
L1StandardBridgeProxy: common.HexToAddress("0x7CC09AC2452D6555d5e0C213Ab9E2d44eFbFc956"),
L1ERC721BridgeProxy: common.HexToAddress("0x57C1C6b596ce90C0e010c358DD4Aa052404bB70F"),
},
L1StartingHeight: 8942381,
},
L1StartingHeight: 8942381,
},
}
......@@ -7,8 +7,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/google/uuid"
"gorm.io/gorm"
)
......@@ -44,25 +42,6 @@ type L2BlockHeader struct {
BlockHeader `gorm:"embedded"`
}
type LegacyStateBatch struct {
// `default:0` is added since gorm would interepret 0 as NULL
// violating the primary key constraint.
Index uint64 `gorm:"primaryKey;default:0"`
Root common.Hash `gorm:"serializer:bytes"`
Size uint64
PrevTotal uint64
L1ContractEventGUID uuid.UUID
}
type OutputProposal struct {
OutputRoot common.Hash `gorm:"primaryKey;serializer:bytes"`
L2OutputIndex *big.Int `gorm:"serializer:u256"`
L2BlockNumber *big.Int `gorm:"serializer:u256"`
L1ContractEventGUID uuid.UUID
}
type BlocksView interface {
L1BlockHeader(common.Hash) (*L1BlockHeader, error)
L1BlockHeaderWithFilter(BlockHeader) (*L1BlockHeader, error)
......@@ -72,9 +51,6 @@ type BlocksView interface {
L2BlockHeaderWithFilter(BlockHeader) (*L2BlockHeader, error)
L2LatestBlockHeader() (*L2BlockHeader, error)
LatestCheckpointedOutput() (*OutputProposal, error)
OutputProposal(index *big.Int) (*OutputProposal, error)
LatestEpoch() (*Epoch, error)
}
......@@ -83,9 +59,6 @@ type BlocksDB interface {
StoreL1BlockHeaders([]L1BlockHeader) error
StoreL2BlockHeaders([]L2BlockHeader) error
StoreLegacyStateBatches([]LegacyStateBatch) error
StoreOutputProposals([]OutputProposal) error
}
/**
......@@ -107,16 +80,6 @@ func (db *blocksDB) StoreL1BlockHeaders(headers []L1BlockHeader) error {
return result.Error
}
func (db *blocksDB) StoreLegacyStateBatches(stateBatches []LegacyStateBatch) error {
result := db.gorm.CreateInBatches(stateBatches, batchInsertSize)
return result.Error
}
func (db *blocksDB) StoreOutputProposals(outputs []OutputProposal) error {
result := db.gorm.CreateInBatches(outputs, batchInsertSize)
return result.Error
}
func (db *blocksDB) L1BlockHeader(hash common.Hash) (*L1BlockHeader, error) {
return db.L1BlockHeaderWithFilter(BlockHeader{Hash: hash})
}
......@@ -148,34 +111,6 @@ func (db *blocksDB) L1LatestBlockHeader() (*L1BlockHeader, error) {
return &l1Header, nil
}
func (db *blocksDB) LatestCheckpointedOutput() (*OutputProposal, error) {
var outputProposal OutputProposal
result := db.gorm.Order("l2_output_index DESC").Take(&outputProposal)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &outputProposal, nil
}
func (db *blocksDB) OutputProposal(index *big.Int) (*OutputProposal, error) {
var outputProposal OutputProposal
result := db.gorm.Where(&OutputProposal{L2OutputIndex: index}).Take(&outputProposal)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &outputProposal, nil
}
// L2
func (db *blocksDB) StoreL2BlockHeaders(headers []L2BlockHeader) error {
......
......@@ -38,6 +38,7 @@ type L1BridgeDeposit struct {
type L1BridgeDepositWithTransactionHashes struct {
L1BridgeDeposit L1BridgeDeposit `gorm:"embedded"`
L1BlockHash common.Hash `gorm:"serializer:bytes"`
L1TransactionHash common.Hash `gorm:"serializer:bytes"`
L2TransactionHash common.Hash `gorm:"serializer:bytes"`
}
......@@ -50,6 +51,7 @@ type L2BridgeWithdrawal struct {
type L2BridgeWithdrawalWithTransactionHashes struct {
L2BridgeWithdrawal L2BridgeWithdrawal `gorm:"embedded"`
L2TransactionHash common.Hash `gorm:"serializer:bytes"`
L2BlockHash common.Hash `gorm:"serializer:bytes"`
ProvenL1TransactionHash common.Hash `gorm:"serializer:bytes"`
FinalizedL1TransactionHash common.Hash `gorm:"serializer:bytes"`
......@@ -154,7 +156,7 @@ func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address, c
ethTransactionDeposits = ethTransactionDeposits.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = initiated_l1_event_guid")
ethTransactionDeposits = ethTransactionDeposits.Select(`
from_address, to_address, amount, data, source_hash AS transaction_source_hash,
l2_transaction_hash, l1_contract_events.transaction_hash AS l1_transaction_hash,
l2_transaction_hash, l1_contract_events.transaction_hash AS l1_transaction_hash, l1_contract_events.block_hash as l1_block_hash,
l1_transaction_deposits.timestamp, NULL AS cross_domain_message_hash, ? AS local_token_address, ? AS remote_token_address`, ethAddressString, ethAddressString)
ethTransactionDeposits = ethTransactionDeposits.Order("timestamp DESC").Limit(limit + 1)
if cursorClause != "" {
......@@ -166,7 +168,7 @@ l1_transaction_deposits.timestamp, NULL AS cross_domain_message_hash, ? AS local
depositsQuery = depositsQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l1_transaction_deposits.initiated_l1_event_guid")
depositsQuery = depositsQuery.Select(`
l1_bridge_deposits.from_address, l1_bridge_deposits.to_address, l1_bridge_deposits.amount, l1_bridge_deposits.data, transaction_source_hash,
l2_transaction_hash, l1_contract_events.transaction_hash AS l1_transaction_hash,
l2_transaction_hash, l1_contract_events.transaction_hash AS l1_transaction_hash, l1_contract_events.block_hash as l1_block_hash,
l1_bridge_deposits.timestamp, cross_domain_message_hash, local_token_address, remote_token_address`)
depositsQuery = depositsQuery.Order("timestamp DESC").Limit(limit + 1)
if cursorClause != "" {
......@@ -269,7 +271,7 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawalsByAddress(address common.Address
ethTransactionWithdrawals = ethTransactionWithdrawals.Joins("LEFT JOIN l1_contract_events AS finalized_l1_events ON finalized_l1_events.guid = l2_transaction_withdrawals.finalized_l1_event_guid")
ethTransactionWithdrawals = ethTransactionWithdrawals.Select(`
from_address, to_address, amount, data, withdrawal_hash AS transaction_withdrawal_hash,
l2_contract_events.transaction_hash AS l2_transaction_hash, proven_l1_events.transaction_hash AS proven_l1_transaction_hash, finalized_l1_events.transaction_hash AS finalized_l1_transaction_hash,
l2_contract_events.transaction_hash AS l2_transaction_hash, l2_contract_events.block_hash as l2_block_hash, proven_l1_events.transaction_hash AS proven_l1_transaction_hash, finalized_l1_events.transaction_hash AS finalized_l1_transaction_hash,
l2_transaction_withdrawals.timestamp, NULL AS cross_domain_message_hash, ? AS local_token_address, ? AS remote_token_address`, ethAddressString, ethAddressString)
ethTransactionWithdrawals = ethTransactionWithdrawals.Order("timestamp DESC").Limit(limit + 1)
if cursorClause != "" {
......@@ -283,7 +285,7 @@ l2_transaction_withdrawals.timestamp, NULL AS cross_domain_message_hash, ? AS lo
withdrawalsQuery = withdrawalsQuery.Joins("LEFT JOIN l1_contract_events AS finalized_l1_events ON finalized_l1_events.guid = l2_transaction_withdrawals.finalized_l1_event_guid")
withdrawalsQuery = withdrawalsQuery.Select(`
l2_bridge_withdrawals.from_address, l2_bridge_withdrawals.to_address, l2_bridge_withdrawals.amount, l2_bridge_withdrawals.data, transaction_withdrawal_hash,
l2_contract_events.transaction_hash AS l2_transaction_hash, proven_l1_events.transaction_hash AS proven_l1_transaction_hash, finalized_l1_events.transaction_hash AS finalized_l1_transaction_hash,
l2_contract_events.transaction_hash AS l2_transaction_hash, l2_contract_events.block_hash as l2_block_hash, proven_l1_events.transaction_hash AS proven_l1_transaction_hash, finalized_l1_events.transaction_hash AS finalized_l1_transaction_hash,
l2_bridge_withdrawals.timestamp, cross_domain_message_hash, local_token_address, remote_token_address`)
withdrawalsQuery = withdrawalsQuery.Order("timestamp DESC").Limit(limit + 1)
if cursorClause != "" {
......
package database
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/mock"
......@@ -53,16 +51,6 @@ func (m *MockBlocksView) L2LatestBlockHeader() (*L2BlockHeader, error) {
return args.Get(0).(*L2BlockHeader), args.Error(1)
}
func (m *MockBlocksView) LatestCheckpointedOutput() (*OutputProposal, error) {
args := m.Called()
return args.Get(0).(*OutputProposal), args.Error(1)
}
func (m *MockBlocksView) OutputProposal(index *big.Int) (*OutputProposal, error) {
args := m.Called()
return args.Get(0).(*OutputProposal), args.Error(1)
}
func (m *MockBlocksView) LatestEpoch() (*Epoch, error) {
args := m.Called()
return args.Get(0).(*Epoch), args.Error(1)
......@@ -82,15 +70,6 @@ func (m *MockBlocksDB) StoreL2BlockHeaders(headers []L2BlockHeader) error {
return args.Error(1)
}
func (m *MockBlocksDB) StoreLegacyStateBatches(headers []LegacyStateBatch) error {
args := m.Called(headers)
return args.Error(1)
}
func (m *MockBlocksDB) StoreOutputProposals(headers []OutputProposal) error {
args := m.Called(headers)
return args.Error(1)
}
// MockDB is a mock database that can be used for testing
type MockDB struct {
MockBlocks *MockBlocksDB
......
......@@ -57,6 +57,7 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
require.NoError(t, err)
require.Len(t, aliceDeposits.Deposits, 1)
require.Equal(t, depositTx.Hash(), aliceDeposits.Deposits[0].L1TransactionHash)
require.Equal(t, depositReceipt.BlockHash, aliceDeposits.Deposits[0].L1BlockHash)
require.Equal(t, "", aliceDeposits.Cursor)
require.Equal(t, false, aliceDeposits.HasNextPage)
require.Equal(t, types.NewTx(depositInfo.DepositTx).Hash().String(), aliceDeposits.Deposits[0].L2TransactionHash.String())
......@@ -293,6 +294,7 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
require.NoError(t, err)
require.Equal(t, proveReceipt.TxHash, aliceWithdrawals.Withdrawals[0].ProvenL1TransactionHash)
require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals.Withdrawals[0].FinalizedL1TransactionHash)
require.Equal(t, withdrawReceipt.BlockHash, aliceWithdrawals.Withdrawals[0].L2BlockHash)
crossDomainBridgeMessage, err = testSuite.DB.BridgeMessages.L2BridgeMessage(*withdrawal.CrossDomainMessageHash)
require.NoError(t, err)
......
......@@ -71,45 +71,6 @@ func TestE2EETL(t *testing.T) {
}
})
/*
TODO: ADD THIS BACK IN WHEN THESE MARKERS ARE INDEXED
t.Run("indexes L2 checkpoints", func(t *testing.T) {
latestOutput, err := testSuite.DB.Blocks.LatestCheckpointedOutput()
require.NoError(t, err)
require.NotNil(t, latestOutput)
require.GreaterOrEqual(t, latestOutput.L2BlockNumber.Int.Uint64(), uint64(9))
l2EthClient, err := node.DialEthClient(testSuite.OpSys.EthInstances["sequencer"].HTTPEndpoint())
require.NoError(t, err)
submissionInterval := testSuite.OpCfg.DeployConfig.L2OutputOracleSubmissionInterval
numOutputs := latestOutput.L2BlockNumber.Int.Uint64() / submissionInterval
for i := int64(0); i < int64(numOutputs); i++ {
blockNumber := big.NewInt((i + 1) * int64(submissionInterval))
output, err := testSuite.DB.Blocks.OutputProposal(big.NewInt(i))
require.NoError(t, err)
require.NotNil(t, output)
require.Equal(t, i, output.L2OutputIndex.Int.Int64())
require.Equal(t, blockNumber, output.L2BlockNumber.Int)
require.NotEmpty(t, output.L1ContractEventGUID)
// we may as well check the integrity of the output root
l2Block, err := testSuite.L2Client.BlockByNumber(context.Background(), blockNumber)
require.NoError(t, err)
messagePasserStorageHash, err := l2EthClient.StorageHash(predeploys.L2ToL1MessagePasserAddr, blockNumber)
require.NoError(t, err)
// construct and check output root
outputRootPreImage := [128]byte{} // 4 words (first 32 are zero for version 0)
copy(outputRootPreImage[32:64], l2Block.Root().Bytes()) // state root
copy(outputRootPreImage[64:96], messagePasserStorageHash.Bytes()) // message passer storage root
copy(outputRootPreImage[96:128], l2Block.Hash().Bytes()) // block hash
require.Equal(t, crypto.Keccak256Hash(outputRootPreImage[:]), output.OutputRoot)
}
})
*/
t.Run("indexes L1 blocks with accompanying contract event", func(t *testing.T) {
l1Contracts := []common.Address{}
testSuite.OpCfg.L1Deployments.ForEach(func(name string, addr common.Address) { l1Contracts = append(l1Contracts, addr) })
......
......@@ -71,15 +71,17 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
L2RPC: opSys.EthInstances["sequencer"].HTTPEndpoint(),
},
Chain: config.ChainConfig{
L1PollingInterval: uint(opCfg.DeployConfig.L1BlockTime) * 1000,
L1ConfirmationDepth: 0,
L2PollingInterval: uint(opCfg.DeployConfig.L2BlockTime) * 1000,
L2ConfirmationDepth: 0,
L1PollingInterval: uint(opCfg.DeployConfig.L1BlockTime) * 1000,
L2PollingInterval: uint(opCfg.DeployConfig.L2BlockTime) * 1000,
L2Contracts: config.L2ContractsFromPredeploys(),
L1Contracts: config.L1Contracts{
AddressManager: opCfg.L1Deployments.AddressManager,
SystemConfigProxy: opCfg.L1Deployments.SystemConfigProxy,
OptimismPortalProxy: opCfg.L1Deployments.OptimismPortalProxy,
L2OutputOracleProxy: opCfg.L1Deployments.L2OutputOracleProxy,
L1CrossDomainMessengerProxy: opCfg.L1Deployments.L1CrossDomainMessengerProxy,
L1StandardBridgeProxy: opCfg.L1Deployments.L1StandardBridgeProxy,
L1ERC721BridgeProxy: opCfg.L1Deployments.L1ERC721BridgeProxy,
},
},
HTTPServer: config.ServerConfig{Host: "127.0.0.1", Port: 0},
......
......@@ -2,7 +2,9 @@ package etl
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
......@@ -10,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
......@@ -27,12 +30,25 @@ type L1ETL struct {
func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, client node.EthClient, contracts config.L1Contracts) (*L1ETL, error) {
log = log.New("etl", "l1")
latestHeader, err := db.Blocks.L1LatestBlockHeader()
if err != nil {
zeroAddr := common.Address{}
l1Contracts := []common.Address{}
if err := contracts.ForEach(func(name string, addr common.Address) error {
// Since we dont have backfill support yet, we want to make sure all expected
// contracts are specified to ensure consistent behavior. Once backfill support
// is ready, we can relax this requirement.
if addr == zeroAddr && !strings.HasPrefix(name, "Legacy") {
log.Error("address not configured", "name", name)
return errors.New("all L1Contracts must be configured")
}
log.Info("configured contract", "name", name, "addr", addr)
l1Contracts = append(l1Contracts, addr)
return nil
}); err != nil {
return nil, err
}
cSlice, err := contracts.AsSlice()
latestHeader, err := db.Blocks.L1LatestBlockHeader()
if err != nil {
return nil, err
}
......@@ -42,7 +58,6 @@ func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
if latestHeader != nil {
log.Info("detected last indexed block", "number", latestHeader.Number, "hash", latestHeader.Hash)
fromHeader = latestHeader.RLPHeader.Header()
} else if cfg.StartHeight.BitLen() > 0 {
log.Info("no indexed state starting from supplied L1 height", "height", cfg.StartHeight.String())
header, err := client.BlockHeaderByNumber(cfg.StartHeight)
......@@ -51,7 +66,6 @@ func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
}
fromHeader = header
} else {
log.Info("no indexed state, starting from genesis")
}
......@@ -66,7 +80,7 @@ func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
log: log,
metrics: metrics,
headerTraversal: node.NewHeaderTraversal(client, fromHeader, cfg.ConfirmationDepth),
contracts: cSlice,
contracts: l1Contracts,
etlBatches: etlBatches,
EthClient: client,
......
......@@ -51,10 +51,12 @@ func TestL1ETLConstruction(t *testing.T) {
client.On("GethEthClient").Return(nil)
return &testSuite{
db: db,
client: client,
start: testStart,
contracts: config.L1Contracts{},
db: db,
client: client,
start: testStart,
// utilize sample l1 contract configuration (optimism)
contracts: config.Presets[10].ChainConfig.L1Contracts,
}
},
assertion: func(etl *L1ETL, err error) {
......@@ -81,10 +83,12 @@ func TestL1ETLConstruction(t *testing.T) {
client.On("GethEthClient").Return(nil)
return &testSuite{
db: db,
client: client,
start: testStart,
contracts: config.L1Contracts{},
db: db,
client: client,
start: testStart,
// utilize sample l1 contract configuration (optimism)
contracts: config.Presets[10].ChainConfig.L1Contracts,
}
},
assertion: func(etl *L1ETL, err error) {
......
......@@ -2,11 +2,12 @@ package etl
import (
"context"
"errors"
"time"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
......@@ -19,14 +20,25 @@ type L2ETL struct {
db *database.DB
}
func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, client node.EthClient) (*L2ETL, error) {
func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, client node.EthClient, contracts config.L2Contracts) (*L2ETL, error) {
log = log.New("etl", "l2")
// allow predeploys to be overridable
zeroAddr := common.Address{}
l2Contracts := []common.Address{}
for name, addr := range predeploys.Predeploys {
if err := contracts.ForEach(func(name string, addr common.Address) error {
// Since we dont have backfill support yet, we want to make sure all expected
// contracts are specified to ensure consistent behavior. Once backfill support
// is ready, we can relax this requirement.
if addr == zeroAddr {
log.Error("address not configured", "name", name)
return errors.New("all L2Contracts must be configured")
}
log.Info("configured contract", "name", name, "addr", addr)
l2Contracts = append(l2Contracts, *addr)
l2Contracts = append(l2Contracts, addr)
return nil
}); err != nil {
return nil, err
}
latestHeader, err := db.Blocks.L2LatestBlockHeader()
......
......@@ -76,7 +76,7 @@ func NewIndexer(
HeaderBufferSize: chainConfig.L2HeaderBufferSize,
ConfirmationDepth: big.NewInt(int64(chainConfig.L2ConfirmationDepth)),
}
l2Etl, err := etl.NewL2ETL(l2Cfg, log, db, etl.NewMetrics(metricsRegistry, "l2"), l2EthClient)
l2Etl, err := etl.NewL2ETL(l2Cfg, log, db, etl.NewMetrics(metricsRegistry, "l2"), l2EthClient, chainConfig.L2Contracts)
if err != nil {
return nil, err
}
......
......@@ -57,6 +57,7 @@ CREATE TABLE IF NOT EXISTS l1_contract_events (
CREATE INDEX IF NOT EXISTS l1_contract_events_timestamp ON l1_contract_events(timestamp);
CREATE INDEX IF NOT EXISTS l1_contract_events_block_hash ON l1_contract_events(block_hash);
CREATE INDEX IF NOT EXISTS l1_contract_events_event_signature ON l1_contract_events(event_signature);
CREATE INDEX IF NOT EXISTS l1_contract_events_contract_address ON l1_contract_events(contract_address);
CREATE TABLE IF NOT EXISTS l2_contract_events (
-- Searchable fields
......@@ -74,52 +75,12 @@ CREATE TABLE IF NOT EXISTS l2_contract_events (
CREATE INDEX IF NOT EXISTS l2_contract_events_timestamp ON l2_contract_events(timestamp);
CREATE INDEX IF NOT EXISTS l2_contract_events_block_hash ON l2_contract_events(block_hash);
CREATE INDEX IF NOT EXISTS l2_contract_events_event_signature ON l2_contract_events(event_signature);
-- Tables that index finalization markers for L2 blocks.
CREATE TABLE IF NOT EXISTS legacy_state_batches (
index INTEGER PRIMARY KEY,
root VARCHAR NOT NULL UNIQUE,
size INTEGER NOT NULL,
prev_total INTEGER NOT NULL,
state_batch_appended_guid VARCHAR NOT NULL UNIQUE REFERENCES l1_contract_events(guid) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS output_proposals (
output_root VARCHAR PRIMARY KEY,
l2_output_index UINT256 NOT NULL UNIQUE,
l2_block_number UINT256 NOT NULL UNIQUE,
output_proposed_guid VARCHAR NOT NULL UNIQUE REFERENCES l1_contract_events(guid) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS l2_contract_events_contract_address ON l2_contract_events(contract_address);
/**
* BRIDGING DATA
*/
-- Bridged L1/L2 Tokens
CREATE TABLE IF NOT EXISTS l1_bridged_tokens (
address VARCHAR PRIMARY KEY,
bridge_address VARCHAR NOT NULL,
name VARCHAR NOT NULL,
symbol VARCHAR NOT NULL,
decimals INTEGER NOT NULL CHECK (decimals >= 0 AND decimals <= 18)
);
CREATE TABLE IF NOT EXISTS l2_bridged_tokens (
address VARCHAR PRIMARY KEY,
bridge_address VARCHAR NOT NULL,
-- L1-L2 relationship is 1 to many so this is not necessarily unique
l1_token_address VARCHAR REFERENCES l1_bridged_tokens(address) ON DELETE CASCADE,
name VARCHAR NOT NULL,
symbol VARCHAR NOT NULL,
decimals INTEGER NOT NULL CHECK (decimals >= 0 AND decimals <= 18)
);
-- OptimismPortal/L2ToL1MessagePasser
CREATE TABLE IF NOT EXISTS l1_transaction_deposits (
source_hash VARCHAR PRIMARY KEY,
......@@ -202,6 +163,26 @@ CREATE INDEX IF NOT EXISTS l2_bridge_messages_transaction_withdrawal_hash ON l2_
CREATE INDEX IF NOT EXISTS l2_bridge_messages_from_address ON l2_bridge_messages(from_address);
-- StandardBridge
CREATE TABLE IF NOT EXISTS l1_bridged_tokens (
address VARCHAR PRIMARY KEY,
bridge_address VARCHAR NOT NULL,
name VARCHAR NOT NULL,
symbol VARCHAR NOT NULL,
decimals INTEGER NOT NULL CHECK (decimals >= 0 AND decimals <= 18)
);
CREATE TABLE IF NOT EXISTS l2_bridged_tokens (
address VARCHAR PRIMARY KEY,
bridge_address VARCHAR NOT NULL,
-- L1-L2 relationship is 1 to many so this is not necessarily unique
l1_token_address VARCHAR REFERENCES l1_bridged_tokens(address) ON DELETE CASCADE,
name VARCHAR NOT NULL,
symbol VARCHAR NOT NULL,
decimals INTEGER NOT NULL CHECK (decimals >= 0 AND decimals <= 18)
);
CREATE TABLE IF NOT EXISTS l1_bridge_deposits (
transaction_source_hash VARCHAR PRIMARY KEY REFERENCES l1_transaction_deposits(source_hash) ON DELETE CASCADE,
cross_domain_message_hash VARCHAR NOT NULL UNIQUE REFERENCES l1_bridge_messages(message_hash) ON DELETE CASCADE,
......
......@@ -155,14 +155,14 @@ func (b *BridgeProcessor) Start(ctx context.Context) error {
for _, group := range l1BlockGroups {
log := l1BridgeLog.New("from_l1_block_number", group.Start, "to_l1_block_number", group.End)
log.Info("scanning for initiated bridge events")
if err := bridge.LegacyL1ProcessInitiatedBridgeEvents(log, tx, b.chainConfig, group.Start, group.End); err != nil {
if err := bridge.LegacyL1ProcessInitiatedBridgeEvents(log, tx, b.chainConfig.L1Contracts, group.Start, group.End); err != nil {
return err
}
}
for _, group := range l2BlockGroups {
log := l2BridgeLog.New("from_l2_block_number", group.Start, "to_l2_block_number", group.End)
log.Info("scanning for initiated bridge events")
if err := bridge.LegacyL2ProcessInitiatedBridgeEvents(log, tx, group.Start, group.End); err != nil {
if err := bridge.LegacyL2ProcessInitiatedBridgeEvents(log, tx, b.chainConfig.L2Contracts, group.Start, group.End); err != nil {
return err
}
}
......@@ -171,14 +171,14 @@ func (b *BridgeProcessor) Start(ctx context.Context) error {
for _, group := range l1BlockGroups {
log := l1BridgeLog.New("from_l1_block_number", group.Start, "to_l1_block_number", group.End)
log.Info("scanning for finalized bridge events")
if err := bridge.LegacyL1ProcessFinalizedBridgeEvents(log, tx, b.l1Etl.EthClient, b.chainConfig, group.Start, group.End); err != nil {
if err := bridge.LegacyL1ProcessFinalizedBridgeEvents(log, tx, b.l1Etl.EthClient, b.chainConfig.L1Contracts, group.Start, group.End); err != nil {
return err
}
}
for _, group := range l2BlockGroups {
log := l2BridgeLog.New("from_l2_block_number", group.Start, "to_l2_block_number", group.End)
log.Info("scanning for finalized bridge events")
if err := bridge.LegacyL2ProcessFinalizedBridgeEvents(log, tx, group.Start, group.End); err != nil {
if err := bridge.LegacyL2ProcessFinalizedBridgeEvents(log, tx, b.chainConfig.L2Contracts, group.Start, group.End); err != nil {
return err
}
}
......@@ -199,14 +199,14 @@ func (b *BridgeProcessor) Start(ctx context.Context) error {
for _, group := range l1BlockGroups {
log := l1BridgeLog.New("from_block_number", group.Start, "to_block_number", group.End)
log.Info("scanning for initiated bridge events")
if err := bridge.L1ProcessInitiatedBridgeEvents(log, tx, b.chainConfig, group.Start, group.End); err != nil {
if err := bridge.L1ProcessInitiatedBridgeEvents(log, tx, b.chainConfig.L1Contracts, group.Start, group.End); err != nil {
return err
}
}
for _, group := range l2BlockGroups {
log := l2BridgeLog.New("from_block_number", group.Start, "to_block_number", group.End)
log.Info("scanning for initiated bridge events")
if err := bridge.L2ProcessInitiatedBridgeEvents(log, tx, group.Start, group.End); err != nil {
if err := bridge.L2ProcessInitiatedBridgeEvents(log, tx, b.chainConfig.L2Contracts, group.Start, group.End); err != nil {
return err
}
}
......@@ -215,14 +215,14 @@ func (b *BridgeProcessor) Start(ctx context.Context) error {
for _, group := range l1BlockGroups {
log := l1BridgeLog.New("from_block_number", group.Start, "to_block_number", group.End)
log.Info("scanning for finalized bridge events")
if err := bridge.L1ProcessFinalizedBridgeEvents(log, tx, b.chainConfig, group.Start, group.End); err != nil {
if err := bridge.L1ProcessFinalizedBridgeEvents(log, tx, b.chainConfig.L1Contracts, group.Start, group.End); err != nil {
return err
}
}
for _, group := range l2BlockGroups {
log := l2BridgeLog.New("from_block_number", group.Start, "to_block_number", group.End)
log.Info("scanning for finalized bridge events")
if err := bridge.L2ProcessFinalizedBridgeEvents(log, tx, group.Start, group.End); err != nil {
if err := bridge.L2ProcessFinalizedBridgeEvents(log, tx, b.chainConfig.L2Contracts, group.Start, group.End); err != nil {
return err
}
}
......
......@@ -17,9 +17,9 @@ import (
// 1. OptimismPortal
// 2. L1CrossDomainMessenger
// 3. L1StandardBridge
func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chainConfig config.ChainConfig, fromHeight *big.Int, toHeight *big.Int) error {
func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
// (1) OptimismPortal
optimismPortalTxDeposits, err := contracts.OptimismPortalTransactionDepositEvents(chainConfig.L1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
optimismPortalTxDeposits, err := contracts.OptimismPortalTransactionDepositEvents(l1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -47,7 +47,7 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chainConfig
}
// (2) L1CrossDomainMessenger
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l1", chainConfig.L1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -77,7 +77,7 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chainConfig
}
// (3) L1StandardBridge
initiatedBridges, err := contracts.StandardBridgeInitiatedEvents("l1", chainConfig.L1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
initiatedBridges, err := contracts.StandardBridgeInitiatedEvents("l1", l1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -121,9 +121,9 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chainConfig
// 1. OptimismPortal (Bedrock prove & finalize steps)
// 2. L1CrossDomainMessenger (relayMessage marker)
// 3. L1StandardBridge (no-op, since this is simply a wrapper over the L1CrossDomainMessenger)
func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, chainConfig config.ChainConfig, fromHeight *big.Int, toHeight *big.Int) error {
func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
// (1) OptimismPortal (proven withdrawals)
provenWithdrawals, err := contracts.OptimismPortalWithdrawalProvenEvents(chainConfig.L1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
provenWithdrawals, err := contracts.OptimismPortalWithdrawalProvenEvents(l1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -148,7 +148,7 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, chainConfig
}
// (2) OptimismPortal (finalized withdrawals)
finalizedWithdrawals, err := contracts.OptimismPortalWithdrawalFinalizedEvents(chainConfig.L1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
finalizedWithdrawals, err := contracts.OptimismPortalWithdrawalFinalizedEvents(l1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -173,7 +173,7 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, chainConfig
}
// (3) L1CrossDomainMessenger
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", chainConfig.L1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -200,7 +200,7 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, chainConfig
}
// (4) L1StandardBridge
finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l1", chainConfig.L1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l1", l1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......
......@@ -5,9 +5,9 @@ import (
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/processors/contracts"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/log"
)
......@@ -17,9 +17,9 @@ import (
// 1. OptimismPortal
// 2. L2CrossDomainMessenger
// 3. L2StandardBridge
func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, fromHeight *big.Int, toHeight *big.Int) error {
func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, l2Contracts config.L2Contracts, fromHeight, toHeight *big.Int) error {
// (1) L2ToL1MessagePasser
l2ToL1MPMessagesPassed, err := contracts.L2ToL1MessagePasserMessagePassedEvents(predeploys.L2ToL1MessagePasserAddr, db, fromHeight, toHeight)
l2ToL1MPMessagesPassed, err := contracts.L2ToL1MessagePasserMessagePassedEvents(l2Contracts.L2ToL1MessagePasser, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -47,7 +47,7 @@ func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, fromHeight
}
// (2) L2CrossDomainMessenger
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l2", predeploys.L2CrossDomainMessengerAddr, db, fromHeight, toHeight)
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l2", l2Contracts.L2CrossDomainMessenger, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -78,7 +78,7 @@ func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, fromHeight
}
// (3) L2StandardBridge
initiatedBridges, err := contracts.StandardBridgeInitiatedEvents("l2", predeploys.L2StandardBridgeAddr, db, fromHeight, toHeight)
initiatedBridges, err := contracts.StandardBridgeInitiatedEvents("l2", l2Contracts.L2StandardBridge, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -122,9 +122,9 @@ func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, fromHeight
// 2. L2StandardBridge (no-op, since this is simply a wrapper over the L2CrossDomainMEssenger)
//
// NOTE: Unlike L1, there's no L2ToL1MessagePasser stage since transaction deposits are apart of the block derivation process.
func L2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, fromHeight *big.Int, toHeight *big.Int) error {
func L2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, l2Contracts config.L2Contracts, fromHeight, toHeight *big.Int) error {
// (1) L2CrossDomainMessenger
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l2", predeploys.L2CrossDomainMessengerAddr, db, fromHeight, toHeight)
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l2", l2Contracts.L2CrossDomainMessenger, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -151,7 +151,7 @@ func L2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, fromHeight
}
// (2) L2StandardBridge
finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l2", predeploys.L2StandardBridgeAddr, db, fromHeight, toHeight)
finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l2", l2Contracts.L2StandardBridge, db, fromHeight, toHeight)
if err != nil {
return err
}
......
......@@ -11,7 +11,6 @@ import (
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/indexer/processors/contracts"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
)
// Legacy Bridge Initiation
......@@ -21,9 +20,9 @@ import (
// 1. CanonicalTransactionChain
// 2. L1CrossDomainMessenger
// 3. L1StandardBridge
func LegacyL1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chainConfig config.ChainConfig, fromHeight *big.Int, toHeight *big.Int) error {
func LegacyL1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
// (1) CanonicalTransactionChain
ctcTxDepositEvents, err := contracts.LegacyCTCDepositEvents(chainConfig.L1Contracts.LegacyCanonicalTransactionChain, db, fromHeight, toHeight)
ctcTxDepositEvents, err := contracts.LegacyCTCDepositEvents(l1Contracts.LegacyCanonicalTransactionChain, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -54,7 +53,7 @@ func LegacyL1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chain
}
// (2) L1CrossDomainMessenger
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l1", chainConfig.L1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -84,7 +83,7 @@ func LegacyL1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chain
}
// (3) L1StandardBridge
initiatedBridges, err := contracts.L1StandardBridgeLegacyDepositInitiatedEvents(chainConfig.L1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
initiatedBridges, err := contracts.L1StandardBridgeLegacyDepositInitiatedEvents(l1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -131,9 +130,9 @@ func LegacyL1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chain
// 1. L2CrossDomainMessenger - The LegacyMessagePasser contract cannot be used as entrypoint to bridge transactions from L2. The protocol
// only allows the L2CrossDomainMessenger as the sole sender when relaying a bridged message.
// 2. L2StandardBridge
func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, fromHeight *big.Int, toHeight *big.Int) error {
func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, l2Contracts config.L2Contracts, fromHeight, toHeight *big.Int) error {
// (1) L2CrossDomainMessenger
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l2", predeploys.L2CrossDomainMessengerAddr, db, fromHeight, toHeight)
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l2", l2Contracts.L2CrossDomainMessenger, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -151,7 +150,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, fromH
// To ensure consistency in the schema, we duplicate this as the "root" transaction withdrawal. The storage key in the message
// passer contract is sha3(calldata + sender). The sender always being the L2CrossDomainMessenger pre-bedrock.
withdrawalHash := crypto.Keccak256Hash(append(sentMessage.MessageCalldata, predeploys.L2CrossDomainMessengerAddr[:]...))
withdrawalHash := crypto.Keccak256Hash(append(sentMessage.MessageCalldata, l2Contracts.L2CrossDomainMessenger[:]...))
l2TransactionWithdrawals[i] = database.L2TransactionWithdrawal{
WithdrawalHash: withdrawalHash,
InitiatedL2EventGUID: sentMessage.Event.GUID,
......@@ -182,7 +181,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, fromH
}
// (2) L2StandardBridge
initiatedBridges, err := contracts.L2StandardBridgeLegacyWithdrawalInitiatedEvents(predeploys.L2StandardBridgeAddr, db, fromHeight, toHeight)
initiatedBridges, err := contracts.L2StandardBridgeLegacyWithdrawalInitiatedEvents(l2Contracts.L2StandardBridge, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -225,10 +224,10 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, fromH
// according to the pre-bedrock protocol. This follows:
// 1. L1CrossDomainMessenger
// 2. L1StandardBridge
func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, l1Client node.EthClient, chainConfig config.ChainConfig, fromHeight *big.Int, toHeight *big.Int) error {
func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, l1Client node.EthClient, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
// (1) L1CrossDomainMessenger -> This is the root-most contract from which bridge events are finalized since withdrawals must be initiated from the
// L2CrossDomainMessenger. Since there's no two-step withdrawal process, we mark the transaction as proven/finalized in the same step
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", chainConfig.L1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
......@@ -305,9 +304,9 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, l1Cli
// according to the pre-bedrock protocol. This follows:
// 1. L2CrossDomainMessenger
// 2. L2StandardBridge
func LegacyL2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, fromHeight *big.Int, toHeight *big.Int) error {
func LegacyL2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, l2Contracts config.L2Contracts, fromHeight, toHeight *big.Int) error {
// (1) L2CrossDomainMessenger
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l2", predeploys.L2CrossDomainMessengerAddr, db, fromHeight, toHeight)
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l2", l2Contracts.L2CrossDomainMessenger, db, fromHeight, toHeight)
if err != nil {
return err
}
......
......@@ -103,6 +103,10 @@ Starts a new fault dispute game that disputes the latest output proposal in the
* `RPC_URL` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_FACTORY_ADDRESS` - the address of the dispute game factory contract on L1.
* `ROOT_CLAIM` a hex encoded 32 byte hash to use as the root claim for the created game.
* The root claim must have the high-order byte set to the
invalid [VM status](../specs/cannon-fault-proof-vm.md#state-hash) (`0x01`) to indicate that the trace concludes
that the disputed output root is invalid.
e.g. `0x0146381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add`
* `SIGNER_ARGS` the remaining args are past as arguments to `cast` when sending transactions.
These arguments must specify a way for `cast` to sign the transactions.
See `cast send --help` for supported options.
......
......@@ -169,6 +169,26 @@ func TestMaxConcurrency(t *testing.T) {
})
}
func TestPollInterval(t *testing.T) {
t.Run("UsesDefault", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeCannon))
require.Equal(t, config.DefaultPollInterval, cfg.PollInterval)
})
t.Run("Valid", func(t *testing.T) {
expected := 100 * time.Second
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--http-poll-interval", "100s"))
require.Equal(t, expected, cfg.PollInterval)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(
t,
"invalid value \"abc\" for flag -http-poll-interval",
addRequiredArgs(config.TraceTypeAlphabet, "--http-poll-interval", "abc"))
})
}
func TestCannonBin(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-bin"))
......
......@@ -78,6 +78,7 @@ func ValidTraceType(value TraceType) bool {
}
const (
DefaultPollInterval = time.Second * 12
DefaultCannonSnapshotFreq = uint(1_000_000_000)
DefaultCannonInfoFreq = uint(10_000_000)
// DefaultGameWindow is the default maximum time duration in the past
......@@ -98,6 +99,7 @@ type Config struct {
AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output
Datadir string // Data Directory
MaxConcurrency uint // Maximum number of threads to use when progressing games
PollInterval time.Duration // Polling interval for latest-block subscription when using an HTTP RPC provider
TraceType TraceType // Type of trace
......@@ -131,6 +133,7 @@ func NewConfig(
L1EthRpc: l1EthRpc,
GameFactoryAddress: gameFactoryAddress,
MaxConcurrency: uint(runtime.NumCPU()),
PollInterval: DefaultPollInterval,
AgreeWithProposedOutput: agreeWithProposedOutput,
......
......@@ -118,6 +118,13 @@ func TestMaxConcurrency(t *testing.T) {
})
}
func TestHttpPollInterval(t *testing.T) {
t.Run("Default", func(t *testing.T) {
config := validConfig(TraceTypeAlphabet)
require.EqualValues(t, DefaultPollInterval, config.PollInterval)
})
}
func TestCannonL2Required(t *testing.T) {
config := validConfig(TraceTypeCannon)
config.CannonL2 = ""
......
......@@ -70,6 +70,12 @@ var (
EnvVars: prefixEnvVars("MAX_CONCURRENCY"),
Value: uint(runtime.NumCPU()),
}
HTTPPollInterval = &cli.DurationFlag{
Name: "http-poll-interval",
Usage: "Polling interval for latest-block subscription when using an HTTP RPC provider.",
EnvVars: prefixEnvVars("HTTP_POLL_INTERVAL"),
Value: config.DefaultPollInterval,
}
AlphabetFlag = &cli.StringFlag{
Name: "alphabet",
Usage: "Correct Alphabet Trace (alphabet trace type only)",
......@@ -142,6 +148,7 @@ var requiredFlags = []cli.Flag{
// optionalFlags is a list of unchecked cli flags
var optionalFlags = []cli.Flag{
MaxConcurrencyFlag,
HTTPPollInterval,
AlphabetFlag,
GameAllowlistFlag,
CannonNetworkFlag,
......@@ -247,6 +254,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
GameAllowlist: allowedGames,
GameWindow: ctx.Duration(GameWindowFlag.Name),
MaxConcurrency: maxConcurrency,
PollInterval: ctx.Duration(HTTPPollInterval.Name),
AlphabetTrace: ctx.String(AlphabetFlag.Name),
CannonNetwork: ctx.String(CannonNetworkFlag.Name),
CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name),
......
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
......@@ -66,7 +67,12 @@ func (a *Agent) Act(ctx context.Context) error {
// Perform the actions
for _, action := range actions {
log := a.log.New("action", action.Type, "is_attack", action.IsAttack, "parent", action.ParentIdx, "value", action.Value)
log := a.log.New("action", action.Type, "is_attack", action.IsAttack, "parent", action.ParentIdx)
if action.Type == types.ActionTypeStep {
log = log.New("prestate", common.Bytes2Hex(action.PreState), "proof", common.Bytes2Hex(action.ProofData))
} else {
log = log.New("value", action.Value)
}
if action.OracleData != nil {
a.log.Info("Updating oracle data", "oracleKey", action.OracleData.OracleKey, "oracleData", action.OracleData.OracleData)
......
......@@ -84,6 +84,27 @@ func TestCalculateNextActions(t *testing.T) {
lastHonestClaim.Attack(common.Hash{0xdd}).ExpectStepAttack()
},
},
{
name: "PoisonedPreState",
agreeWithOutputRoot: true,
setupGame: func(builder *faulttest.GameBuilder) {
// A claim hash that has no pre-image
maliciousStateHash := common.Hash{0x01, 0xaa}
// Dishonest actor counters their own claims to set up a situation with an invalid prestate
// The honest actor should attack all claims that support the root claim (disagree with the output root)
builder.Seq().ExpectAttack(). // This expected action is the winning move.
Attack(maliciousStateHash).
Defend(maliciousStateHash).ExpectAttack().
Attack(maliciousStateHash).
Attack(maliciousStateHash).ExpectStepAttack()
// The attempt to step against our malicious leaf node will fail because the pre-state won't match our
// malicious state hash. However, it is the very first expected action, attacking the root claim with
// the correct hash that wins the game since it will be the left-most uncountered claim.
},
},
}
for _, test := range tests {
......@@ -93,7 +114,8 @@ func TestCalculateNextActions(t *testing.T) {
test.setupGame(builder)
game := builder.Game
for i, claim := range game.Claims() {
t.Logf("Claim %v: Pos: %v ParentIdx: %v, Countered: %v, Value: %v", i, claim.Position.ToGIndex(), claim.ParentContractIndex, claim.Countered, claim.Value)
t.Logf("Claim %v: Pos: %v TraceIdx: %v ParentIdx: %v, Countered: %v, Value: %v",
i, claim.Position.ToGIndex(), claim.Position.TraceIndex(maxDepth), claim.ParentContractIndex, claim.Countered, claim.Value)
}
solver := NewGameSolver(maxDepth, claimBuilder.CorrectTraceProvider())
......
......@@ -124,10 +124,7 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
e.logger.Info("Generating trace", "proof", i, "cmd", e.cannon, "args", strings.Join(args, ", "))
execStart := time.Now()
err = e.cmdExecutor(ctx, e.logger.New("proof", i), e.cannon, args...)
if err != nil {
execDuration := time.Since(execStart).Seconds()
e.metrics.RecordCannonExecutionTime(execDuration)
}
e.metrics.RecordCannonExecutionTime(time.Since(execStart).Seconds())
return err
}
......
......@@ -40,7 +40,8 @@ func TestGenerateProof(t *testing.T) {
L2BlockNumber: big.NewInt(3333),
}
captureExec := func(t *testing.T, cfg config.Config, proofAt uint64) (string, string, map[string]string) {
executor := NewExecutor(testlog.Logger(t, log.LvlInfo), metrics.NoopMetrics, &cfg, inputs)
m := &cannonDurationMetrics{}
executor := NewExecutor(testlog.Logger(t, log.LvlInfo), m, &cfg, inputs)
executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) {
return input, nil
}
......@@ -63,6 +64,7 @@ func TestGenerateProof(t *testing.T) {
}
err := executor.GenerateProof(context.Background(), dir, proofAt)
require.NoError(t, err)
require.Equal(t, 1, m.executionTimeRecordCount, "Should record cannon execution time")
return binary, subcommand, args
}
......@@ -211,3 +213,12 @@ func TestFindStartingSnapshot(t *testing.T) {
require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot)
})
}
type cannonDurationMetrics struct {
metrics.NoopMetricsImpl
executionTimeRecordCount int
}
func (c *cannonDurationMetrics) RecordCannonExecutionTime(_ float64) {
c.executionTimeRecordCount++
}
......@@ -22,7 +22,8 @@ import (
)
const (
proofsDir = "proofs"
proofsDir = "proofs"
diskStateCache = "state.json.gz"
)
type proofData struct {
......@@ -52,8 +53,6 @@ type CannonTraceProvider struct {
// lastStep stores the last step in the actual trace if known. 0 indicates unknown.
// Cached as an optimisation to avoid repeatedly attempting to execute beyond the end of the trace.
lastStep uint64
// lastProof stores the proof data to use for all steps extended beyond lastStep
lastProof *proofData
}
func NewTraceProvider(ctx context.Context, logger log.Logger, m CannonMetricer, cfg *config.Config, l1Client bind.ContractCaller, dir string, gameAddr common.Address) (*CannonTraceProvider, error) {
......@@ -138,9 +137,18 @@ func (p *CannonTraceProvider) AbsolutePreStateCommitment(ctx context.Context) (c
// loadProof will attempt to load or generate the proof data at the specified index
// If the requested index is beyond the end of the actual trace it is extended with no-op instructions.
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, error) {
if p.lastProof != nil && i > p.lastStep {
// If the requested index is after the last step in the actual trace, extend the final no-op step
return p.lastProof, nil
// Attempt to read the last step from disk cache
if p.lastStep == 0 {
step, err := readLastStep(p.dir)
if err != nil {
p.logger.Warn("Failed to read last step from disk cache", "err", err)
} else {
p.lastStep = step
}
}
// If the last step is tracked, set i to the last step to generate or load the final proof
if p.lastStep != 0 && i > p.lastStep {
i = p.lastStep
}
path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json.gz", i))
file, err := ioutil.OpenDecompressed(path)
......@@ -176,7 +184,9 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
OracleValue: nil,
OracleOffset: 0,
}
p.lastProof = proof
if err := writeLastStep(p.dir, proof, p.lastStep); err != nil {
p.logger.Warn("Failed to write last step to disk cache", "step", p.lastStep)
}
return proof, nil
} else {
return nil, fmt.Errorf("expected proof not generated but final state was not exited, requested step %v, final state at step %v", i, state.Step)
......@@ -194,3 +204,35 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
}
return &proof, nil
}
type diskStateCacheObj struct {
Step uint64 `json:"step"`
}
// readLastStep reads the tracked last step from disk.
func readLastStep(dir string) (uint64, error) {
state := diskStateCacheObj{}
file, err := ioutil.OpenDecompressed(filepath.Join(dir, diskStateCache))
if err != nil {
return 0, err
}
defer file.Close()
err = json.NewDecoder(file).Decode(&state)
if err != nil {
return 0, err
}
return state.Step, nil
}
// writeLastStep writes the last step and proof to disk as a persistent cache.
func writeLastStep(dir string, proof *proofData, step uint64) error {
state := diskStateCacheObj{Step: step}
lastStepFile := filepath.Join(dir, diskStateCache)
if err := ioutil.WriteCompressedJson(lastStepFile, state); err != nil {
return fmt.Errorf("failed to write last step to %v: %w", lastStepFile, err)
}
if err := ioutil.WriteCompressedJson(filepath.Join(dir, proofsDir, fmt.Sprintf("%d.json.gz", step)), proof); err != nil {
return fmt.Errorf("failed to write proof: %w", err)
}
return nil
}
......@@ -65,8 +65,8 @@ func TestGet(t *testing.T) {
}
func TestGetStepData(t *testing.T) {
dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, data, err := provider.GetStepData(context.Background(), 0)
require.NoError(t, err)
......@@ -80,6 +80,7 @@ func TestGetStepData(t *testing.T) {
})
t.Run("GenerateProof", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
......@@ -105,6 +106,7 @@ func TestGetStepData(t *testing.T) {
})
t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
......@@ -129,7 +131,48 @@ func TestGetStepData(t *testing.T) {
require.Nil(t, data)
})
t.Run("ReadLastStepFromDisk", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, initGenerator := setupWithTestData(t, dataDir, prestate)
initGenerator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
Step: 10,
Exited: true,
}
initGenerator.proof = &proofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
_, _, _, err := provider.GetStepData(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, initGenerator.generated, 7000, "should have tried to generate the proof")
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
Step: 10,
Exited: true,
}
generator.proof = &proofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
}
preimage, proof, data, err := provider.GetStepData(context.Background(), 7000)
require.NoError(t, err)
require.Empty(t, generator.generated, "should not have to generate the proof again")
require.EqualValues(t, initGenerator.finalState.EncodeWitness(), preimage)
require.Empty(t, proof)
require.Nil(t, data)
})
t.Run("MissingStateData", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
_, _, _, err := provider.GetStepData(context.Background(), 1)
require.ErrorContains(t, err, "missing state data")
......@@ -137,6 +180,7 @@ func TestGetStepData(t *testing.T) {
})
t.Run("IgnoreUnknownFields", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, data, err := provider.GetStepData(context.Background(), 2)
require.NoError(t, err)
......
......@@ -9,7 +9,12 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
)
......@@ -32,6 +37,20 @@ type gameMonitor struct {
gameWindow time.Duration
fetchBlockNumber blockNumberFetcher
allowedGames []common.Address
l1HeadsSub ethereum.Subscription
l1Source *headSource
}
type MinimalSubscriber interface {
EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (ethereum.Subscription, error)
}
type headSource struct {
inner MinimalSubscriber
}
func (s *headSource) SubscribeNewHead(ctx context.Context, ch chan<- *ethTypes.Header) (ethereum.Subscription, error) {
return s.inner.EthSubscribe(ctx, ch, "newHeads")
}
func newGameMonitor(
......@@ -42,6 +61,7 @@ func newGameMonitor(
gameWindow time.Duration,
fetchBlockNumber blockNumberFetcher,
allowedGames []common.Address,
l1Source MinimalSubscriber,
) *gameMonitor {
return &gameMonitor{
logger: logger,
......@@ -51,6 +71,7 @@ func newGameMonitor(
gameWindow: gameWindow,
fetchBlockNumber: fetchBlockNumber,
allowedGames: allowedGames,
l1Source: &headSource{inner: l1Source},
}
}
......@@ -99,29 +120,32 @@ func (m *gameMonitor) progressGames(ctx context.Context, blockNum uint64) error
return nil
}
func (m *gameMonitor) MonitorGames(ctx context.Context) error {
m.logger.Info("Monitoring fault dispute games")
func (m *gameMonitor) onNewL1Head(ctx context.Context, sig eth.L1BlockRef) {
if err := m.progressGames(ctx, sig.Number); err != nil {
m.logger.Error("Failed to progress games", "err", err)
}
}
func (m *gameMonitor) resubscribeFunction(ctx context.Context) event.ResubscribeErrFunc {
return func(innerCtx context.Context, err error) (event.Subscription, error) {
if err != nil {
m.logger.Warn("resubscribing after failed L1 subscription", "err", err)
}
return eth.WatchHeadChanges(ctx, m.l1Source, m.onNewL1Head)
}
}
blockNum := uint64(0)
func (m *gameMonitor) MonitorGames(ctx context.Context) error {
m.l1HeadsSub = event.ResubscribeErr(time.Second*10, m.resubscribeFunction(ctx))
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
nextBlockNum, err := m.fetchBlockNumber(ctx)
if err != nil {
m.logger.Error("Failed to load current block number", "err", err)
continue
}
if nextBlockNum > blockNum {
blockNum = nextBlockNum
if err := m.progressGames(ctx, nextBlockNum); err != nil {
m.logger.Error("Failed to progress games", "err", err)
}
}
if err := m.clock.SleepCtx(ctx, time.Second); err != nil {
return nil
case err, ok := <-m.l1HeadsSub.Err():
if !ok {
return err
}
m.logger.Error("L1 subscription error", "err", err)
}
}
}
......@@ -2,35 +2,40 @@ package game
import (
"context"
"fmt"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-service/clock"
)
func TestMonitorMinGameTimestamp(t *testing.T) {
t.Parallel()
t.Run("zero game window returns zero", func(t *testing.T) {
monitor, _, _ := setupMonitorTest(t, []common.Address{})
monitor, _, _, _ := setupMonitorTest(t, []common.Address{})
monitor.gameWindow = time.Duration(0)
require.Equal(t, monitor.minGameTimestamp(), uint64(0))
})
t.Run("non-zero game window with zero clock", func(t *testing.T) {
monitor, _, _ := setupMonitorTest(t, []common.Address{})
monitor, _, _, _ := setupMonitorTest(t, []common.Address{})
monitor.gameWindow = time.Minute
monitor.clock = clock.NewDeterministicClock(time.Unix(0, 0))
require.Equal(t, monitor.minGameTimestamp(), uint64(0))
})
t.Run("minimum computed correctly", func(t *testing.T) {
monitor, _, _ := setupMonitorTest(t, []common.Address{})
monitor, _, _, _ := setupMonitorTest(t, []common.Address{})
monitor.gameWindow = time.Minute
frozen := time.Unix(int64(time.Hour.Seconds()), 0)
monitor.clock = clock.NewDeterministicClock(frozen)
......@@ -39,29 +44,95 @@ func TestMonitorMinGameTimestamp(t *testing.T) {
})
}
func TestMonitorExitsWhenContextDone(t *testing.T) {
monitor, _, _ := setupMonitorTest(t, []common.Address{{}})
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := monitor.MonitorGames(ctx)
require.ErrorIs(t, err, context.Canceled)
// TestMonitorGames tests that the monitor can handle a new head event
// and resubscribe to new heads if the subscription errors.
func TestMonitorGames(t *testing.T) {
t.Run("Schedules games", func(t *testing.T) {
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
monitor, source, sched, mockHeadSource := setupMonitorTest(t, []common.Address{})
source.games = []FaultDisputeGame{newFDG(addr1, 9999), newFDG(addr2, 9999)}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
headerNotSent := true
waitErr := wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) {
if len(sched.scheduled) >= 1 {
return true, nil
}
if mockHeadSource.sub == nil {
return false, nil
}
if headerNotSent {
mockHeadSource.sub.headers <- &ethtypes.Header{
Number: big.NewInt(1),
}
headerNotSent = false
}
return false, nil
})
require.NoError(t, waitErr)
mockHeadSource.err = fmt.Errorf("eth subscribe test error")
cancel()
}()
err := monitor.MonitorGames(ctx)
require.NoError(t, err)
require.Len(t, sched.scheduled, 1)
require.Equal(t, []common.Address{addr1, addr2}, sched.scheduled[0])
})
t.Run("Resubscribes on error", func(t *testing.T) {
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
monitor, source, sched, mockHeadSource := setupMonitorTest(t, []common.Address{})
source.games = []FaultDisputeGame{newFDG(addr1, 9999), newFDG(addr2, 9999)}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
headerNotSent := true
waitErr := wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) {
return mockHeadSource.sub != nil, nil
})
require.NoError(t, waitErr)
mockHeadSource.sub.errChan <- fmt.Errorf("test error")
waitErr = wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) {
if len(sched.scheduled) >= 1 {
return true, nil
}
if mockHeadSource.sub == nil {
return false, nil
}
if headerNotSent {
mockHeadSource.sub.headers <- &ethtypes.Header{
Number: big.NewInt(1),
}
headerNotSent = false
}
return false, nil
})
require.NoError(t, waitErr)
mockHeadSource.err = fmt.Errorf("eth subscribe test error")
cancel()
}()
err := monitor.MonitorGames(ctx)
require.NoError(t, err)
require.Len(t, sched.scheduled, 1)
require.Equal(t, []common.Address{addr1, addr2}, sched.scheduled[0])
})
}
func TestMonitorCreateAndProgressGameAgents(t *testing.T) {
monitor, source, sched := setupMonitorTest(t, []common.Address{})
monitor, source, sched, _ := setupMonitorTest(t, []common.Address{})
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
source.games = []FaultDisputeGame{
{
Proxy: addr1,
Timestamp: 9999,
},
{
Proxy: addr2,
Timestamp: 9999,
},
}
source.games = []FaultDisputeGame{newFDG(addr1, 9999), newFDG(addr2, 9999)}
require.NoError(t, monitor.progressGames(context.Background(), uint64(1)))
......@@ -72,18 +143,8 @@ func TestMonitorCreateAndProgressGameAgents(t *testing.T) {
func TestMonitorOnlyScheduleSpecifiedGame(t *testing.T) {
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
monitor, source, sched := setupMonitorTest(t, []common.Address{addr2})
source.games = []FaultDisputeGame{
{
Proxy: addr1,
Timestamp: 9999,
},
{
Proxy: addr2,
Timestamp: 9999,
},
}
monitor, source, sched, _ := setupMonitorTest(t, []common.Address{addr2})
source.games = []FaultDisputeGame{newFDG(addr1, 9999), newFDG(addr2, 9999)}
require.NoError(t, monitor.progressGames(context.Background(), uint64(1)))
......@@ -91,7 +152,17 @@ func TestMonitorOnlyScheduleSpecifiedGame(t *testing.T) {
require.Equal(t, []common.Address{addr2}, sched.scheduled[0])
}
func setupMonitorTest(t *testing.T, allowedGames []common.Address) (*gameMonitor, *stubGameSource, *stubScheduler) {
func newFDG(proxy common.Address, timestamp uint64) FaultDisputeGame {
return FaultDisputeGame{
Proxy: proxy,
Timestamp: timestamp,
}
}
func setupMonitorTest(
t *testing.T,
allowedGames []common.Address,
) (*gameMonitor, *stubGameSource, *stubScheduler, *mockNewHeadSource) {
logger := testlog.Logger(t, log.LvlDebug)
source := &stubGameSource{}
i := uint64(1)
......@@ -100,15 +171,58 @@ func setupMonitorTest(t *testing.T, allowedGames []common.Address) (*gameMonitor
return i, nil
}
sched := &stubScheduler{}
monitor := newGameMonitor(logger, clock.SystemClock, source, sched, time.Duration(0), fetchBlockNum, allowedGames)
return monitor, source, sched
mockHeadSource := &mockNewHeadSource{}
monitor := newGameMonitor(
logger,
clock.SystemClock,
source,
sched,
time.Duration(0),
fetchBlockNum,
allowedGames,
mockHeadSource,
)
return monitor, source, sched, mockHeadSource
}
type mockNewHeadSource struct {
sub *mockSubscription
err error
}
func (m *mockNewHeadSource) EthSubscribe(
ctx context.Context,
ch any,
args ...any,
) (ethereum.Subscription, error) {
errChan := make(chan error)
m.sub = &mockSubscription{errChan, (ch).(chan<- *ethtypes.Header)}
if m.err != nil {
return nil, m.err
}
return m.sub, nil
}
type mockSubscription struct {
errChan chan error
headers chan<- *ethtypes.Header
}
func (m *mockSubscription) Unsubscribe() {}
func (m *mockSubscription) Err() <-chan error {
return m.errChan
}
type stubGameSource struct {
games []FaultDisputeGame
}
func (s *stubGameSource) FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockNumber *big.Int) ([]FaultDisputeGame, error) {
func (s *stubGameSource) FetchAllGamesAtBlock(
ctx context.Context,
earliest uint64,
blockNumber *big.Int,
) ([]FaultDisputeGame, error) {
return s.games, nil
}
......
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-challenger/version"
opClient "github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/clock"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
......@@ -34,7 +35,7 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se
return nil, fmt.Errorf("failed to create the transaction manager: %w", err)
}
client, err := client.DialEthClientWithTimeout(client.DefaultDialTimeout, logger, cfg.L1EthRpc)
l1Client, err := client.DialEthClientWithTimeout(client.DefaultDialTimeout, logger, cfg.L1EthRpc)
if err != nil {
return nil, fmt.Errorf("failed to dial L1: %w", err)
}
......@@ -57,10 +58,10 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se
logger.Error("error starting metrics server", "err", err)
}
}()
m.StartBalanceMetrics(ctx, logger, client, txMgr.From())
m.StartBalanceMetrics(ctx, logger, l1Client, txMgr.From())
}
factory, err := bindings.NewDisputeGameFactory(cfg.GameFactoryAddress, client)
factory, err := bindings.NewDisputeGameFactory(cfg.GameFactoryAddress, l1Client)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game factory contract: %w", err)
}
......@@ -73,10 +74,14 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se
disk,
cfg.MaxConcurrency,
func(addr common.Address, dir string) (scheduler.GamePlayer, error) {
return fault.NewGamePlayer(ctx, logger, m, cfg, dir, addr, txMgr, client)
return fault.NewGamePlayer(ctx, logger, m, cfg, dir, addr, txMgr, l1Client)
})
monitor := newGameMonitor(logger, cl, loader, sched, cfg.GameWindow, client.BlockNumber, cfg.GameAllowlist)
pollClient, err := opClient.NewRPCWithClient(ctx, logger, cfg.L1EthRpc, opClient.NewBaseRPCClient(l1Client.Client()), cfg.PollInterval)
if err != nil {
return nil, fmt.Errorf("failed to create RPC client: %w", err)
}
monitor := newGameMonitor(logger, cl, loader, sched, cfg.GameWindow, l1Client.BlockNumber, cfg.GameAllowlist, pollClient)
m.RecordInfo(version.SimpleWithMeta)
m.RecordUp()
......
......@@ -4,21 +4,21 @@ import (
txmetrics "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
)
type noopMetrics struct {
type NoopMetricsImpl struct {
txmetrics.NoopTxMetrics
}
var NoopMetrics Metricer = new(noopMetrics)
var NoopMetrics Metricer = new(NoopMetricsImpl)
func (*noopMetrics) RecordInfo(version string) {}
func (*noopMetrics) RecordUp() {}
func (*NoopMetricsImpl) RecordInfo(version string) {}
func (*NoopMetricsImpl) RecordUp() {}
func (*noopMetrics) RecordGameMove() {}
func (*noopMetrics) RecordGameStep() {}
func (*NoopMetricsImpl) RecordGameMove() {}
func (*NoopMetricsImpl) RecordGameStep() {}
func (*noopMetrics) RecordCannonExecutionTime(t float64) {}
func (*NoopMetricsImpl) RecordCannonExecutionTime(t float64) {}
func (*noopMetrics) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {}
func (*NoopMetricsImpl) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {}
func (*noopMetrics) RecordGameUpdateScheduled() {}
func (*noopMetrics) RecordGameUpdateCompleted() {}
func (*NoopMetricsImpl) RecordGameUpdateScheduled() {}
func (*NoopMetricsImpl) RecordGameUpdateCompleted() {}
......@@ -22,6 +22,7 @@ do
IFS=$SAVEIFS # Restore original IFS
GAME_ADDR="${GAME[2]}"
CLAIMS=$(cast call --rpc-url "${RPC}" "${GAME_ADDR}" "claimDataLen() returns(uint256)")
STATUS=$(cast call --rpc-url "${RPC}" "${GAME_ADDR}" "status() return(uint8)" | cast to-dec)
if [[ "${STATUS}" == "0" ]]
then
......@@ -33,5 +34,5 @@ do
then
STATUS="Defender Wins"
fi
echo "${i} Game: ${GAME_ADDR} Type: ${GAME[0]} Created: ${GAME[1]} Status: ${STATUS}"
echo "${i} Game: ${GAME_ADDR} Type: ${GAME[0]} Created: ${GAME[1]} Claims: ${CLAIMS} Status: ${STATUS}"
done
......@@ -145,6 +145,10 @@ func NewChallengerConfig(t *testing.T, l1Endpoint string, options ...Option) *co
_, err := os.Stat(cfg.CannonAbsolutePreState)
require.NoError(t, err, "cannon pre-state should be built. Make sure you've run make cannon-prestate")
}
if cfg.PollInterval == 0 {
cfg.PollInterval = time.Second
}
return &cfg
}
......
......@@ -65,7 +65,7 @@ func (g *FaultGameHelper) MaxDepth(ctx context.Context) int64 {
}
func (g *FaultGameHelper) waitForClaim(ctx context.Context, errorMsg string, predicate func(claim ContractClaim) bool) {
timedCtx, cancel := context.WithTimeout(ctx, time.Minute)
timedCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
err := wait.For(timedCtx, time.Second, func() (bool, error) {
count, err := g.game.ClaimDataLen(&bind.CallOpts{Context: timedCtx})
......@@ -89,6 +89,31 @@ func (g *FaultGameHelper) waitForClaim(ctx context.Context, errorMsg string, pre
}
}
func (g *FaultGameHelper) waitForNoClaim(ctx context.Context, errorMsg string, predicate func(claim ContractClaim) bool) {
timedCtx, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()
err := wait.For(timedCtx, time.Second, func() (bool, error) {
count, err := g.game.ClaimDataLen(&bind.CallOpts{Context: timedCtx})
if err != nil {
return false, fmt.Errorf("retrieve number of claims: %w", err)
}
// Search backwards because the new claims are at the end and more likely the ones we will fail on.
for i := count.Int64() - 1; i >= 0; i-- {
claimData, err := g.game.ClaimData(&bind.CallOpts{Context: timedCtx}, big.NewInt(i))
if err != nil {
return false, fmt.Errorf("retrieve claim %v: %w", i, err)
}
if predicate(claimData) {
return false, nil
}
}
return true, nil
})
if err != nil { // Avoid waiting time capturing game data when there's no error
g.require.NoErrorf(err, "%v\n%v", errorMsg, g.gameData(ctx))
}
}
func (g *FaultGameHelper) GetClaimValue(ctx context.Context, claimIdx int64) common.Hash {
g.WaitForClaimCount(ctx, claimIdx+1)
claim := g.getClaim(ctx, claimIdx)
......@@ -105,6 +130,16 @@ func (g *FaultGameHelper) getClaim(ctx context.Context, claimIdx int64) Contract
return claimData
}
func (g *FaultGameHelper) WaitForClaimAtDepth(ctx context.Context, depth int) {
g.waitForClaim(
ctx,
fmt.Sprintf("Could not find claim depth %v", depth),
func(claim ContractClaim) bool {
pos := types.NewPositionFromGIndex(claim.Position.Uint64())
return pos.Depth() == depth
})
}
func (g *FaultGameHelper) WaitForClaimAtMaxDepth(ctx context.Context, countered bool) {
maxDepth := g.MaxDepth(ctx)
g.waitForClaim(
......@@ -116,6 +151,15 @@ func (g *FaultGameHelper) WaitForClaimAtMaxDepth(ctx context.Context, countered
})
}
func (g *FaultGameHelper) WaitForAllClaimsCountered(ctx context.Context) {
g.waitForNoClaim(
ctx,
"Did not find all claims countered",
func(claim ContractClaim) bool {
return !claim.Countered
})
}
func (g *FaultGameHelper) Resolve(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
......
......@@ -100,7 +100,7 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
l2BlockNumber := h.waitForProposals(ctx)
l1Head := h.checkpointL1Block(ctx)
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
trace := alphabet.NewTraceProvider(claimedAlphabet, alphabetGameDepth)
......
......@@ -341,6 +341,66 @@ func TestCannonProposedOutputRootInvalid(t *testing.T) {
}
}
func TestCannonPoisonedPostState(t *testing.T) {
t.Skip("Known failure case")
InitParallel(t)
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
l1Endpoint := sys.NodeEndpoint("l1")
l2Endpoint := sys.NodeEndpoint("sequencer")
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
game, correctTrace := disputeGameFactory.StartCannonGameWithCorrectRoot(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint,
challenger.WithPrivKey(sys.cfg.Secrets.Mallory),
)
require.NotNil(t, game)
game.LogGameData(ctx)
// Honest first attack at "honest" level
correctTrace.Attack(ctx, 0)
// Honest defense at "dishonest" level
correctTrace.Defend(ctx, 1)
// Dishonest attack at "honest" level - honest move would be to defend
game.Attack(ctx, 2, common.Hash{0x03, 0xaa})
// Start the honest challenger
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Honest",
// Agree with the proposed output, so disagree with the root claim
challenger.WithAgreeProposedOutput(true),
challenger.WithPrivKey(sys.cfg.Secrets.Bob),
)
// Start dishonest challenger that posts correct claims
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "DishonestCorrect",
// Disagree with the proposed output, so agree with the root claim
challenger.WithAgreeProposedOutput(false),
challenger.WithPrivKey(sys.cfg.Secrets.Mallory),
)
// Give the challengers time to progress down the full game depth
depth := game.MaxDepth(ctx)
for i := 3; i <= int(depth); i++ {
game.WaitForClaimAtDepth(ctx, i)
game.LogGameData(ctx)
}
// Wait for all the leaf nodes to be countered
// Wait for the challengers to drive the game down to the leaf node which should be countered
game.WaitForAllClaimsCountered(ctx)
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
}
// setupDisputeGameForInvalidOutputRoot sets up an L2 chain with at least one valid output root followed by an invalid output root.
// A cannon dispute game is started to dispute the invalid output root with the correct root claim provided.
// An honest challenger is run to defend the root claim (ie disagree with the invalid output root).
......
......@@ -80,9 +80,11 @@ func NewRPC(ctx context.Context, lgr log.Logger, addr string, opts ...RPCOption)
return nil, fmt.Errorf("rpc option %d failed to apply to RPC config: %w", i, err)
}
}
if cfg.backoffAttempts < 1 { // default to at least 1 attempt, or it always fails to dial.
cfg.backoffAttempts = 1
}
underlying, err := dialRPCClientWithBackoff(ctx, lgr, addr, cfg.backoffAttempts, cfg.gethRPCOptions...)
if err != nil {
return nil, err
......@@ -94,11 +96,15 @@ func NewRPC(ctx context.Context, lgr log.Logger, addr string, opts ...RPCOption)
wrapped = NewRateLimitingClient(wrapped, rate.Limit(cfg.limit), cfg.burst)
}
return NewRPCWithClient(ctx, lgr, addr, wrapped, cfg.httpPollInterval)
}
// NewRPCWithClient builds a new polling client with the given underlying RPC client.
func NewRPCWithClient(ctx context.Context, lgr log.Logger, addr string, underlying RPC, pollInterval time.Duration) (RPC, error) {
if httpRegex.MatchString(addr) {
wrapped = NewPollingClient(ctx, lgr, wrapped, WithPollRate(cfg.httpPollInterval))
underlying = NewPollingClient(ctx, lgr, underlying, WithPollRate(pollInterval))
}
return wrapped, nil
return underlying, nil
}
// Dials a JSON-RPC endpoint repeatedly, with a backoff, until a client connection is established. Auth is optional.
......
......@@ -2,6 +2,7 @@ package ioutil
import (
"compress/gzip"
"encoding/json"
"fmt"
"io"
"os"
......@@ -38,6 +39,20 @@ func OpenCompressed(file string, flag int, perm os.FileMode) (io.WriteCloser, er
return out, nil
}
// WriteCompressedJson writes the object to the specified file as a compressed json object
// if the filename ends with .gz.
func WriteCompressedJson(file string, obj any) error {
if !IsGzip(file) {
return fmt.Errorf("file %v does not have .gz extension", file)
}
out, err := OpenCompressed(file, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer out.Close()
return json.NewEncoder(out).Encode(obj)
}
// IsGzip determines if a path points to a gzip compressed file.
// Returns true when the file has a .gz extension.
func IsGzip(path string) bool {
......
package ioutil
import (
"encoding/json"
"io"
"os"
"path/filepath"
......@@ -47,3 +48,43 @@ func TestReadWriteWithOptionalCompression(t *testing.T) {
})
}
}
func TestWriteReadCompressedJson(t *testing.T) {
tests := []struct {
name string
filename string
err string
}{
{"Uncompressed", "test.notgz", "does not have .gz extension"},
{"Gzipped", "test.gz", ""},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, test.filename)
err := WriteCompressedJson(path, struct {
A int
B string
}{A: 1, B: "test"})
if test.err != "" {
require.ErrorContains(t, err, test.err)
return
}
require.NoError(t, err)
var read struct {
A int
B string
}
in, err := OpenDecompressed(path)
require.NoError(t, err)
err = json.NewDecoder(in).Decode(&read)
require.NoError(t, err)
require.Equal(t, struct {
A int
B string
}{A: 1, B: "test"}, read)
})
}
}
......@@ -211,14 +211,8 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*
}
gasFeeCap := calcGasFeeCap(basefee, gasTipCap)
nonce, err := m.nextNonce(ctx)
if err != nil {
return nil, err
}
rawTx := &types.DynamicFeeTx{
ChainID: m.chainID,
Nonce: nonce,
To: candidate.To,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
......@@ -247,6 +241,13 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*
rawTx.Gas = gas
}
// Avoid bumping the nonce if the gas estimation fails.
nonce, err := m.nextNonce(ctx)
if err != nil {
return nil, err
}
rawTx.Nonce = nonce
ctx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
return m.cfg.Signer(ctx, m.cfg.From, types.NewTx(rawTx))
......
......@@ -93,6 +93,7 @@ type gasPricer struct {
mineAtEpoch int64
baseGasTipFee *big.Int
baseBaseFee *big.Int
err error
mu sync.Mutex
}
......@@ -206,6 +207,9 @@ func (b *mockBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*typ
}
func (b *mockBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
if b.g.err != nil {
return 0, b.g.err
}
return b.g.basefee().Uint64(), nil
}
......@@ -420,6 +424,31 @@ func TestTxMgr_EstimateGas(t *testing.T) {
require.Equal(t, gasEstimate, tx.Gas())
}
func TestTxMgr_EstimateGasFails(t *testing.T) {
t.Parallel()
h := newTestHarness(t)
candidate := h.createTxCandidate()
// Set the gas limit to zero to trigger gas estimation.
candidate.GasLimit = 0
// Craft a successful transaction.
tx, err := h.mgr.craftTx(context.Background(), candidate)
require.Nil(t, err)
lastNonce := tx.Nonce()
// Mock gas estimation failure.
h.gasPricer.err = fmt.Errorf("execution error")
_, err = h.mgr.craftTx(context.Background(), candidate)
require.ErrorContains(t, err, "failed to estimate gas")
// Ensure successful craft uses the correct nonce
h.gasPricer.err = nil
tx, err = h.mgr.craftTx(context.Background(), candidate)
require.Nil(t, err)
require.Equal(t, lastNonce+1, tx.Nonce())
}
// TestTxMgrOnlyOnePublicationSucceeds asserts that the tx manager will return a
// receipt so long as at least one of the publications is able to succeed with a
// simulated rpc failure.
......
......@@ -48,7 +48,7 @@ contract Deploy is Deployer {
/// @notice The create2 salt used for deployment of the contract implementations.
/// Using this helps to reduce config across networks as the implementation
/// addresses will be the same across networks when deployed with create2.
bytes32 constant IMPL_SALT = bytes32("ether's phoenix");
bytes32 constant IMPL_SALT = keccak256(bytes("ether's phoenix"));
/// @notice The name of the script, used to ensure the right deploy artifacts
/// are used.
......
......@@ -54,7 +54,7 @@
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"ts-node": "^10.9.1",
"typedoc": "^0.24.8",
"typedoc": "^0.25.1",
"typescript": "^5.1.6",
"viem": "^1.6.0",
"vitest": "^0.34.2",
......
......@@ -15,6 +15,7 @@ import { IBridgeAdapter } from './bridge-adapter'
export enum L1ChainID {
MAINNET = 1,
GOERLI = 5,
SEPOLIA = 11155111,
HARDHAT_LOCAL = 31337,
BEDROCK_LOCAL_DEVNET = 900,
}
......@@ -25,6 +26,7 @@ export enum L1ChainID {
export enum L2ChainID {
OPTIMISM = 10,
OPTIMISM_GOERLI = 420,
OPTIMISM_SEPOLIA = 11155420,
OPTIMISM_HARDHAT_LOCAL = 31337,
OPTIMISM_HARDHAT_DEVNET = 17,
OPTIMISM_BEDROCK_ALPHA_TESTNET = 28528,
......
......@@ -2,50 +2,62 @@ import { predeploys } from '@eth-optimism/core-utils'
import { ethers } from 'ethers'
import portalArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/OptimismPortalProxy.json'
import portalArtifactsGoerli from '@eth-optimism/contracts-bedrock/deployments/goerli/OptimismPortalProxy.json'
import portalArtifactsSepolia from '@eth-optimism/contracts-bedrock/deployments/sepolia/OptimismPortalProxy.json'
import l2OutputOracleArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L2OutputOracleProxy.json'
import l2OutputOracleArtifactsGoerli from '@eth-optimism/contracts-bedrock/deployments/goerli/L2OutputOracleProxy.json'
import l2OutputOracleArtifactsSepolia from '@eth-optimism/contracts-bedrock/deployments/sepolia/L2OutputOracleProxy.json'
import addressManagerArtifactMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/AddressManager.json'
import addressManagerArtifactGoerli from '@eth-optimism/contracts-bedrock/deployments/goerli/AddressManager.json'
import addressManagerArtifactSepolia from '@eth-optimism/contracts-bedrock/deployments/sepolia/AddressManager.json'
import l1StandardBridgeArtifactMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L1StandardBridgeProxy.json'
import l1StandardBridgeArtifactGoerli from '@eth-optimism/contracts-bedrock/deployments/goerli/L1StandardBridgeProxy.json'
import l1StandardBridgeArtifactSepolia from '@eth-optimism/contracts-bedrock/deployments/sepolia/L1StandardBridgeProxy.json'
import l1CrossDomainMessengerArtifactMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L1CrossDomainMessengerProxy.json'
import l1CrossDomainMessengerArtifactGoerli from '@eth-optimism/contracts-bedrock/deployments/goerli/L1CrossDomainMessengerProxy.json'
import l1CrossDomainMessengerArtifactSepolia from '@eth-optimism/contracts-bedrock/deployments/sepolia/L1CrossDomainMessengerProxy.json'
const portalAddresses = {
mainnet: portalArtifactsMainnet.address,
goerli: portalArtifactsGoerli.address,
sepolia: portalArtifactsSepolia.address,
}
const l2OutputOracleAddresses = {
mainnet: l2OutputOracleArtifactsMainnet.address,
goerli: l2OutputOracleArtifactsGoerli.address,
sepolia: l2OutputOracleArtifactsSepolia.address,
}
const addressManagerAddresses = {
mainnet: addressManagerArtifactMainnet.address,
goerli: addressManagerArtifactGoerli.address,
sepolia: addressManagerArtifactSepolia.address,
}
const l1StandardBridgeAddresses = {
mainnet: l1StandardBridgeArtifactMainnet.address,
goerli: l1StandardBridgeArtifactGoerli.address,
sepolia: l1StandardBridgeArtifactSepolia.address,
}
const l1CrossDomainMessengerAddresses = {
mainnet: l1CrossDomainMessengerArtifactMainnet.address,
goerli: l1CrossDomainMessengerArtifactGoerli.address,
sepolia: l1CrossDomainMessengerArtifactSepolia.address,
}
// legacy
const stateCommitmentChainAddresses = {
mainnet: '0xBe5dAb4A2e9cd0F27300dB4aB94BeE3A233AEB19',
goerli: '0x9c945aC97Baf48cB784AbBB61399beB71aF7A378',
sepolia: ethers.constants.AddressZero,
}
// legacy
const canonicalTransactionChainAddresses = {
mainnet: '0x5E4e65926BA27467555EB562121fac00D24E9dD2',
goerli: '0x607F755149cFEB3a14E1Dc3A4E2450Cde7dfb04D',
sepolia: ethers.constants.AddressZero,
}
import {
......@@ -67,6 +79,7 @@ export const DEPOSIT_CONFIRMATION_BLOCKS: {
} = {
[L2ChainID.OPTIMISM]: 50 as const,
[L2ChainID.OPTIMISM_GOERLI]: 12 as const,
[L2ChainID.OPTIMISM_SEPOLIA]: 12 as const,
[L2ChainID.OPTIMISM_HARDHAT_LOCAL]: 2 as const,
[L2ChainID.OPTIMISM_HARDHAT_DEVNET]: 2 as const,
[L2ChainID.OPTIMISM_BEDROCK_ALPHA_TESTNET]: 12 as const,
......@@ -81,6 +94,7 @@ export const CHAIN_BLOCK_TIMES: {
} = {
[L1ChainID.MAINNET]: 13 as const,
[L1ChainID.GOERLI]: 15 as const,
[L1ChainID.SEPOLIA]: 15 as const,
[L1ChainID.HARDHAT_LOCAL]: 1 as const,
[L1ChainID.BEDROCK_LOCAL_DEVNET]: 15 as const,
}
......@@ -137,6 +151,10 @@ export const CONTRACT_ADDRESSES: {
l1: getL1ContractsByNetworkName('goerli'),
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
[L2ChainID.OPTIMISM_SEPOLIA]: {
l1: getL1ContractsByNetworkName('sepolia'),
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
[L2ChainID.OPTIMISM_HARDHAT_LOCAL]: {
l1: {
AddressManager: '0x5FbDB2315678afecb367f032d93F642f64180aa3' as const,
......
......@@ -17,7 +17,7 @@ importers:
devDependencies:
'@babel/eslint-parser':
specifier: ^7.18.2
version: 7.22.15(@babel/core@7.22.10)(eslint@8.47.0)
version: 7.22.15(@babel/core@7.22.10)(eslint@8.49.0)
'@changesets/changelog-github':
specifier: ^0.4.8
version: 0.4.8
......@@ -35,55 +35,55 @@ importers:
version: 10.0.1
'@types/node':
specifier: ^20.5.3
version: 20.5.3
version: 20.6.0
'@typescript-eslint/eslint-plugin':
specifier: ^6.7.0
version: 6.7.0(@typescript-eslint/parser@5.60.1)(eslint@8.47.0)(typescript@5.1.6)
version: 6.7.0(@typescript-eslint/parser@6.7.0)(eslint@8.49.0)(typescript@5.1.6)
'@typescript-eslint/parser':
specifier: ^5.60.1
version: 5.60.1(eslint@8.47.0)(typescript@5.1.6)
specifier: ^6.7.0
version: 6.7.0(eslint@8.49.0)(typescript@5.1.6)
chai:
specifier: ^4.2.0
version: 4.3.7
depcheck:
specifier: ^1.4.3
version: 1.4.3
version: 1.4.6
doctoc:
specifier: ^2.2.0
version: 2.2.0
version: 2.2.1
eslint:
specifier: ^8.43.0
version: 8.47.0
version: 8.49.0
eslint-config-prettier:
specifier: ^8.3.0
version: 8.3.0(eslint@8.47.0)
version: 8.3.0(eslint@8.49.0)
eslint-config-standard:
specifier: ^16.0.3
version: 16.0.3(eslint-plugin-import@2.28.1)(eslint-plugin-node@11.1.0)(eslint-plugin-promise@5.2.0)(eslint@8.47.0)
version: 16.0.3(eslint-plugin-import@2.28.1)(eslint-plugin-node@11.1.0)(eslint-plugin-promise@5.2.0)(eslint@8.49.0)
eslint-plugin-import:
specifier: ^2.26.0
version: 2.28.1(@typescript-eslint/parser@5.60.1)(eslint@8.47.0)
version: 2.28.1(@typescript-eslint/parser@6.7.0)(eslint@8.49.0)
eslint-plugin-jsdoc:
specifier: ^35.1.2
version: 35.5.1(eslint@8.47.0)
version: 35.5.1(eslint@8.49.0)
eslint-plugin-node:
specifier: ^11.1.0
version: 11.1.0(eslint@8.47.0)
version: 11.1.0(eslint@8.49.0)
eslint-plugin-prefer-arrow:
specifier: ^1.2.3
version: 1.2.3(eslint@8.47.0)
version: 1.2.3(eslint@8.49.0)
eslint-plugin-prettier:
specifier: ^4.0.0
version: 4.2.1(eslint-config-prettier@8.3.0)(eslint@8.47.0)(prettier@2.8.8)
version: 4.2.1(eslint-config-prettier@8.3.0)(eslint@8.49.0)(prettier@2.8.8)
eslint-plugin-promise:
specifier: ^5.1.0
version: 5.2.0(eslint@8.47.0)
version: 5.2.0(eslint@8.49.0)
eslint-plugin-react:
specifier: ^7.24.0
version: 7.33.2(eslint@8.47.0)
version: 7.33.2(eslint@8.49.0)
eslint-plugin-unicorn:
specifier: ^42.0.0
version: 42.0.0(eslint@8.47.0)
version: 42.0.0(eslint@8.49.0)
husky:
specifier: ^8.0.3
version: 8.0.3
......@@ -175,7 +175,7 @@ importers:
version: 2.17.2(ts-node@10.9.1)(typescript@5.2.2)
ts-node:
specifier: ^10.9.1
version: 10.9.1(@types/node@20.5.3)(typescript@5.2.2)
version: 10.9.1(@types/node@20.6.0)(typescript@5.2.2)
tsx:
specifier: ^3.12.7
version: 3.12.7
......@@ -260,10 +260,10 @@ importers:
devDependencies:
'@typescript-eslint/eslint-plugin':
specifier: ^6.7.0
version: 6.7.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.1.6)
version: 6.7.0(@typescript-eslint/parser@6.4.0)(eslint@8.49.0)(typescript@5.1.6)
'@typescript-eslint/parser':
specifier: ^6.4.0
version: 6.4.0(eslint@8.47.0)(typescript@5.1.6)
version: 6.4.0(eslint@8.49.0)(typescript@5.1.6)
tsx:
specifier: ^3.12.7
version: 3.12.7
......@@ -333,7 +333,7 @@ importers:
version: 5.1.6
vite:
specifier: ^4.4.6
version: 4.4.6(@types/node@20.5.3)
version: 4.4.6(@types/node@20.6.0)
vitest:
specifier: ^0.34.2
version: 0.34.2(jsdom@22.1.0)
......@@ -427,7 +427,7 @@ importers:
version: 1.3.1(typescript@5.1.6)
vite:
specifier: ^4.4.6
version: 4.4.6(@types/node@20.5.3)
version: 4.4.6(@types/node@20.6.0)
vitest:
specifier: ^0.34.2
version: 0.34.2(jsdom@22.1.0)
......@@ -508,8 +508,8 @@ importers:
specifier: ^10.9.1
version: 10.9.1(@types/node@20.5.0)(typescript@5.1.6)
typedoc:
specifier: ^0.24.8
version: 0.24.8(typescript@5.1.6)
specifier: ^0.25.1
version: 0.25.1(typescript@5.1.6)
typescript:
specifier: ^5.1.6
version: 5.1.6
......@@ -558,7 +558,7 @@ importers:
version: 1.6.0(typescript@5.1.6)(zod@3.22.0)
vite:
specifier: ^4.4.9
version: 4.4.9(@types/node@20.5.3)
version: 4.4.9(@types/node@20.6.0)
vitest:
specifier: ^0.34.1
version: 0.34.1
......@@ -623,7 +623,7 @@ packages:
- supports-color
dev: true
/@babel/eslint-parser@7.22.15(@babel/core@7.22.10)(eslint@8.47.0):
/@babel/eslint-parser@7.22.15(@babel/core@7.22.10)(eslint@8.49.0):
resolution: {integrity: sha512-yc8OOBIQk1EcRrpizuARSQS0TWAcOMpEJ1aafhNznaeYkeL+OhqnDObGFylB8ka8VFF/sZc+S4RzHyO+3LjQxg==}
engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0}
peerDependencies:
......@@ -632,7 +632,7 @@ packages:
dependencies:
'@babel/core': 7.22.10
'@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1
eslint: 8.47.0
eslint: 8.49.0
eslint-visitor-keys: 2.1.0
semver: 6.3.1
dev: true
......@@ -751,16 +751,16 @@ packages:
chalk: 2.4.2
js-tokens: 4.0.0
/@babel/parser@7.16.4:
resolution: {integrity: sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==}
/@babel/parser@7.22.10:
resolution: {integrity: sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.22.10
dev: true
/@babel/parser@7.22.10:
resolution: {integrity: sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==}
/@babel/parser@7.22.5:
resolution: {integrity: sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
......@@ -789,8 +789,8 @@ packages:
'@babel/types': 7.22.10
dev: true
/@babel/traverse@7.18.2:
resolution: {integrity: sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==}
/@babel/traverse@7.22.10:
resolution: {integrity: sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.22.10
......@@ -807,8 +807,8 @@ packages:
- supports-color
dev: true
/@babel/traverse@7.22.10:
resolution: {integrity: sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==}
/@babel/traverse@7.22.5:
resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.22.10
......@@ -1591,13 +1591,13 @@ packages:
dev: true
optional: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.47.0):
/@eslint-community/eslint-utils@4.4.0(eslint@8.49.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
dependencies:
eslint: 8.47.0
eslint: 8.49.0
eslint-visitor-keys: 3.4.3
dev: true
......@@ -1623,8 +1623,8 @@ packages:
- supports-color
dev: true
/@eslint/js@8.47.0:
resolution: {integrity: sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==}
/@eslint/js@8.49.0:
resolution: {integrity: sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
......@@ -2222,8 +2222,8 @@ packages:
'@trufflesuite/bigint-buffer': 1.1.9
dev: true
/@humanwhocodes/config-array@0.11.10:
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
/@humanwhocodes/config-array@0.11.11:
resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==}
engines: {node: '>=10.10.0'}
dependencies:
'@humanwhocodes/object-schema': 1.2.1
......@@ -3707,20 +3707,20 @@ packages:
/@types/bn.js@4.11.6:
resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/bn.js@5.1.0:
resolution: {integrity: sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/body-parser@1.19.1:
resolution: {integrity: sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==}
dependencies:
'@types/connect': 3.4.35
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/chai-as-promised@7.1.5:
......@@ -3746,7 +3746,7 @@ packages:
/@types/connect@3.4.35:
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
/@types/dateformat@5.0.0:
resolution: {integrity: sha512-SZg4JdHIWHQGEokbYGZSDvo5wA4TLYPXaqhigs/wH+REDOejcJzgH+qyY+HtEUtWOZxEUkbhbdYPqQDiEgrXeA==}
......@@ -3760,7 +3760,7 @@ packages:
/@types/express-serve-static-core@4.17.35:
resolution: {integrity: sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
'@types/qs': 6.9.7
'@types/range-parser': 1.2.4
'@types/send': 0.17.1
......@@ -3779,7 +3779,7 @@ packages:
resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==}
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 20.5.0
'@types/node': 20.6.0
dev: true
/@types/is-ci@3.0.0:
......@@ -3806,7 +3806,7 @@ packages:
dependencies:
'@types/abstract-leveldown': 5.0.2
'@types/level-errors': 3.0.0
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/lru-cache@5.1.1:
......@@ -3838,7 +3838,7 @@ packages:
/@types/mkdirp@0.5.2:
resolution: {integrity: sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/mocha@10.0.1:
......@@ -3848,7 +3848,7 @@ packages:
/@types/morgan@1.9.4:
resolution: {integrity: sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==}
dependencies:
'@types/node': 20.5.0
'@types/node': 20.6.0
dev: true
/@types/ms@0.7.31:
......@@ -3857,7 +3857,7 @@ packages:
/@types/node-fetch@2.6.4:
resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
form-data: 3.0.1
dev: true
......@@ -3872,8 +3872,8 @@ packages:
resolution: {integrity: sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q==}
dev: true
/@types/node@20.5.3:
resolution: {integrity: sha512-ITI7rbWczR8a/S6qjAW7DMqxqFMjjTo61qZVWJ1ubPvbIQsL5D/TvwjYEalM8Kthpe3hTzOGrF2TGbAu2uyqeA==}
/@types/node@20.6.0:
resolution: {integrity: sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==}
/@types/normalize-package-data@2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
......@@ -3885,7 +3885,7 @@ packages:
/@types/pbkdf2@3.1.0:
resolution: {integrity: sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/pino-multi-stream@5.1.3:
......@@ -3903,13 +3903,13 @@ packages:
/@types/pino-std-serializers@2.4.1:
resolution: {integrity: sha512-17XcksO47M24IVTVKPeAByWUd3Oez7EbIjXpSbzMPhXVzgjGtrOa49gKBwxH9hb8dKv58OelsWQ+A1G1l9S3wQ==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/pino@6.3.11:
resolution: {integrity: sha512-S7+fLONqSpHeW9d7TApUqO6VN47KYgOXhCNKwGBVLHObq8HhaAYlVqUNdfnvoXjCMiwE5xcPm/5R2ZUh8bgaXQ==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
'@types/pino-pretty': 4.7.1
'@types/pino-std-serializers': 2.4.1
sonic-boom: 2.8.0
......@@ -3955,7 +3955,7 @@ packages:
/@types/readable-stream@2.3.15:
resolution: {integrity: sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
safe-buffer: 5.1.2
dev: true
......@@ -3966,7 +3966,7 @@ packages:
/@types/secp256k1@4.0.3:
resolution: {integrity: sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/seedrandom@3.0.1:
......@@ -3985,14 +3985,14 @@ packages:
resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==}
dependencies:
'@types/mime': 1.3.2
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/serve-static@1.13.10:
resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==}
dependencies:
'@types/mime': 1.3.2
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@types/sinon-chai@3.2.5:
......@@ -4029,21 +4029,21 @@ packages:
/@types/ws@7.4.7:
resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
/@types/ws@8.5.3:
resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: false
/@types/ws@8.5.5:
resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==}
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
dev: true
/@typescript-eslint/eslint-plugin@6.7.0(@typescript-eslint/parser@5.60.1)(eslint@8.47.0)(typescript@5.1.6):
/@typescript-eslint/eslint-plugin@6.7.0(@typescript-eslint/parser@6.4.0)(eslint@8.49.0)(typescript@5.1.6):
resolution: {integrity: sha512-gUqtknHm0TDs1LhY12K2NA3Rmlmp88jK9Tx8vGZMfHeNMLE3GH2e9TRub+y+SOjuYgtOmok+wt1AyDPZqxbNag==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
......@@ -4055,13 +4055,13 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.6.2
'@typescript-eslint/parser': 5.60.1(eslint@8.47.0)(typescript@5.1.6)
'@typescript-eslint/parser': 6.4.0(eslint@8.49.0)(typescript@5.1.6)
'@typescript-eslint/scope-manager': 6.7.0
'@typescript-eslint/type-utils': 6.7.0(eslint@8.47.0)(typescript@5.1.6)
'@typescript-eslint/utils': 6.7.0(eslint@8.47.0)(typescript@5.1.6)
'@typescript-eslint/type-utils': 6.7.0(eslint@8.49.0)(typescript@5.1.6)
'@typescript-eslint/utils': 6.7.0(eslint@8.49.0)(typescript@5.1.6)
'@typescript-eslint/visitor-keys': 6.7.0
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.47.0
eslint: 8.49.0
graphemer: 1.4.0
ignore: 5.2.4
natural-compare: 1.4.0
......@@ -4072,7 +4072,7 @@ packages:
- supports-color
dev: true
/@typescript-eslint/eslint-plugin@6.7.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.1.6):
/@typescript-eslint/eslint-plugin@6.7.0(@typescript-eslint/parser@6.7.0)(eslint@8.49.0)(typescript@5.1.6):
resolution: {integrity: sha512-gUqtknHm0TDs1LhY12K2NA3Rmlmp88jK9Tx8vGZMfHeNMLE3GH2e9TRub+y+SOjuYgtOmok+wt1AyDPZqxbNag==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
......@@ -4084,13 +4084,13 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.6.2
'@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.1.6)
'@typescript-eslint/parser': 6.7.0(eslint@8.49.0)(typescript@5.1.6)
'@typescript-eslint/scope-manager': 6.7.0
'@typescript-eslint/type-utils': 6.7.0(eslint@8.47.0)(typescript@5.1.6)
'@typescript-eslint/utils': 6.7.0(eslint@8.47.0)(typescript@5.1.6)
'@typescript-eslint/type-utils': 6.7.0(eslint@8.49.0)(typescript@5.1.6)
'@typescript-eslint/utils': 6.7.0(eslint@8.49.0)(typescript@5.1.6)
'@typescript-eslint/visitor-keys': 6.7.0
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.47.0
eslint: 8.49.0
graphemer: 1.4.0
ignore: 5.2.4
natural-compare: 1.4.0
......@@ -4101,28 +4101,29 @@ packages:
- supports-color
dev: true
/@typescript-eslint/parser@5.60.1(eslint@8.47.0)(typescript@5.1.6):
resolution: {integrity: sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
/@typescript-eslint/parser@6.4.0(eslint@8.49.0)(typescript@5.1.6):
resolution: {integrity: sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
eslint: ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 5.60.1
'@typescript-eslint/types': 5.60.1
'@typescript-eslint/typescript-estree': 5.60.1(typescript@5.1.6)
'@typescript-eslint/scope-manager': 6.4.0
'@typescript-eslint/types': 6.4.0
'@typescript-eslint/typescript-estree': 6.4.0(typescript@5.1.6)
'@typescript-eslint/visitor-keys': 6.4.0
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.47.0
eslint: 8.49.0
typescript: 5.1.6
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@6.4.0(eslint@8.47.0)(typescript@5.1.6):
resolution: {integrity: sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==}
/@typescript-eslint/parser@6.7.0(eslint@8.49.0)(typescript@5.1.6):
resolution: {integrity: sha512-jZKYwqNpNm5kzPVP5z1JXAuxjtl2uG+5NpaMocFPTNC2EdYIgbXIPImObOkhbONxtFTTdoZstLZefbaK+wXZng==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
......@@ -4131,25 +4132,17 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 6.4.0
'@typescript-eslint/types': 6.4.0
'@typescript-eslint/typescript-estree': 6.4.0(typescript@5.1.6)
'@typescript-eslint/visitor-keys': 6.4.0
'@typescript-eslint/scope-manager': 6.7.0
'@typescript-eslint/types': 6.7.0
'@typescript-eslint/typescript-estree': 6.7.0(typescript@5.1.6)
'@typescript-eslint/visitor-keys': 6.7.0
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.47.0
eslint: 8.49.0
typescript: 5.1.6
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/scope-manager@5.60.1:
resolution: {integrity: sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
'@typescript-eslint/types': 5.60.1
'@typescript-eslint/visitor-keys': 5.60.1
dev: true
/@typescript-eslint/scope-manager@6.4.0:
resolution: {integrity: sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig==}
engines: {node: ^16.0.0 || >=18.0.0}
......@@ -4166,7 +4159,7 @@ packages:
'@typescript-eslint/visitor-keys': 6.7.0
dev: true
/@typescript-eslint/type-utils@6.7.0(eslint@8.47.0)(typescript@5.1.6):
/@typescript-eslint/type-utils@6.7.0(eslint@8.49.0)(typescript@5.1.6):
resolution: {integrity: sha512-f/QabJgDAlpSz3qduCyQT0Fw7hHpmhOzY/Rv6zO3yO+HVIdPfIWhrQoAyG+uZVtWAIS85zAyzgAFfyEr+MgBpg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
......@@ -4177,20 +4170,15 @@ packages:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 6.7.0(typescript@5.1.6)
'@typescript-eslint/utils': 6.7.0(eslint@8.47.0)(typescript@5.1.6)
'@typescript-eslint/utils': 6.7.0(eslint@8.49.0)(typescript@5.1.6)
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.47.0
eslint: 8.49.0
ts-api-utils: 1.0.1(typescript@5.1.6)
typescript: 5.1.6
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/types@5.60.1:
resolution: {integrity: sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/@typescript-eslint/types@6.4.0:
resolution: {integrity: sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg==}
engines: {node: ^16.0.0 || >=18.0.0}
......@@ -4201,27 +4189,6 @@ packages:
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
/@typescript-eslint/typescript-estree@5.60.1(typescript@5.1.6):
resolution: {integrity: sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/types': 5.60.1
'@typescript-eslint/visitor-keys': 5.60.1
debug: 4.3.4(supports-color@8.1.1)
globby: 11.1.0
is-glob: 4.0.3
semver: 7.5.4
tsutils: 3.21.0(typescript@5.1.6)
typescript: 5.1.6
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/typescript-estree@6.4.0(typescript@5.1.6):
resolution: {integrity: sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA==}
engines: {node: ^16.0.0 || >=18.0.0}
......@@ -4264,33 +4231,25 @@ packages:
- supports-color
dev: true
/@typescript-eslint/utils@6.7.0(eslint@8.47.0)(typescript@5.1.6):
/@typescript-eslint/utils@6.7.0(eslint@8.49.0)(typescript@5.1.6):
resolution: {integrity: sha512-MfCq3cM0vh2slSikQYqK2Gq52gvOhe57vD2RM3V4gQRZYX4rDPnKLu5p6cm89+LJiGlwEXU8hkYxhqqEC/V3qA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0)
'@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0)
'@types/json-schema': 7.0.12
'@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 6.7.0
'@typescript-eslint/types': 6.7.0
'@typescript-eslint/typescript-estree': 6.7.0(typescript@5.1.6)
eslint: 8.47.0
eslint: 8.49.0
semver: 7.5.4
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/@typescript-eslint/visitor-keys@5.60.1:
resolution: {integrity: sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
'@typescript-eslint/types': 5.60.1
eslint-visitor-keys: 3.4.3
dev: true
/@typescript-eslint/visitor-keys@6.4.0:
resolution: {integrity: sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA==}
engines: {node: ^16.0.0 || >=18.0.0}
......@@ -5394,10 +5353,10 @@ packages:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
/anchor-markdown-header@0.5.7:
resolution: {integrity: sha512-AmikqcK15r3q99hPvTa1na9n3eLkW0uE+RL9BZMSgwYalQeDnNXbYrN06BIcBPfGlmsGIE2jvkuvl/x0hyPF5Q==}
/anchor-markdown-header@0.6.0:
resolution: {integrity: sha512-v7HJMtE1X7wTpNFseRhxsY/pivP4uAJbidVhPT+yhz4i/vV1+qx371IXuV9V7bN6KjFtheLJxqaSm0Y/8neJTA==}
dependencies:
emoji-regex: 6.1.3
emoji-regex: 10.1.0
dev: true
/ansi-colors@4.1.1:
......@@ -6066,6 +6025,10 @@ packages:
function-bind: 1.1.1
get-intrinsic: 1.2.1
/callsite@1.0.0:
resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==}
dev: true
/callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
......@@ -6821,32 +6784,32 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
/depcheck@1.4.3:
resolution: {integrity: sha512-vy8xe1tlLFu7t4jFyoirMmOR7x7N601ubU9Gkifyr9z8rjBFtEdWHDBMqXyk6OkK+94NXutzddVXJuo0JlUQKQ==}
/depcheck@1.4.6:
resolution: {integrity: sha512-Jxy9+u1DE+Svj2N0V/ueEQiOgH2X3KRPAsBfM0m/vCtuiG5QSC//b1mt0rbN/u3BFFEzXqpHzYiwDjmvAydEsw==}
engines: {node: '>=10'}
hasBin: true
dependencies:
'@babel/parser': 7.16.4
'@babel/traverse': 7.18.2
'@babel/parser': 7.22.5
'@babel/traverse': 7.22.5
'@vue/compiler-sfc': 3.2.36
callsite: 1.0.0
camelcase: 6.3.0
cosmiconfig: 7.0.1
debug: 4.3.4(supports-color@8.1.1)
deps-regex: 0.1.4
findup-sync: 5.0.0
ignore: 5.2.4
is-core-module: 2.12.1
is-core-module: 2.13.0
js-yaml: 3.14.1
json5: 2.2.3
lodash: 4.17.21
minimatch: 3.1.2
multimatch: 5.0.0
please-upgrade-node: 3.2.0
query-ast: 1.0.4
readdirp: 3.6.0
require-package-name: 2.0.1
resolve: 1.22.2
sass: 1.52.1
scss-parser: 1.0.5
resolve: 1.22.4
resolve-from: 5.0.0
semver: 7.5.4
yargs: 16.2.0
transitivePeerDependencies:
......@@ -6874,6 +6837,11 @@ packages:
/detect-browser@5.3.0:
resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==}
/detect-file@1.0.0:
resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==}
engines: {node: '>=0.10.0'}
dev: true
/detect-indent@6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
......@@ -6921,14 +6889,14 @@ packages:
dependencies:
path-type: 4.0.0
/doctoc@2.2.0:
resolution: {integrity: sha512-PtiyaS+S3kcMbpx6x2V0S+PeDKisxmjEFnZsuYkkj4Lh3ObozJuuYh9dM4+sX02Ouuty8RF2LOCnIbpu/hWy/A==}
/doctoc@2.2.1:
resolution: {integrity: sha512-qNJ1gsuo7hH40vlXTVVrADm6pdg30bns/Mo7Nv1SxuXSM1bwF9b4xQ40a6EFT/L1cI+Yylbyi8MPI4G4y7XJzQ==}
hasBin: true
dependencies:
'@textlint/markdown-to-ast': 12.2.1
anchor-markdown-header: 0.5.7
anchor-markdown-header: 0.6.0
htmlparser2: 7.2.0
minimist: 1.2.6
minimist: 1.2.8
underscore: 1.13.4
update-section: 0.3.3
transitivePeerDependencies:
......@@ -7068,8 +7036,8 @@ packages:
engines: {node: '>=12'}
dev: true
/emoji-regex@6.1.3:
resolution: {integrity: sha512-73/zxHTjP2N2FQf0J5ngNjxP9LqG2krUshxYaowI8HxZQsiL2pYJc3k9/O93fc5/lCSkZv+bQ5Esk6k6msiSvg==}
/emoji-regex@10.1.0:
resolution: {integrity: sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==}
dev: true
/emoji-regex@8.0.0:
......@@ -7547,16 +7515,16 @@ packages:
engines: {node: '>=10'}
dev: true
/eslint-config-prettier@8.3.0(eslint@8.47.0):
/eslint-config-prettier@8.3.0(eslint@8.49.0):
resolution: {integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
eslint: 8.47.0
eslint: 8.49.0
dev: true
/eslint-config-standard@16.0.3(eslint-plugin-import@2.28.1)(eslint-plugin-node@11.1.0)(eslint-plugin-promise@5.2.0)(eslint@8.47.0):
/eslint-config-standard@16.0.3(eslint-plugin-import@2.28.1)(eslint-plugin-node@11.1.0)(eslint-plugin-promise@5.2.0)(eslint@8.49.0):
resolution: {integrity: sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==}
peerDependencies:
eslint: ^7.12.1
......@@ -7564,10 +7532,10 @@ packages:
eslint-plugin-node: ^11.1.0
eslint-plugin-promise: ^4.2.1 || ^5.0.0
dependencies:
eslint: 8.47.0
eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.60.1)(eslint@8.47.0)
eslint-plugin-node: 11.1.0(eslint@8.47.0)
eslint-plugin-promise: 5.2.0(eslint@8.47.0)
eslint: 8.49.0
eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.0)(eslint@8.49.0)
eslint-plugin-node: 11.1.0(eslint@8.49.0)
eslint-plugin-promise: 5.2.0(eslint@8.49.0)
dev: true
/eslint-import-resolver-node@0.3.9:
......@@ -7580,7 +7548,7 @@ packages:
- supports-color
dev: true
/eslint-module-utils@2.8.0(@typescript-eslint/parser@5.60.1)(eslint-import-resolver-node@0.3.9)(eslint@8.47.0):
/eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.0)(eslint-import-resolver-node@0.3.9)(eslint@8.49.0):
resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
engines: {node: '>=4'}
peerDependencies:
......@@ -7601,26 +7569,26 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 5.60.1(eslint@8.47.0)(typescript@5.1.6)
'@typescript-eslint/parser': 6.7.0(eslint@8.49.0)(typescript@5.1.6)
debug: 3.2.7
eslint: 8.47.0
eslint: 8.49.0
eslint-import-resolver-node: 0.3.9
transitivePeerDependencies:
- supports-color
dev: true
/eslint-plugin-es@3.0.1(eslint@8.47.0):
/eslint-plugin-es@3.0.1(eslint@8.49.0):
resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==}
engines: {node: '>=8.10.0'}
peerDependencies:
eslint: '>=4.19.1'
dependencies:
eslint: 8.47.0
eslint: 8.49.0
eslint-utils: 2.1.0
regexpp: 3.2.0
dev: true
/eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.60.1)(eslint@8.47.0):
/eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.0)(eslint@8.49.0):
resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==}
engines: {node: '>=4'}
peerDependencies:
......@@ -7630,16 +7598,16 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.60.1(eslint@8.47.0)(typescript@5.1.6)
'@typescript-eslint/parser': 6.7.0(eslint@8.49.0)(typescript@5.1.6)
array-includes: 3.1.6
array.prototype.findlastindex: 1.2.2
array.prototype.flat: 1.3.1
array.prototype.flatmap: 1.3.1
debug: 3.2.7
doctrine: 2.1.0
eslint: 8.47.0
eslint: 8.49.0
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.60.1)(eslint-import-resolver-node@0.3.9)(eslint@8.47.0)
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.0)(eslint-import-resolver-node@0.3.9)(eslint@8.49.0)
has: 1.0.3
is-core-module: 2.13.0
is-glob: 4.0.3
......@@ -7655,7 +7623,7 @@ packages:
- supports-color
dev: true
/eslint-plugin-jsdoc@35.5.1(eslint@8.47.0):
/eslint-plugin-jsdoc@35.5.1(eslint@8.49.0):
resolution: {integrity: sha512-pPYPWtsykwVEue1tYEyoppBj4dgF7XicF67tLLLraY6RQYBq7qMKjUHji19+hfiTtYKKBD0YfeK8hgjPAE5viw==}
engines: {node: '>=12'}
peerDependencies:
......@@ -7664,7 +7632,7 @@ packages:
'@es-joy/jsdoccomment': 0.9.0-alpha.1
comment-parser: 1.1.6-beta.0
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.47.0
eslint: 8.49.0
esquery: 1.4.0
jsdoc-type-pratt-parser: 1.1.1
lodash: 4.17.21
......@@ -7675,14 +7643,14 @@ packages:
- supports-color
dev: true
/eslint-plugin-node@11.1.0(eslint@8.47.0):
/eslint-plugin-node@11.1.0(eslint@8.49.0):
resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==}
engines: {node: '>=8.10.0'}
peerDependencies:
eslint: '>=5.16.0'
dependencies:
eslint: 8.47.0
eslint-plugin-es: 3.0.1(eslint@8.47.0)
eslint: 8.49.0
eslint-plugin-es: 3.0.1(eslint@8.49.0)
eslint-utils: 2.1.0
ignore: 5.2.4
minimatch: 3.1.2
......@@ -7690,15 +7658,15 @@ packages:
semver: 6.3.1
dev: true
/eslint-plugin-prefer-arrow@1.2.3(eslint@8.47.0):
/eslint-plugin-prefer-arrow@1.2.3(eslint@8.49.0):
resolution: {integrity: sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ==}
peerDependencies:
eslint: '>=2.0.0'
dependencies:
eslint: 8.47.0
eslint: 8.49.0
dev: true
/eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.3.0)(eslint@8.47.0)(prettier@2.8.8):
/eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.3.0)(eslint@8.49.0)(prettier@2.8.8):
resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
......@@ -7709,22 +7677,22 @@ packages:
eslint-config-prettier:
optional: true
dependencies:
eslint: 8.47.0
eslint-config-prettier: 8.3.0(eslint@8.47.0)
eslint: 8.49.0
eslint-config-prettier: 8.3.0(eslint@8.49.0)
prettier: 2.8.8
prettier-linter-helpers: 1.0.0
dev: true
/eslint-plugin-promise@5.2.0(eslint@8.47.0):
/eslint-plugin-promise@5.2.0(eslint@8.49.0):
resolution: {integrity: sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==}
engines: {node: ^10.12.0 || >=12.0.0}
peerDependencies:
eslint: ^7.0.0
dependencies:
eslint: 8.47.0
eslint: 8.49.0
dev: true
/eslint-plugin-react@7.33.2(eslint@8.47.0):
/eslint-plugin-react@7.33.2(eslint@8.49.0):
resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==}
engines: {node: '>=4'}
peerDependencies:
......@@ -7735,7 +7703,7 @@ packages:
array.prototype.tosorted: 1.1.1
doctrine: 2.1.0
es-iterator-helpers: 1.0.13
eslint: 8.47.0
eslint: 8.49.0
estraverse: 5.3.0
jsx-ast-utils: 3.2.0
minimatch: 3.1.2
......@@ -7749,7 +7717,7 @@ packages:
string.prototype.matchall: 4.0.8
dev: true
/eslint-plugin-unicorn@42.0.0(eslint@8.47.0):
/eslint-plugin-unicorn@42.0.0(eslint@8.49.0):
resolution: {integrity: sha512-ixBsbhgWuxVaNlPTT8AyfJMlhyC5flCJFjyK3oKE8TRrwBnaHvUbuIkCM1lqg8ryYrFStL/T557zfKzX4GKSlg==}
engines: {node: '>=12'}
peerDependencies:
......@@ -7758,8 +7726,8 @@ packages:
'@babel/helper-validator-identifier': 7.16.7
ci-info: 3.8.0
clean-regexp: 1.0.0
eslint: 8.47.0
eslint-utils: 3.0.0(eslint@8.47.0)
eslint: 8.49.0
eslint-utils: 3.0.0(eslint@8.49.0)
esquery: 1.4.0
indent-string: 4.0.0
is-builtin-module: 3.1.0
......@@ -7795,13 +7763,13 @@ packages:
eslint-visitor-keys: 1.3.0
dev: true
/eslint-utils@3.0.0(eslint@8.47.0):
/eslint-utils@3.0.0(eslint@8.49.0):
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies:
eslint: '>=5'
dependencies:
eslint: 8.47.0
eslint: 8.49.0
eslint-visitor-keys: 2.1.0
dev: true
......@@ -7820,16 +7788,16 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/eslint@8.47.0:
resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==}
/eslint@8.49.0:
resolution: {integrity: sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0)
'@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0)
'@eslint-community/regexpp': 4.6.2
'@eslint/eslintrc': 2.1.2
'@eslint/js': 8.47.0
'@humanwhocodes/config-array': 0.11.10
'@eslint/js': 8.49.0
'@humanwhocodes/config-array': 0.11.11
'@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8
ajv: 6.12.6
......@@ -8229,6 +8197,13 @@ packages:
strip-final-newline: 3.0.0
dev: true
/expand-tilde@2.0.2:
resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==}
engines: {node: '>=0.10.0'}
dependencies:
homedir-polyfill: 1.0.3
dev: true
/express-prom-bundle@6.6.0(prom-client@14.2.0):
resolution: {integrity: sha512-tZh2P2p5a8/yxQ5VbRav011Poa4R0mHqdFwn9Swe/obXDe5F0jY9wtRAfNYnqk4LXY7akyvR/nrvAHxQPWUjsQ==}
engines: {node: '>=10'}
......@@ -8479,6 +8454,16 @@ packages:
micromatch: 4.0.5
dev: true
/findup-sync@5.0.0:
resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==}
engines: {node: '>= 10.13.0'}
dependencies:
detect-file: 1.0.0
is-glob: 4.0.3
micromatch: 4.0.5
resolve-dir: 1.0.1
dev: true
/flat-cache@3.0.4:
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
engines: {node: ^10.12.0 || >=12.0.0}
......@@ -8824,6 +8809,18 @@ packages:
path-scurry: 1.10.1
dev: true
/glob@10.3.4:
resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
dependencies:
foreground-child: 3.1.1
jackspeak: 2.2.1
minimatch: 9.0.3
minipass: 7.0.3
path-scurry: 1.10.1
dev: true
/glob@7.1.4:
resolution: {integrity: sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==}
dependencies:
......@@ -8878,6 +8875,26 @@ packages:
once: 1.4.0
path-is-absolute: 1.0.1
/global-modules@1.0.0:
resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==}
engines: {node: '>=0.10.0'}
dependencies:
global-prefix: 1.0.2
is-windows: 1.0.2
resolve-dir: 1.0.1
dev: true
/global-prefix@1.0.2:
resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==}
engines: {node: '>=0.10.0'}
dependencies:
expand-tilde: 2.0.2
homedir-polyfill: 1.0.3
ini: 1.3.8
is-windows: 1.0.2
which: 1.3.1
dev: true
/globals@11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
......@@ -9109,7 +9126,7 @@ packages:
solc: 0.7.3(debug@4.3.4)
source-map-support: 0.5.21
stacktrace-parser: 0.1.10
ts-node: 10.9.1(@types/node@20.5.3)(typescript@5.2.2)
ts-node: 10.9.1(@types/node@20.6.0)(typescript@5.2.2)
tsort: 0.0.1
typescript: 5.2.2
undici: 5.24.0
......@@ -9212,6 +9229,13 @@ packages:
minimalistic-assert: 1.0.1
minimalistic-crypto-utils: 1.0.1
/homedir-polyfill@1.0.3:
resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
engines: {node: '>=0.10.0'}
dependencies:
parse-passwd: 1.0.0
dev: true
/hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
......@@ -9373,6 +9397,10 @@ packages:
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
/ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
dev: true
/internal-slot@1.0.5:
resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==}
engines: {node: '>= 0.4'}
......@@ -9381,12 +9409,6 @@ packages:
has: 1.0.3
side-channel: 1.0.4
/invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
dependencies:
loose-envify: 1.4.0
dev: true
/invert-kv@1.0.0:
resolution: {integrity: sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==}
engines: {node: '>=0.10.0'}
......@@ -9480,12 +9502,6 @@ packages:
ci-info: 3.8.0
dev: false
/is-core-module@2.12.1:
resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==}
dependencies:
has: 1.0.3
dev: true
/is-core-module@2.13.0:
resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==}
dependencies:
......@@ -11051,10 +11067,6 @@ packages:
kind-of: 6.0.3
dev: false
/minimist@1.2.6:
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
dev: true
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
......@@ -11824,6 +11836,11 @@ packages:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
/parse-passwd@1.0.0:
resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==}
engines: {node: '>=0.10.0'}
dev: true
/parse5@7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
dependencies:
......@@ -12320,13 +12337,6 @@ packages:
resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==}
engines: {node: '>=0.6'}
/query-ast@1.0.4:
resolution: {integrity: sha512-KFJFSvODCBjIH5HbHvITj9EEZKYUU6VX0T5CuB1ayvjUoUaZkKMi6eeby5Tf8DMukyZHlJQOE1+f3vevKUe6eg==}
dependencies:
invariant: 2.2.4
lodash: 4.17.21
dev: true
/query-string@6.14.1:
resolution: {integrity: sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==}
engines: {node: '>=6'}
......@@ -12701,6 +12711,14 @@ packages:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: true
/resolve-dir@1.0.1:
resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==}
engines: {node: '>=0.10.0'}
dependencies:
expand-tilde: 2.0.2
global-modules: 1.0.0
dev: true
/resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
......@@ -12789,7 +12807,7 @@ packages:
engines: {node: '>=14'}
hasBin: true
dependencies:
glob: 10.3.3
glob: 10.3.4
dev: true
/ripemd160@2.0.2:
......@@ -12902,16 +12920,6 @@ packages:
/safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
/sass@1.52.1:
resolution: {integrity: sha512-fSzYTbr7z8oQnVJ3Acp9hV80dM1fkMN7mSD/25mpcct9F7FPBMOI8krEYALgU1aZoqGhQNhTPsuSmxjnIvAm4Q==}
engines: {node: '>=12.0.0'}
hasBin: true
dependencies:
chokidar: 3.5.3
immutable: 4.1.0
source-map-js: 1.0.2
dev: true
/saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
......@@ -12927,14 +12935,6 @@ packages:
/scrypt-js@3.0.1:
resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==}
/scss-parser@1.0.5:
resolution: {integrity: sha512-RZOtvCmCnwkDo7kdcYBi807Y5EoTIxJ34AgEgJNDmOH1jl0/xG0FyYZFbH6Ga3Iwu7q8LSdxJ4C5UkzNXjQxKQ==}
engines: {node: '>=6.0.0'}
dependencies:
invariant: 2.2.4
lodash: 4.17.21
dev: true
/secp256k1@4.0.3:
resolution: {integrity: sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==}
engines: {node: '>=10.0.0'}
......@@ -13889,7 +13889,7 @@ packages:
yn: 3.1.1
dev: true
/ts-node@10.9.1(@types/node@20.5.3)(typescript@5.2.2):
/ts-node@10.9.1(@types/node@20.6.0)(typescript@5.2.2):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
......@@ -13908,7 +13908,7 @@ packages:
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.5.3
'@types/node': 20.6.0
acorn: 8.10.0
acorn-walk: 8.2.0
arg: 4.1.3
......@@ -14056,16 +14056,6 @@ packages:
- ts-node
dev: true
/tsutils@3.21.0(typescript@5.1.6):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
peerDependencies:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
dependencies:
tslib: 1.14.1
typescript: 5.1.6
dev: true
/tsx@3.12.7:
resolution: {integrity: sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==}
hasBin: true
......@@ -14240,12 +14230,12 @@ packages:
dependencies:
is-typedarray: 1.0.0
/typedoc@0.24.8(typescript@5.1.6):
resolution: {integrity: sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==}
engines: {node: '>= 14.14'}
/typedoc@0.25.1(typescript@5.1.6):
resolution: {integrity: sha512-c2ye3YUtGIadxN2O6YwPEXgrZcvhlZ6HlhWZ8jQRNzwLPn2ylhdGqdR8HbyDRyALP8J6lmSANILCkkIdNPFxqA==}
engines: {node: '>= 16'}
hasBin: true
peerDependencies:
typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x
typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x
dependencies:
lunr: 2.3.9
marked: 4.3.0
......@@ -14610,7 +14600,7 @@ packages:
- zod
dev: true
/vite-node@0.34.1(@types/node@20.5.0):
/vite-node@0.34.1(@types/node@20.6.0):
resolution: {integrity: sha512-odAZAL9xFMuAg8aWd7nSPT+hU8u2r9gU3LRm9QKjxBEF2rRdWpMuqkrkjvyVQEdNFiBctqr2Gg4uJYizm5Le6w==}
engines: {node: '>=v14.18.0'}
hasBin: true
......@@ -14620,7 +14610,7 @@ packages:
mlly: 1.4.0
pathe: 1.1.1
picocolors: 1.0.0
vite: 4.4.9(@types/node@20.5.0)
vite: 4.4.9(@types/node@20.6.0)
transitivePeerDependencies:
- '@types/node'
- less
......@@ -14632,7 +14622,7 @@ packages:
- terser
dev: true
/vite-node@0.34.2(@types/node@20.5.0):
/vite-node@0.34.2(@types/node@20.6.0):
resolution: {integrity: sha512-JtW249Zm3FB+F7pQfH56uWSdlltCo1IOkZW5oHBzeQo0iX4jtC7o1t9aILMGd9kVekXBP2lfJBEQt9rBh07ebA==}
engines: {node: '>=v14.18.0'}
hasBin: true
......@@ -14642,7 +14632,7 @@ packages:
mlly: 1.4.0
pathe: 1.1.1
picocolors: 1.0.0
vite: 4.4.9(@types/node@20.5.0)
vite: 4.4.9(@types/node@20.6.0)
transitivePeerDependencies:
- '@types/node'
- less
......@@ -14654,7 +14644,7 @@ packages:
- terser
dev: true
/vite@4.4.6(@types/node@20.5.3):
/vite@4.4.6(@types/node@20.6.0):
resolution: {integrity: sha512-EY6Mm8vJ++S3D4tNAckaZfw3JwG3wa794Vt70M6cNJ6NxT87yhq7EC8Rcap3ahyHdo8AhCmV9PTk+vG1HiYn1A==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
......@@ -14682,7 +14672,7 @@ packages:
terser:
optional: true
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
esbuild: 0.18.15
postcss: 8.4.27
rollup: 3.26.3
......@@ -14690,43 +14680,7 @@ packages:
fsevents: 2.3.2
dev: true
/vite@4.4.9(@types/node@20.5.0):
resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
'@types/node': '>= 14'
less: '*'
lightningcss: ^1.21.0
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
dependencies:
'@types/node': 20.5.0
esbuild: 0.18.15
postcss: 8.4.27
rollup: 3.28.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/vite@4.4.9(@types/node@20.5.3):
/vite@4.4.9(@types/node@20.6.0):
resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
......@@ -14754,7 +14708,7 @@ packages:
terser:
optional: true
dependencies:
'@types/node': 20.5.3
'@types/node': 20.6.0
esbuild: 0.18.15
postcss: 8.4.27
rollup: 3.28.0
......@@ -14793,9 +14747,9 @@ packages:
webdriverio:
optional: true
dependencies:
'@types/chai': 4.3.5
'@types/chai': 4.3.6
'@types/chai-subset': 1.3.3
'@types/node': 20.5.0
'@types/node': 20.6.0
'@vitest/expect': 0.34.1
'@vitest/runner': 0.34.1
'@vitest/snapshot': 0.34.1
......@@ -14804,7 +14758,7 @@ packages:
acorn: 8.10.0
acorn-walk: 8.2.0
cac: 6.7.14
chai: 4.3.7
chai: 4.3.8
debug: 4.3.4(supports-color@8.1.1)
local-pkg: 0.4.3
magic-string: 0.30.1
......@@ -14814,8 +14768,8 @@ packages:
strip-literal: 1.0.1
tinybench: 2.5.0
tinypool: 0.7.0
vite: 4.4.9(@types/node@20.5.0)
vite-node: 0.34.1(@types/node@20.5.0)
vite: 4.4.9(@types/node@20.6.0)
vite-node: 0.34.1(@types/node@20.6.0)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
......@@ -14858,9 +14812,9 @@ packages:
webdriverio:
optional: true
dependencies:
'@types/chai': 4.3.5
'@types/chai': 4.3.6
'@types/chai-subset': 1.3.3
'@types/node': 20.5.0
'@types/node': 20.6.0
'@vitest/expect': 0.34.2
'@vitest/runner': 0.34.2
'@vitest/snapshot': 0.34.2
......@@ -14869,7 +14823,7 @@ packages:
acorn: 8.10.0
acorn-walk: 8.2.0
cac: 6.7.14
chai: 4.3.7
chai: 4.3.8
debug: 4.3.4(supports-color@8.1.1)
jsdom: 22.1.0
local-pkg: 0.4.3
......@@ -14880,8 +14834,8 @@ packages:
strip-literal: 1.0.1
tinybench: 2.5.0
tinypool: 0.7.0
vite: 4.4.9(@types/node@20.5.0)
vite-node: 0.34.2(@types/node@20.5.0)
vite: 4.4.9(@types/node@20.6.0)
vite-node: 0.34.2(@types/node@20.6.0)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
......@@ -15414,7 +15368,6 @@ packages:
hasBin: true
dependencies:
isexe: 2.0.0
dev: false
/which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
......
......@@ -2,3 +2,4 @@ packages:
- 'packages/*'
- 'endpoint-monitor'
- 'op-exporter'
- 'indexer/ts'
......@@ -15,7 +15,7 @@ that maintains 1:1 compatibility with Ethereum.
- [Rollup Node](rollup-node.md)
- [Rollup Node P2p](rollup-node-p2p.md)
- [L2 Chain Derivation](derivation.md)
- [Network Upgrades](network-upgrades.md)
- [Superchain Upgrades](superchain-upgrades.md)
- [System Config](system_config.md)
- [Batch Submitter](batcher.md)
- [Guaranteed Gas Market](guaranteed-gas-market.md)
......
......@@ -16,6 +16,7 @@
- [Extended PayloadAttributesV1](#extended-payloadattributesv1)
- [`engine_newPayloadV1`](#engine_newpayloadv1)
- [`engine_getPayloadV1`](#engine_getpayloadv1)
- [`engine_signalSuperchainV1`](#engine_signalsuperchainv1)
- [Networking](#networking)
- [Sync](#sync)
- [Happy-path sync](#happy-path-sync)
......@@ -198,6 +199,37 @@ Applies a L2 block to the engine state.
No modifications to [`engine_getPayloadV1`][engine_getPayloadV1].
Retrieves a payload by ID, prepared by `engine_forkchoiceUpdatedV1` when called with `payloadAttributes`.
### `engine_signalSuperchainV1`
Optional extension to the Engine API. Signals superchain information to the Engine:
V1 signals which protocol version is recommended and required.
Types:
```javascript
SuperchainSignal: {
recommended: ProtocolVersion
required: ProtocolVersion
}
```
`ProtocolVersion`: encoded for RPC as defined in
[Protocol Version format specification](./superchain-upgrades.md#protocol-version-format).
Parameters:
- `signal`: `SuperchainSignal`, the signaled superchain information.
Returns:
- `ProtocolVersion`: the latest supported OP-Stack protocol version of the execution engine.
The execution engine SHOULD warn the user when the recommended version is newer than
the current version supported by the execution engine.
The execution engine SHOULD take safety precautions if it does not meet the required protocol version.
This may include halting the engine, with consent of the execution engine operator.
## Networking
The execution engine can acquire all data through the rollup node, as derived from L1:
......
# Network Upgrades
Network upgrades, also known as forks or hardforks, implement consensus-breaking changes.
These changes are transitioned into deterministically across all nodes through an activation rule.
This document lists the network upgrades of the OP Stack, starting after the Bedrock upgrade.
Prospective upgrades may be listed as proposals, but are not governed through these specifications.
Activation rule parameters of network upgrades are configured in respective chain configurations,
and not part of this specification.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [Activation rules](#activation-rules)
- [L2 Block-number based activation](#l2-block-number-based-activation)
- [L2 Block-timestamp based activation](#l2-block-timestamp-based-activation)
- [Post-Bedrock Network upgrades](#post-bedrock-network-upgrades)
- [Regolith](#regolith)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Activation rules
The below L2-block based activation rules may be applied in two contexts:
- The rollup node, specified through the rollup configuration (known as `rollup.json`),
referencing L2 blocks (or block input-attributes) that pass through the derivation pipeline.
- The execution engine, specified through the chain configuration (known as the `config` part of `genesis.json`),
referencing blocks or input-attributes that are part of, or applied to, the L2 chain.
### L2 Block-number based activation
Activation rule: `x != null && x >= upgradeNumber`
Starting at, and including, the L2 `block` with `block.number == x`, the upgrade rules apply.
If the upgrade block-number `x` is not specified in the configuration, the upgrade is ignored.
This applies to the L2 block number, not to the L1-origin block number.
This means that an L2 upgrade may be inactive, and then active, without changing the L1-origin.
This block number based method has commonly been used in L1 up until the Bellatrix/Paris upgrade, a.k.a. The Merge,
which was upgraded through special rules.
### L2 Block-timestamp based activation
Activation rule: `x != null && x >= upgradeTime`
Starting at, and including, the L2 `block` with `block.timestamp == x`, the upgrade rules apply.
If the upgrade block-timestamp `x` is not specified in the configuration, the upgrade is ignored.
This applies to the L2 block timestamp, not to the L1-origin block timestamp.
This means that an L2 upgrade may be inactive, and then active, without changing the L1-origin.
This timestamp based method has become the default on L1 after the Bellatrix/Paris upgrade, a.k.a. The Merge,
because it can be planned in accordance with beacon-chain epochs and slots.
Note that the L2 version is not limited to timestamps that match L1 beacon-chain slots or epochs.
A timestamp may be chosen to be synchronous with a specific slot or epoch on L1,
but the matching L1-origin information may not be present at the time of activation on L2.
## Post-Bedrock Network upgrades
### Regolith
The Regolith upgrade, named after a material best described as "deposited dust on top of a layer of bedrock",
implements minor changes to deposit processing, based on reports of the Sherlock Audit-contest and findings in
the Bedrock Optimism Goerli testnet.
Summary of changes:
- The `isSystemTx` boolean is disabled, system transactions now use the same gas accounting rules as regular deposits.
- The actual deposit gas-usage is recorded in the receipt of the deposit transaction,
and subtracted from the L2 block gas-pool.
Unused gas of deposits is not refunded with ETH however, as it is burned on L1.
- The `nonce` value of the deposit sender account, before the transaction state-transition, is recorded in a new
optional field (`depositNonce`), extending the transaction receipt (i.e. not present in pre-Regolith receipts).
- The recorded deposit `nonce` is used to correct the transaction and receipt metadata in RPC responses,
including the `contractAddress` field of deposits that deploy contracts.
- The `gas` and `depositNonce` data is committed to as part of the consensus-representation of the receipt,
enabling the data to be safely synced between independent L2 nodes.
- The L1-cost function was corrected to more closely match pre-Bedrock behavior.
The [deposit specification](./deposits.md) specifies the deposit changes of the Regolith upgrade in more detail.
The [execution engine specification](./exec-engine.md) specifies the L1 cost function difference.
The Regolith upgrade uses a *L2 block-timestamp* activation-rule, and is specified in both the
rollup-node (`regolith_time`) and execution engine (`config.regolithTime`).
......@@ -24,6 +24,7 @@ currently only concerned with the specification of the rollup driver.
- [Derivation](#derivation)
- [L2 Output RPC method](#l2-output-rpc-method)
- [Output Method API](#output-method-api)
- [Protocol Version tracking](#protocol-version-tracking)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
......@@ -72,3 +73,20 @@ The input and return types here are as defined by the [engine API specs][engine-
- returns:
1. `version`: `DATA`, 32 Bytes - the output root version number, beginning with 0.
1. `l2OutputRoot`: `DATA`, 32 Bytes - the output root.
## Protocol Version tracking
The rollup-node should monitor the recommended and required protocol version by monitoring
the Protocol Version contract on L1, as specified in the [Superchain Version Signaling specifications].
[Superchain Version Signaling specifications]: ./superchain-upgrades.md#superchain-version-signaling
This can be implemented through polling in the [Driver](#driver) loop.
After polling the Protocol Version, the rollup node SHOULD communicate it with the execution-engine through an
[`engine_signalSuperchainV1`](./exec-engine.md#enginesignalsuperchainv1) call.
The rollup node SHOULD warn the user when the recommended version is newer than
the current version supported by the rollup node.
The rollup node SHOULD take safety precautions if it does not meet the required protocol version.
This may include halting the engine, with consent of the rollup node operator.
# Superchain Upgrades
Superchain upgrades, also known as forks or hardforks, implement consensus-breaking changes.
A Superchain upgrade requires the node software to support up to a given Protocol Version.
The version indicates support, the upgrade indicates the activation of new functionality.
This document lists the protocol versions of the OP-Stack, starting at the Bedrock upgrade,
as well as the default Superchain Targets.
Activation rule parameters of network upgrades are configured as part of the Superchain Target specification:
chains following the same Superchain Target upgrade synchronously.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [Protocol Version](#protocol-version)
- [Protocol Version Format](#protocol-version-format)
- [Build identifier](#build-identifier)
- [Major versions](#major-versions)
- [Minor versions](#minor-versions)
- [Patch versions](#patch-versions)
- [Pre-releases](#pre-releases)
- [Protocol Version Exposure](#protocol-version-exposure)
- [Superchain Target](#superchain-target)
- [Superchain Version signaling](#superchain-version-signaling)
- [`ProtocolVersions` L1 contract](#protocolversions-l1-contract)
- [Activation rules](#activation-rules)
- [L2 Block-number based activation (deprecated)](#l2-block-number-based-activation-deprecated)
- [L2 Block-timestamp based activation](#l2-block-timestamp-based-activation)
- [OP-Stack Protocol versions](#op-stack-protocol-versions)
- [Post-Bedrock Network upgrades](#post-bedrock-network-upgrades)
- [Regolith](#regolith)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Protocol Version
The Protocol Version documents the progression of the total set of canonical OP-Stack specifications.
Components of the OP-Stack implement the subset of their respective protocol component domain,
up to a given Protocol Version of the OP-Stack.
OP-Stack mods, i.e. non-canonical extensions to the OP-Stack, are not included in the versioning of the Protocol.
Instead, mods must specify which upstream Protocol Version they are based on and where breaking changes are made.
This ensures tooling of the OP-Stack can be shared and collaborated on with OP-Stack mods.
The Protocol Version is NOT a hardfork identifier, but rather indicates software-support for a well-defined set
of features introduced in past and future hardforks, not the activation of said hardforks.
Changes that can be included in prospective Protocol Versions may be included in the specifications as proposals,
with explicit notice of the Protocol Version they are based on.
This enables an iterative integration process into the canonical set of specifications,
but does not guarantee the proposed specifications become canonical.
Note that the Protocol Version only applies to the Protocol specifications with the Superchain Targets specified within.
This versioning is independent of the [Semver] versioning used in OP Stack smart-contracts,
and the [Semver]-versioned reference software of the OP-Stack.
### Protocol Version Format
The Protocol Version is [Semver]-compatible.
It is encoded as a single 32 bytes long `<protocol version>`.
The version must be encoded as 32 bytes of `DATA` in JSON RPC usage.
The encoding is typed, to ensure future-compatibility.
```text
<protocol version> ::= <version-type><typed-payload>
<version-type> ::= <uint8>
<typed-payload> ::= <31 bytes>
```
version-type `0`:
```text
<reserved><build><major><minor><patch><pre-release>
<reserved> ::= <7 zeroed bytes>
<build> ::= <8 bytes>
<major> ::= <big-endian uint32>
<minor> ::= <big-endian uint32>
<patch> ::= <big-endian uint32>
<pre-release> ::= <big-endian uint32>
```
The `<reserved>` bytes of the Protocol Version are reserved for future extensions.
Protocol versions with a different `<version-type>` should not be compared directly.
[Semver]: https://semver.org/
#### Build identifier
The `<build>` identifier, as defined by [Semver], is ignored when determining version precedence.
The `<build>` must be non-zero to apply to the protocol version.
Modifications of the OP-Stack should define a `<build>` to distinguish from the canonical protocol feature-set.
Changes to the `<build>` may be encoded in the `<build>` itself to stay aligned with the upstream protocol.
The major/minor/patch versions should align with that of the upstream protocol that the modifications are based on.
Users of the protocol can choose to implement custom support for the alternative `<build>`,
but may work out of the box if the major features are consistent with that of the upstream protocol version.
The 8 byte `<build>` identifier may be presented as string for human readability if the contents are alpha-numeric,
including `-` and `.`, as outlined in the [Semver] format specs. Trailing `0` bytes can be used for padding.
It may be presented as `0x`-prefixed hex string otherwise.
#### Major versions
Major version changes indicate support for new consensus-breaking functionality.
Major versions should retain support for functionality of previous major versions for
syncing/indexing of historical chain data.
Implementations may drop support for previous Major versions, when there are viable alternatives,
e.g. `l2geth` for pre-Bedrock data.
#### Minor versions
Minor version changes indicate support for backward compatible extensions,
including backward-compatible additions to the set of chains in a Superchain Target.
Backward-compatibility is defined by the requirement for existing end-users to upgrade nodes and tools or not.
Minor version changes may also include optional offchain functionality, such as additional syncing protocols.
#### Patch versions
Patch version changes indicate backward compatible bug fixes and improvements.
#### Pre-releases
Pre-releases of the protocol are proposals: these are not stable targets for production usage.
A pre-release might not satisfy the intended compatibility requirements as denoted by its associated normal version.
The `<pre-release>` must be non-zero to apply to the protocol version.
Node-software may support a pre-release, but must not activate any protocol changes without the user explicitly
opting in through the means of a feature-flag or configuration change.
A pre-release is not an official version, and meant for protocol developers to communicate an experimental changeset
before the changeset is reviewed by governance. Pre-releases are subject to change.
### Protocol Version Exposure
The Protocol Version is not exposed to the application-layer environment:
hardforks already expose the change of functionality upon activation as required,
and the Protocol Version is meant for offchain usage only.
The protocol version indicates support rather than activation of functionality.
There is one exception however: signaling by onchain components to offchain components.
More about this in [Superchain Version signaling].
## Superchain Target
Changes to the L2 state-transition function are transitioned into deterministically across all nodes
through an **activation rule**.
Changes to L1 smart-contracts must be compatible with the latest activated L2 functionality,
and are executed through **L1 contract-upgrades**.
A Superchain Target defines a set of activation rules and L1 contract upgrades shared between OP-Stack chains,
to upgrade the chains collectively.
### Superchain Version signaling
Each Superchain Target tracks the protocol changes, and signals the `recommended` and `required`
Protocol Version ahead of activation of new breaking functionality.
- `recommended`: a signal in advance of a network upgrade, to alert users of the protocol change to be prepared for.
Node software is recommended to signal the recommendation to users through logging and metrics.
- `required`: a signal shortly in advance of a breaking network upgrade, to alert users of breaking changes.
Users may opt in to elevated alerts or preventive measures, to ensure consistency with the upgrade.
Signaling is done through a L1 smart-contract that is monitored by the OP-Stack software.
Not all components of the OP-Stack are required to directly monitor L1 however:
cross-component APIs like the Engine API may be used to forward the Protocol Version signals,
to keep components encapsulated from L1.
See [`engine_signalOPStackVersionV1`](./exec-engine.md#enginesignalopstackversionv1).
### `ProtocolVersions` L1 contract
The `ProtocolVersions` contract on L1 enables L2 nodes to pick up on superchain protocol version signals.
The interface is:
- Required storage slot: `bytes32(uint256(keccak256("protocolversion.required")) - 1)`
- Recommended storage slot: `bytes32(uint256(keccak256("protocolversion.recommended")) - 1)`
- Required getter: `required()` returns `ProtocolVersion`
- Recommended getter `recommended()` returns `ProtocolVersion`
- Version updates also emit a typed event:
`event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data)`
## Activation rules
The below L2-block based activation rules may be applied in two contexts:
- The rollup node, specified through the rollup configuration (known as `rollup.json`),
referencing L2 blocks (or block input-attributes) that pass through the derivation pipeline.
- The execution engine, specified through the chain configuration (known as the `config` part of `genesis.json`),
referencing blocks or input-attributes that are part of, or applied to, the L2 chain.
For both types of configurations, some activation parameters may apply to all chains within the superchain,
and are then retrieved from the superchain target configuration.
### L2 Block-number based activation (deprecated)
Activation rule: `x != null && x >= upgradeNumber`
This block number based method has commonly been used in L1 up until the Bellatrix/Paris upgrade, a.k.a. The Merge,
which was upgraded through special rules.
This method is not superchain-compatible, as the activation-parameter is chain-specific
(different chains may have different block-heights at the same moment in time).
Starting at, and including, the L2 `block` with `block.number == x`, the upgrade rules apply.
If the upgrade block-number `x` is not specified in the configuration, the upgrade is ignored.
This applies to the L2 block number, not to the L1-origin block number.
This means that an L2 upgrade may be inactive, and then active, without changing the L1-origin.
### L2 Block-timestamp based activation
Activation rule: `x != null && x >= upgradeTime`
This is the preferred superchain upgrade activation-parameter type:
it is synchronous between all L2 chains and compatible with post-Merge timestamp-based chain upgrades in L1.
Starting at, and including, the L2 `block` with `block.timestamp == x`, the upgrade rules apply.
If the upgrade block-timestamp `x` is not specified in the configuration, the upgrade is ignored.
This applies to the L2 block timestamp, not to the L1-origin block timestamp.
This means that an L2 upgrade may be inactive, and then active, without changing the L1-origin.
This timestamp based method has become the default on L1 after the Bellatrix/Paris upgrade, a.k.a. The Merge,
because it can be planned in accordance with beacon-chain epochs and slots.
Note that the L2 version is not limited to timestamps that match L1 beacon-chain slots or epochs.
A timestamp may be chosen to be synchronous with a specific slot or epoch on L1,
but the matching L1-origin information may not be present at the time of activation on L2.
## OP-Stack Protocol versions
- `v1.0.0`: 2021 Jan 16th - Mainnet Soft Launch, based on OVM.
([announcement](https://medium.com/ethereum-optimism/mainnet-soft-launch-7cacc0143cd5))
- `v1.1.0`: 2021 Aug 19th - Community launch.
([announcement](https://medium.com/ethereum-optimism/community-launch-7c9a2a9d3e84))
- `v2.0.0`: 2021 Nov 12th - the EVM-Equivalence update, also known as OVM 2.0 and chain regenesis.
([announcement](https://twitter.com/optimismfnd/status/1458953238867165192))
- `v2.1.0`: 2022 May 31st - Optimism Collective.
([announcement](https://optimism.mirror.xyz/gQWKlrDqHzdKPsB1iUnI-cVN3v0NvsWnazK7ajlt1fI)).
- `v3.0.0-1`: 2023 Jan 13th - Bedrock pre-release, deployed on OP-Goerli, and later Base-Goerli.
- `v3.0.0`: 2023 Jun 6th - Bedrock, including the Regolith hardfork improvements, first deployed on OP-Mainnet.
## Post-Bedrock Network upgrades
### Regolith
The Regolith upgrade, named after a material best described as "deposited dust on top of a layer of bedrock",
implements minor changes to deposit processing, based on reports of the Sherlock Audit-contest and findings in
the Bedrock Optimism Goerli testnet.
Summary of changes:
- The `isSystemTx` boolean is disabled, system transactions now use the same gas accounting rules as regular deposits.
- The actual deposit gas-usage is recorded in the receipt of the deposit transaction,
and subtracted from the L2 block gas-pool.
Unused gas of deposits is not refunded with ETH however, as it is burned on L1.
- The `nonce` value of the deposit sender account, before the transaction state-transition, is recorded in a new
optional field (`depositNonce`), extending the transaction receipt (i.e. not present in pre-Regolith receipts).
- The recorded deposit `nonce` is used to correct the transaction and receipt metadata in RPC responses,
including the `contractAddress` field of deposits that deploy contracts.
- The `gas` and `depositNonce` data is committed to as part of the consensus-representation of the receipt,
enabling the data to be safely synced between independent L2 nodes.
- The L1-cost function was corrected to more closely match pre-Bedrock behavior.
The [deposit specification](./deposits.md) specifies the deposit changes of the Regolith upgrade in more detail.
The [execution engine specification](./exec-engine.md) specifies the L1 cost function difference.
The Regolith upgrade uses a *L2 block-timestamp* activation-rule, and is specified in both the
rollup-node (`regolith_time`) and execution engine (`config.regolithTime`).
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