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

Merge branch 'develop' into clabby/ctb/contributing-md

parents 7ea6f166 535d4bb1
...@@ -43,7 +43,7 @@ require ( ...@@ -43,7 +43,7 @@ require (
golang.org/x/term v0.11.0 golang.org/x/term v0.11.0
golang.org/x/time v0.3.0 golang.org/x/time v0.3.0
gorm.io/driver/postgres v1.5.2 gorm.io/driver/postgres v1.5.2
gorm.io/gorm v1.25.2 gorm.io/gorm v1.25.3
) )
require ( require (
......
...@@ -1111,8 +1111,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= ...@@ -1111,8 +1111,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= gorm.io/gorm v1.25.3 h1:zi4rHZj1anhZS2EuEODMhDisGy+Daq9jtPrNGgbQYD8=
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.3/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
......
package api package api
import ( import (
"encoding/json" "fmt"
"net/http" "net/http"
"github.com/ethereum-optimism/optimism/indexer/api/routes"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
type PaginationResponse struct { const ethereumAddressRegex = `^0x[a-fA-F0-9]{40}$`
// TODO type this better
Data interface{} `json:"data"`
Cursor string `json:"cursor"`
HasNextPage bool `json:"hasNextPage"`
}
func (a *Api) L1DepositsHandler(w http.ResponseWriter, r *http.Request) {
bv := a.BridgeTransfersView
address := common.HexToAddress(chi.URLParam(r, "address"))
// limit := getIntFromQuery(r, "limit", 10)
// cursor := r.URL.Query().Get("cursor")
// sortDirection := r.URL.Query().Get("sortDirection")
deposits, err := bv.L1BridgeDepositsByAddress(address)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// This is not the shape of the response we want!!!
// will add in the individual features in future prs 1 by 1
response := PaginationResponse{
Data: deposits,
// Cursor: nextCursor,
HasNextPage: false,
}
jsonResponse(w, response, http.StatusOK)
}
func (a *Api) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
bv := a.BridgeTransfersView
address := common.HexToAddress(chi.URLParam(r, "address"))
// limit := getIntFromQuery(r, "limit", 10)
// cursor := r.URL.Query().Get("cursor")
// sortDirection := r.URL.Query().Get("sortDirection")
withdrawals, err := bv.L2BridgeWithdrawalsByAddress(address)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// This is not the shape of the response we want!!!
// will add in the individual features in future prs 1 by 1
response := PaginationResponse{
Data: withdrawals,
// Cursor: nextCursor,
HasNextPage: false,
}
jsonResponse(w, response, http.StatusOK)
}
func (a *Api) HealthzHandler(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, "ok", http.StatusOK)
}
func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
if err := json.NewEncoder(w).Encode(data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
type Api struct { type Api struct {
Router *chi.Mux Router *chi.Mux
BridgeTransfersView database.BridgeTransfersView
} }
func NewApi(bv database.BridgeTransfersView) *Api { func NewApi(bv database.BridgeTransfersView, logger log.Logger) *Api {
logger.Info("Initializing API...")
r := chi.NewRouter() r := chi.NewRouter()
api := &Api{Router: r, BridgeTransfersView: bv} h := routes.NewRoutes(logger, bv)
// these regex are .+ because I wasn't sure what they should be api := &Api{Router: r}
// don't want a regex for addresses because would prefer to validate the address
// with go-ethereum and throw a friendly error message
r.Get("/api/v0/deposits/{address:.+}", api.L1DepositsHandler)
r.Get("/api/v0/withdrawals/{address:.+}", api.L2WithdrawalsHandler)
r.Get("/healthz", api.HealthzHandler)
return api r.Get("/healthz", h.HealthzHandler)
r.Get(fmt.Sprintf("/api/v0/deposits/{address:%s}", ethereumAddressRegex), h.L1DepositsHandler)
r.Get(fmt.Sprintf("/api/v0/withdrawals/{address:%s}", ethereumAddressRegex), h.L2WithdrawalsHandler)
return api
} }
func (a *Api) Listen(port string) error { func (a *Api) Listen(port string) error {
......
package api package api
import ( import (
"fmt"
"math/big" "math/big"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/ethereum-optimism/optimism/indexer/database" "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/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// MockBridgeTransfersView mocks the BridgeTransfersView interface // MockBridgeTransfersView mocks the BridgeTransfersView interface
type MockBridgeTransfersView struct{} type MockBridgeTransfersView struct{}
var mockAddress = "0x4204204204204204204204204204204204204204"
var ( var (
deposit = database.L1BridgeDeposit{ deposit = database.L1BridgeDeposit{
TransactionSourceHash: common.HexToHash("abc"), TransactionSourceHash: common.HexToHash("abc"),
...@@ -23,7 +28,7 @@ var ( ...@@ -23,7 +28,7 @@ var (
} }
withdrawal = database.L2BridgeWithdrawal{ withdrawal = database.L2BridgeWithdrawal{
TransactionWithdrawalHash: common.HexToHash("0x456"), TransactionWithdrawalHash: common.HexToHash("0x420"),
CrossDomainMessengerNonce: &database.U256{Int: big.NewInt(0)}, CrossDomainMessengerNonce: &database.U256{Int: big.NewInt(0)},
Tx: database.Transaction{}, Tx: database.Transaction{},
TokenPair: database.TokenPair{}, TokenPair: database.TokenPair{},
...@@ -65,7 +70,8 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common. ...@@ -65,7 +70,8 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.
} }
func TestHealthz(t *testing.T) { func TestHealthz(t *testing.T) {
api := NewApi(&MockBridgeTransfersView{}) logger := testlog.Logger(t, log.LvlInfo)
api := NewApi(&MockBridgeTransfersView{}, logger)
request, err := http.NewRequest("GET", "/healthz", nil) request, err := http.NewRequest("GET", "/healthz", nil)
assert.Nil(t, err) assert.Nil(t, err)
...@@ -76,8 +82,9 @@ func TestHealthz(t *testing.T) { ...@@ -76,8 +82,9 @@ func TestHealthz(t *testing.T) {
} }
func TestL1BridgeDepositsHandler(t *testing.T) { func TestL1BridgeDepositsHandler(t *testing.T) {
api := NewApi(&MockBridgeTransfersView{}) logger := testlog.Logger(t, log.LvlInfo)
request, err := http.NewRequest("GET", "/api/v0/deposits/0x123", nil) api := NewApi(&MockBridgeTransfersView{}, logger)
request, err := http.NewRequest("GET", fmt.Sprintf("/api/v0/deposits/%s", mockAddress), nil)
assert.Nil(t, err) assert.Nil(t, err)
responseRecorder := httptest.NewRecorder() responseRecorder := httptest.NewRecorder()
...@@ -87,8 +94,9 @@ func TestL1BridgeDepositsHandler(t *testing.T) { ...@@ -87,8 +94,9 @@ func TestL1BridgeDepositsHandler(t *testing.T) {
} }
func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) { func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) {
api := NewApi(&MockBridgeTransfersView{}) logger := testlog.Logger(t, log.LvlInfo)
request, err := http.NewRequest("GET", "/api/v0/withdrawals/0x123", nil) api := NewApi(&MockBridgeTransfersView{}, logger)
request, err := http.NewRequest("GET", fmt.Sprintf("/api/v0/withdrawals/%s", mockAddress), nil)
assert.Nil(t, err) assert.Nil(t, err)
responseRecorder := httptest.NewRecorder() responseRecorder := httptest.NewRecorder()
......
package routes
import (
"encoding/json"
"net/http"
"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)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
logger.Error("Failed to marshal JSON: %v", err)
return
}
w.WriteHeader(statusCode)
_, err = w.Write(jsonData)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
logger.Error("Failed to write JSON data", err)
return
}
}
package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/go-chi/chi/v5"
)
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"`
}
type DepositResponse struct {
Cursor string `json:"cursor"`
HasNextPage bool `json:"hasNextPage"`
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.L1BridgeDepositWithTransactionHashes) DepositResponse {
items := make([]DepositItem, len(deposits))
for _, deposit := range 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.Int.String(),
L1Token: TokenInfo{
ChainId: 1,
Address: deposit.L1BridgeDeposit.TokenPair.L1TokenAddress.String(),
Name: "TODO",
Symbol: "TODO",
Decimals: 420,
Extensions: Extensions{
OptimismBridgeAddress: "0x420", // TODO
},
},
L2Token: TokenInfo{
ChainId: 10,
Address: deposit.L1BridgeDeposit.TokenPair.L2TokenAddress.String(),
Name: "TODO",
Symbol: "TODO",
Decimals: 420,
Extensions: Extensions{
OptimismBridgeAddress: "0x420", // TODO
},
},
}
items = append(items, item)
}
return DepositResponse{
Cursor: "42042042-4204-4204-4204-420420420420", // TODO
HasNextPage: false, // TODO
Items: items,
}
}
func (h Routes) L1DepositsHandler(w http.ResponseWriter, r *http.Request) {
address := common.HexToAddress(chi.URLParam(r, "address"))
deposits, err := h.BridgeTransfersView.L1BridgeDepositsByAddress(address)
if err != nil {
http.Error(w, "Internal server error reading deposits", http.StatusInternalServerError)
h.Logger.Error("Unable to read deposits from DB")
h.Logger.Error(err.Error())
return
}
response := newDepositResponse(deposits)
jsonResponse(w, h.Logger, response, http.StatusOK)
}
package routes
import (
"net/http"
)
func (h Routes) HealthzHandler(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, h.Logger, "ok", http.StatusOK)
}
package routes
import (
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/log"
)
type Routes struct {
Logger log.Logger
BridgeTransfersView database.BridgeTransfersView
}
func NewRoutes(logger log.Logger, bv database.BridgeTransfersView) Routes {
return Routes{
Logger: logger,
BridgeTransfersView: bv,
}
}
package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"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"`
}
type WithdrawalResponse struct {
Cursor string `json:"cursor"`
HasNextPage bool `json:"hasNextPage"`
Items []WithdrawalItem `json:"items"`
}
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse
func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransactionHashes) WithdrawalResponse {
items := make([]WithdrawalItem, len(withdrawals))
for _, withdrawal := range 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.Int.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.L1TokenAddress.String(),
Name: "Example", // TODO
Symbol: "EXAMPLE", // TODO
Decimals: 18, // TODO
Extensions: Extensions{
OptimismBridgeAddress: "0x636Af16bf2f682dD3109e60102b8E1A089FedAa8",
},
},
L2Token: TokenInfo{
ChainId: 10,
Address: withdrawal.L2BridgeWithdrawal.TokenPair.L2TokenAddress.String(),
Name: "Example", // TODO
Symbol: "EXAMPLE", // TODO
Decimals: 18, // TODO
Extensions: Extensions{
OptimismBridgeAddress: "0x36Af16bf2f682dD3109e60102b8E1A089FedAa86",
},
},
}
items = append(items, item)
}
return WithdrawalResponse{
Cursor: "42042042-0420-4204-2042-420420420420", // TODO
HasNextPage: true, // TODO
Items: items,
}
}
func (h Routes) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
address := common.HexToAddress(chi.URLParam(r, "address"))
withdrawals, err := h.BridgeTransfersView.L2BridgeWithdrawalsByAddress(address)
if err != nil {
http.Error(w, "Internal server error fetching withdrawals", http.StatusInternalServerError)
h.Logger.Error("Unable to read deposits from DB")
h.Logger.Error(err.Error())
return
}
response := newWithdrawalResponse(withdrawals)
jsonResponse(w, h.Logger, response, http.StatusOK)
}
...@@ -3,12 +3,14 @@ package cli ...@@ -3,12 +3,14 @@ package cli
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"github.com/ethereum-optimism/optimism/indexer" "github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/api"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/opio" "github.com/ethereum-optimism/optimism/op-service/opio"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -23,17 +25,28 @@ type Cli struct { ...@@ -23,17 +25,28 @@ type Cli struct {
} }
func runIndexer(ctx *cli.Context) error { func runIndexer(ctx *cli.Context) error {
logger := log.NewLogger(log.ReadCLIConfig(ctx)) logger := log.NewLogger(log.CLIConfig{
Level: "warn",
Color: false,
Format: "terminal",
})
configPath := ctx.String(ConfigFlag.Name) configPath := ctx.String(ConfigFlag.Name)
cfg, err := config.LoadConfig(configPath) cfg, err := config.LoadConfig(logger, configPath)
if err != nil { if err != nil {
logger.Error("failed to load config", "err", err) logger.Error("failed to load config", "err", err)
return err return err
} }
cfg.Logger = logger logger = log.NewLogger(cfg.Logger)
indexer, err := indexer.NewIndexer(cfg)
db, err := database.NewDB(cfg.DB)
if err != nil {
return err
}
indexer, err := indexer.NewIndexer(cfg.Chain, cfg.RPCs, db, logger)
if err != nil { if err != nil {
return err return err
} }
...@@ -51,17 +64,21 @@ func runApi(ctx *cli.Context) error { ...@@ -51,17 +64,21 @@ func runApi(ctx *cli.Context) error {
logger := log.NewLogger(log.ReadCLIConfig(ctx)) logger := log.NewLogger(log.ReadCLIConfig(ctx))
configPath := ctx.String(ConfigFlag.Name) configPath := ctx.String(ConfigFlag.Name)
cfg, err := config.LoadConfig(configPath) cfg, err := config.LoadConfig(logger, configPath)
if err != nil { if err != nil {
logger.Error("failed to load config", "err", err) logger.Error("failed to load config", "err", err)
return err return err
} }
cfg.Logger = logger db, err := database.NewDB(cfg.DB)
fmt.Println(cfg)
// finish me if err != nil {
return err logger.Crit("Failed to connect to database", "err", err)
}
server := api.NewApi(db.BridgeTransfers, logger)
return server.Listen(strconv.Itoa(cfg.API.Port))
} }
var ( var (
...@@ -81,7 +98,7 @@ func (c *Cli) Run(args []string) error { ...@@ -81,7 +98,7 @@ func (c *Cli) Run(args []string) error {
} }
func NewCli(GitVersion string, GitCommit string, GitDate string) *Cli { func NewCli(GitVersion string, GitCommit string, GitDate string) *Cli {
flags := append([]cli.Flag{ConfigFlag}, log.CLIFlags("INDEXER")...) flags := []cli.Flag{ConfigFlag}
app := &cli.App{ app := &cli.App{
Version: fmt.Sprintf("%s-%s", GitVersion, params.VersionWithCommit(GitCommit, GitDate)), Version: fmt.Sprintf("%s-%s", GitVersion, params.VersionWithCommit(GitCommit, GitDate)),
Description: "An indexer of all optimism events with a serving api layer", Description: "An indexer of all optimism events with a serving api layer",
......
package config package config
import ( import (
"fmt"
"os" "os"
"reflect"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/ethereum-optimism/optimism/indexer/processor" "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/common"
geth_log "github.com/ethereum/go-ethereum/log"
"github.com/joho/godotenv" "github.com/joho/godotenv"
) )
// in future presets can just be onchain config and fetched on initialization
// Config represents the `indexer.toml` file used to configure the indexer // Config represents the `indexer.toml` file used to configure the indexer
type Config struct { type Config struct {
Chain ChainConfig Chain ChainConfig
...@@ -17,14 +22,42 @@ type Config struct { ...@@ -17,14 +22,42 @@ type Config struct {
DB DBConfig DB DBConfig
API APIConfig API APIConfig
Metrics MetricsConfig Metrics MetricsConfig
Logger log.Logger `toml:"-"` Logger log.CLIConfig
}
// fetch this via onchain config from RPCsConfig and remove from config in future
type L1Contracts struct {
OptimismPortal common.Address
L2OutputOracle common.Address
L1CrossDomainMessenger common.Address
L1StandardBridge common.Address
L1ERC721Bridge common.Address
// Some more contracts -- ProxyAdmin, SystemConfig, etcc
// Ignore the auxiliary contracts?
// Legacy contracts? We'll add this in to index the legacy chain.
// Remove afterwards?
}
func (c L1Contracts) ToSlice() []common.Address {
fields := reflect.VisibleFields(reflect.TypeOf(c))
v := reflect.ValueOf(c)
contracts := make([]common.Address, len(fields))
for i, field := range fields {
contracts[i] = (v.FieldByName(field.Name).Interface()).(common.Address)
}
return contracts
} }
// ChainConfig configures of the chain being indexed // ChainConfig configures of the chain being indexed
type ChainConfig struct { type ChainConfig struct {
// Configure known chains with the l2 chain id // Configure known chains with the l2 chain id
Preset int Preset int
L1Contracts processor.L1Contracts // Configure custom chains via providing the L1Contract addresses
L1Contracts L1Contracts
} }
// RPCsConfig configures the RPC urls // RPCsConfig configures the RPC urls
...@@ -55,32 +88,38 @@ type MetricsConfig struct { ...@@ -55,32 +88,38 @@ type MetricsConfig struct {
} }
// LoadConfig loads the `indexer.toml` config file from a given path // LoadConfig loads the `indexer.toml` config file from a given path
func LoadConfig(path string) (Config, error) { func LoadConfig(logger geth_log.Logger, path string) (Config, error) {
if err := godotenv.Load(); err != nil { if err := godotenv.Load(); err != nil {
log.Warn("Unable to load .env file", err) logger.Warn("Unable to load .env file", err)
log.Info("Continuing without .env file") logger.Info("Continuing without .env file")
} else { } else {
log.Info("Loaded .env file") logger.Info("Loaded .env file")
} }
var conf Config var conf Config
// Read the config file.
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
return conf, err return conf, err
} }
// Replace environment variables.
data = []byte(os.ExpandEnv(string(data))) data = []byte(os.ExpandEnv(string(data)))
// Decode the TOML data.
if _, err := toml.Decode(string(data), &conf); err != nil { if _, err := toml.Decode(string(data), &conf); err != nil {
log.Info("Failed to decode config file", "message", err) logger.Info("Failed to decode config file", "message", err)
return conf, err return conf, err
} }
log.Debug("Loaded config file", conf) if conf.Chain.Preset != 0 {
knownContracts, ok := presetL1Contracts[conf.Chain.Preset]
if ok {
conf.Chain.L1Contracts = knownContracts
} else {
return conf, fmt.Errorf("unknown preset: %d", conf.Chain.Preset)
}
}
logger.Debug("Loaded config file", conf)
return conf, nil return conf, nil
} }
package config package config
import ( import (
"fmt"
"os" "os"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestLoadConfig(t *testing.T) { func TestLoadConfig(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
tmpfile, err := os.CreateTemp("", "test.toml") tmpfile, err := os.CreateTemp("", "test.toml")
require.NoError(t, err) require.NoError(t, err)
defer os.Remove(tmpfile.Name()) defer os.Remove(tmpfile.Name())
...@@ -15,7 +20,7 @@ func TestLoadConfig(t *testing.T) { ...@@ -15,7 +20,7 @@ func TestLoadConfig(t *testing.T) {
testData := ` testData := `
[chain] [chain]
preset = 1234 preset = 420
[rpcs] [rpcs]
l1-rpc = "https://l1.example.com" l1-rpc = "https://l1.example.com"
...@@ -45,10 +50,15 @@ func TestLoadConfig(t *testing.T) { ...@@ -45,10 +50,15 @@ func TestLoadConfig(t *testing.T) {
err = tmpfile.Close() err = tmpfile.Close()
require.NoError(t, err) require.NoError(t, err)
conf, err := LoadConfig(tmpfile.Name()) conf, err := LoadConfig(logger, tmpfile.Name())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, conf.Chain.Preset, 1234) require.Equal(t, conf.Chain.Preset, 420)
require.Equal(t, conf.Chain.L1Contracts.OptimismPortal.String(), presetL1Contracts[420].OptimismPortal.String())
require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessenger.String(), presetL1Contracts[420].L1CrossDomainMessenger.String())
require.Equal(t, conf.Chain.L1Contracts.L1ERC721Bridge.String(), presetL1Contracts[420].L1ERC721Bridge.String())
require.Equal(t, conf.Chain.L1Contracts.L1StandardBridge.String(), presetL1Contracts[420].L1StandardBridge.String())
require.Equal(t, conf.Chain.L1Contracts.L2OutputOracle.String(), presetL1Contracts[420].L2OutputOracle.String())
require.Equal(t, conf.RPCs.L1RPC, "https://l1.example.com") require.Equal(t, conf.RPCs.L1RPC, "https://l1.example.com")
require.Equal(t, conf.RPCs.L2RPC, "https://l2.example.com") require.Equal(t, conf.RPCs.L2RPC, "https://l2.example.com")
require.Equal(t, conf.DB.Host, "127.0.0.1") require.Equal(t, conf.DB.Host, "127.0.0.1")
...@@ -61,3 +71,69 @@ func TestLoadConfig(t *testing.T) { ...@@ -61,3 +71,69 @@ func TestLoadConfig(t *testing.T) {
require.Equal(t, conf.Metrics.Host, "127.0.0.1") require.Equal(t, conf.Metrics.Host, "127.0.0.1")
require.Equal(t, conf.Metrics.Port, 7300) require.Equal(t, conf.Metrics.Port, 7300)
} }
func TestLoadConfig_WithoutPreset(t *testing.T) {
tmpfile, err := os.CreateTemp("", "test_without_preset.toml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
defer tmpfile.Close()
testData := `
[chain]
l1contracts = { OptimismPortal = "0x4205Fc579115071764c7423A4f12eDde41f106Ed", L2OutputOracle = "0x42097868233d1aa22e815a266982f2cf17685a27", L1CrossDomainMessenger = "0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1", L1StandardBridge = "0x4209fc46f92E8a1c0deC1b1747d010903E884bE1", L1ERC721Bridge ="0x420749f83b81B301cAb5f48EB8516B986DAef23D" }
[rpcs]
l1-rpc = "https://l1.example.com"
l2-rpc = "https://l2.example.com"
`
data := []byte(testData)
err = os.WriteFile(tmpfile.Name(), data, 0644)
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = tmpfile.Close()
require.NoError(t, err)
logger := testlog.Logger(t, log.LvlInfo)
conf, err := LoadConfig(logger, tmpfile.Name())
require.NoError(t, err)
require.Equal(t, conf.Chain.L1Contracts.OptimismPortal.String(), common.HexToAddress("0x4205Fc579115071764c7423A4f12eDde41f106Ed").String())
require.Equal(t, conf.Chain.L1Contracts.L2OutputOracle.String(), common.HexToAddress("0x42097868233d1aa22e815a266982f2cf17685a27").String())
require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessenger.String(), common.HexToAddress("0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1").String())
require.Equal(t, conf.Chain.L1Contracts.L1StandardBridge.String(), common.HexToAddress("0x4209fc46f92E8a1c0deC1b1747d010903E884bE1").String())
require.Equal(t, conf.Chain.L1Contracts.L1ERC721Bridge.String(), common.HexToAddress("0x420749f83b81B301cAb5f48EB8516B986DAef23D").String())
require.Equal(t, conf.Chain.Preset, 0)
}
func TestLoadConfig_WithUnknownPreset(t *testing.T) {
tmpfile, err := os.CreateTemp("", "test_bad_preset.toml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
defer tmpfile.Close()
testData := `
[chain]
preset = 1234567890 # this preset doesn't exist
[rpcs]
l1-rpc = "https://l1.example.com"
l2-rpc = "https://l2.example.com"
`
data := []byte(testData)
err = os.WriteFile(tmpfile.Name(), data, 0644)
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
err = tmpfile.Close()
require.NoError(t, err)
logger := testlog.Logger(t, log.LvlInfo)
conf, err := LoadConfig(logger, tmpfile.Name())
var faultyPreset = 1234567890
require.Equal(t, conf.Chain.Preset, faultyPreset)
require.Error(t, err)
require.Equal(t, fmt.Sprintf("unknown preset: %d", faultyPreset), err.Error())
}
package config
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 presetL1Contracts = map[int]L1Contracts{
// OP Mainnet
10: {
OptimismPortal: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"),
L2OutputOracle: common.HexToAddress("0xdfe97868233d1aa22e815a266982f2cf17685a27"),
L1CrossDomainMessenger: common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1"),
L1StandardBridge: common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1"),
L1ERC721Bridge: common.HexToAddress("0x5a7749f83b81B301cAb5f48EB8516B986DAef23D"),
},
// OP Goerli
420: {
OptimismPortal: common.HexToAddress("0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"),
L2OutputOracle: common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"),
L1CrossDomainMessenger: common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"),
L1StandardBridge: common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"),
L1ERC721Bridge: common.HexToAddress("0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9"),
},
// Base Mainnet
8453: {
OptimismPortal: common.HexToAddress("0x49048044D57e1C92A77f79988d21Fa8fAF74E97e"),
L2OutputOracle: common.HexToAddress("0x56315b90c40730925ec5485cf004d835058518A0"),
L1CrossDomainMessenger: common.HexToAddress("0x866E82a600A1414e583f7F13623F1aC5d58b0Afa"),
L1StandardBridge: common.HexToAddress("0x3154Cf16ccdb4C6d922629664174b904d80F2C35"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
},
// Base Goerli
84531: {
OptimismPortal: common.HexToAddress("0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA"),
L2OutputOracle: common.HexToAddress("0x2A35891ff30313CcFa6CE88dcf3858bb075A2298"),
L1CrossDomainMessenger: common.HexToAddress("0x8e5693140eA606bcEB98761d9beB1BC87383706D"),
L1StandardBridge: common.HexToAddress("0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
},
// Zora mainnet
7777777: {
OptimismPortal: common.HexToAddress("0x1a0ad011913A150f69f6A19DF447A0CfD9551054"),
L2OutputOracle: common.HexToAddress("0x9E6204F750cD866b299594e2aC9eA824E2e5f95c"),
L1CrossDomainMessenger: common.HexToAddress("0xdC40a14d9abd6F410226f1E6de71aE03441ca506"),
L1StandardBridge: common.HexToAddress("0x3e2Ea9B92B7E48A52296fD261dc26fd995284631"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
},
// Zora goerli
999: {
OptimismPortal: common.HexToAddress("0xDb9F51790365e7dc196e7D072728df39Be958ACe"),
L2OutputOracle: common.HexToAddress("0xdD292C9eEd00f6A32Ff5245d0BCd7f2a15f24e00"),
L1CrossDomainMessenger: common.HexToAddress("0xD87342e16352D33170557A7dA1e5fB966a60FafC"),
L1StandardBridge: common.HexToAddress("0x7CC09AC2452D6555d5e0C213Ab9E2d44eFbFc956"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
},
}
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
package database package database
import ( import (
"fmt"
"github.com/ethereum-optimism/optimism/indexer/config"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
...@@ -17,7 +20,14 @@ type DB struct { ...@@ -17,7 +20,14 @@ type DB struct {
BridgeTransactions BridgeTransactionsDB BridgeTransactions BridgeTransactionsDB
} }
func NewDB(dsn string) (*DB, error) { func NewDB(dbConfig config.DBConfig) (*DB, error) {
dsn := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable", dbConfig.Host, dbConfig.Port, dbConfig.Name)
if dbConfig.User != "" {
dsn += fmt.Sprintf(" user=%s", dbConfig.User)
}
if dbConfig.Password != "" {
dsn += fmt.Sprintf(" password=%s", dbConfig.Password)
}
gorm, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ gorm, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
// The indexer will explicitly manage the transaction // The indexer will explicitly manage the transaction
// flow processing blocks // flow processing blocks
......
...@@ -17,6 +17,29 @@ services: ...@@ -17,6 +17,29 @@ services:
- postgres_data:/data/postgres - postgres_data:/data/postgres
indexer: indexer:
build:
context: ..
dockerfile: indexer/Dockerfile.refresh
command: ["indexer-refresh", "processor"]
# healthcheck:
# Add healthcheck once figure out good way how
# maybe after we add metrics?
ports:
- 8080:8080
environment:
- INDEXER_DB_PORT=5432
- INDEXER_DB_USER=db_username
- INDEXER_DB_PASSWORD=db_password
- INDEXER_DB_NAME=db_name
- INDEXER_DB_HOST=postgres
- INDEXER_CONFIG=/configs/indexer.toml
volumes:
- ./indexer.toml:/configs/indexer.toml
depends_on:
postgres:
condition: service_healthy
api:
build: build:
context: .. context: ..
dockerfile: indexer/Dockerfile dockerfile: indexer/Dockerfile
...@@ -75,5 +98,121 @@ services: ...@@ -75,5 +98,121 @@ services:
postgres: postgres:
condition: service_healthy condition: service_healthy
gateway-frontend:
command: pnpm nx start @gateway/frontend --host 0.0.0.0 --port 5173
# Change tag to `latest` after https://github.com/ethereum-optimism/gateway/pull/2541 merges
image: ethereumoptimism/gateway-frontend:0687c408e4f85cbe81acf7a197e861df8c7282df
ports:
- 5173:5173
healthcheck:
test: curl http://0.0.0.0:5173
environment:
- VITE_GROWTHBOOK=${VITE_GROWTHBOOK:-https://cdn.growthbook.io/api/features/dev_iGoAbSwtGOtEJONeHdVTosV0BD3TvTPttAccGyRxqsk}
- VITE_ENABLE_DEVNET=true
- VITE_RPC_URL_ETHEREUM_MAINNET=$VITE_RPC_URL_ETHEREUM_MAINNET
- VITE_RPC_URL_ETHEREUM_OPTIMISM_MAINNET=$VITE_RPC_URL_OPTIMISM_MAINNET
- VITE_RPC_URL_ETHEREUM_GOERLI=$VITE_RPC_URL_ETHEREUM_GOERLI
- VITE_RPC_URL_ETHEREUM_OPTIMISM_GOERLI=$VITE_RPC_URL_OPTIMISM_GOERLI
- VITE_BACKEND_URL_MAINNET=http://localhost:7421
- VITE_BACKEND_URL_GOERLI=http://localhost:7422
- VITE_ENABLE_ALL_FEATURES=true
backend-mainnet:
image: ethereumoptimism/gateway-backend:latest
environment:
# this enables the backend to proxy history requests to the indexer
- BRIDGE_INDEXER_URI=http://api
- HOST=0.0.0.0
- PORT=7300
- MIGRATE_APP_DB_USER=${MIGRATE_APP_DB_USER:-postgres}
- MIGRATE_APP_DB_PASSWORD=${MIGRATE_APP_DB_PASSWORD:-db_password}
- APP_DB_HOST=${APP_DB_HOST:-postgres-app}
- APP_DB_USER=${APP_DB_USER:-gateway-backend-mainnet@oplabs-local-web.iam}
- APP_DB_NAME=${APP_DB_NAME:-gateway}
- APP_DB_PORT=${APP_DB_PORT:-5432}
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_HOST=postgres-mainnet
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_USER=db_username
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_PASS=db_password
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_NAME=db_name
# THis is for the legacy indexer which won't be used but the env variable is still required
- INDEXER_DB_PORT=5432
# THis is for the legacy indexer which won't be used but the env variable is still required
- DATABASE_URL=postgres://db_username:db_password@postgres-mainnet:5432/db_name
- JSON_RPC_URLS_L1=$JSON_RPC_URLS_L1_MAINNET
- JSON_RPC_URLS_L2=$JSON_RPC_URLS_L2_MAINNET
- JSON_RPC_URLS_L2_GOERLI=$JSON_RPC_URLS_L2_GOERLI
# anvil[0] privater key as placeholder
- FAUCET_AUTH_ADMIN_WALLET_PRIVATE_KEY=${$FAUCET_AUTH_ADMIN_WALLET_PRIVATE_KEY:-0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80}
- IRON_SESSION_SECRET=${IRON_SESSION_SECRET:-UNKNOWN_IRON_SESSION_PASSWORD_32}
- CHAIN_ID_L1=1
- CHAIN_ID_L2=10
- FLEEK_BUCKET_ADDRESS=34a609661-6774-441f-9fdb-453fdbb89931-bucket
- FLEEK_API_SECRET=$FLEEK_API_SECRET
- FLEEK_API_KEY=$FLEEK_API_KEY
- MOCK_MERKLE_PROOF=true
- LOOP_INTERVAL_MINUTES=.1
- GITHUB_CLIENT_ID=$GITHUB_CLIENT_ID
- GITHUB_SECRET=$GITHUB_SECRET
- MAINNET_BEDROCK=$MAINNET_BEDROCK
- TRM_API_KEY=$TRM_API_KEY
- GOOGLE_CLOUD_STORAGE_BUCKET_NAME=oplabs-dev-web-content
# Recommened to uncomment for local dev unless you need it
#- BYPASS_EVENT_LOG_POLLER_BOOTSTRAP=true
ports:
- 7421:7300
# overrides command in Dockerfile so we can hot reload the server in docker while developing
#command: ['pnpm', 'nx', 'run', '@gateway/backend:docker:watch']
healthcheck:
test: curl http://0.0.0.0:7300/api/v0/healthz
backend-goerli:
image: ethereumoptimism/gateway-backend:latest
environment:
# this enables the backend to proxy history requests to the indexer
- BRIDGE_INDEXER_URI=http://api
- HOST=0.0.0.0
- PORT=7300
- MIGRATE_APP_DB_USER=${MIGRATE_APP_DB_USER:-postgres}
- MIGRATE_APP_DB_PASSWORD=${MIGRATE_APP_DB_PASSWORD:-db_password}
- APP_DB_HOST=${APP_DB_HOST:-postgres-app}
- APP_DB_USER=${APP_DB_USER:-gateway-backend-goerli@oplabs-local-web.iam}
- APP_DB_NAME=${APP_DB_NAME:-gateway}
- APP_DB_PORT=${APP_DB_PORT:-5432}
- INDEXER_DB_HOST=${INDEXER_DB_HOST_GOERLI:-postgres-goerli}
- INDEXER_DB_USER=${INDEXER_DB_USER_GOERLI:-db_username}
- INDEXER_DB_PASS=${INDEXER_DB_PASSWORD_GOERLI:-db_password}
- INDEXER_DB_NAME=${INDEXER_DB_NAME_GOERLI:-db_name}
- INDEXER_DB_PORT=${INDEXER_DB_PORT_GOERLI:-5432}
- DATABASE_URL=${DATABASE_URL_GOERLI:-postgres://db_username:db_password@postgres-goerli:5432/db_name}
- JSON_RPC_URLS_L1=$JSON_RPC_URLS_L1_GOERLI
- JSON_RPC_URLS_L2=$JSON_RPC_URLS_L2_GOERLI
- JSON_RPC_URLS_L2_GOERLI=$JSON_RPC_URLS_L2_GOERLI
- FAUCET_AUTH_ADMIN_WALLET_PRIVATE_KEY=$FAUCET_AUTH_ADMIN_WALLET_PRIVATE_KEY
- IRON_SESSION_SECRET=${IRON_SESSION_SECRET:-UNKNOWN_IRON_SESSION_PASSWORD_32}
- CHAIN_ID_L1=5
- CHAIN_ID_L2=420
- FLEEK_BUCKET_ADDRESS=34a609661-6774-441f-9fdb-453fdbb89931-bucket
- FLEEK_API_SECRET=$FLEEK_API_SECRET
- FLEEK_API_KEY=$FLEEK_API_KEY
- MOCK_MERKLE_PROOF=true
- LOOP_INTERVAL_MINUTES=.1
- GITHUB_CLIENT_ID=$GITHUB_CLIENT_ID
- GITHUB_SECRET=$GITHUB_SECRET
- MAINNET_BEDROCK=true
- TRM_API_KEY=$TRM_API_KEY
- GOOGLE_CLOUD_STORAGE_BUCKET_NAME=oplabs-dev-web-content
# Recommened to uncomment for local dev unless you need it
#- BYPASS_EVENT_LOG_POLLER_BOOTSTRAP=true
ports:
- 7422:7300
# overrides command in Dockerfile so we can hot reload the server in docker while developing
#command: ['pnpm', 'nx', 'run', '@gateway/backend:docker:watch']
healthcheck:
test: curl http://0.0.0.0:7300/api/v0/healthz
volumes: volumes:
postgres_data: postgres_data:
...@@ -13,10 +13,10 @@ import ( ...@@ -13,10 +13,10 @@ import (
"github.com/ethereum-optimism/optimism/indexer" "github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/processor"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
op_log "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -59,7 +59,10 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite { ...@@ -59,7 +59,10 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
// Indexer Configuration and Start // Indexer Configuration and Start
indexerCfg := config.Config{ indexerCfg := config.Config{
Logger: logger,
Logger: op_log.CLIConfig{
Level: "warn",
},
DB: config.DBConfig{ DB: config.DBConfig{
Host: "127.0.0.1", Host: "127.0.0.1",
Port: 5432, Port: 5432,
...@@ -71,7 +74,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite { ...@@ -71,7 +74,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
L2RPC: opSys.Nodes["sequencer"].HTTPEndpoint(), L2RPC: opSys.Nodes["sequencer"].HTTPEndpoint(),
}, },
Chain: config.ChainConfig{ Chain: config.ChainConfig{
L1Contracts: processor.L1Contracts{ L1Contracts: config.L1Contracts{
OptimismPortal: opCfg.L1Deployments.OptimismPortalProxy, OptimismPortal: opCfg.L1Deployments.OptimismPortalProxy,
L2OutputOracle: opCfg.L1Deployments.L2OutputOracleProxy, L2OutputOracle: opCfg.L1Deployments.L2OutputOracleProxy,
L1CrossDomainMessenger: opCfg.L1Deployments.L1CrossDomainMessengerProxy, L1CrossDomainMessenger: opCfg.L1Deployments.L1CrossDomainMessengerProxy,
...@@ -81,9 +84,14 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite { ...@@ -81,9 +84,14 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
}, },
} }
db, err := database.NewDB(fmt.Sprintf("postgres://%s@localhost:5432/%s?sslmode=disable", dbUser, dbName)) db, err := database.NewDB(indexerCfg.DB)
require.NoError(t, err) require.NoError(t, err)
indexer, err := indexer.NewIndexer(indexerCfg) indexer, err := indexer.NewIndexer(
indexerCfg.Chain,
indexerCfg.RPCs,
db,
logger,
)
require.NoError(t, err) require.NoError(t, err)
indexerStoppedCh := make(chan interface{}, 1) indexerStoppedCh := make(chan interface{}, 1)
......
...@@ -24,44 +24,31 @@ type Indexer struct { ...@@ -24,44 +24,31 @@ type Indexer struct {
} }
// NewIndexer initializes an instance of the Indexer // NewIndexer initializes an instance of the Indexer
func NewIndexer(cfg config.Config) (*Indexer, error) { func NewIndexer(chainConfig config.ChainConfig, rpcsConfig config.RPCsConfig, db *database.DB, logger log.Logger) (*Indexer, error) {
dsn := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable", cfg.DB.Host, cfg.DB.Port, cfg.DB.Name) l1Contracts := chainConfig.L1Contracts
if cfg.DB.User != "" { l1EthClient, err := node.DialEthClient(rpcsConfig.L1RPC)
dsn += fmt.Sprintf(" user=%s", cfg.DB.User)
}
if cfg.DB.Password != "" {
dsn += fmt.Sprintf(" password=%s", cfg.DB.Password)
}
db, err := database.NewDB(dsn)
if err != nil {
return nil, err
}
l1Contracts := cfg.Chain.L1Contracts
l1EthClient, err := node.DialEthClient(cfg.RPCs.L1RPC)
if err != nil { if err != nil {
return nil, err return nil, err
} }
l1Processor, err := processor.NewL1Processor(cfg.Logger, l1EthClient, db, l1Contracts) l1Processor, err := processor.NewL1Processor(logger, l1EthClient, db, l1Contracts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// L2Processor (predeploys). Although most likely the right setting, make this configurable? // L2Processor (predeploys). Although most likely the right setting, make this configurable?
l2Contracts := processor.L2ContractPredeploys() l2Contracts := processor.L2ContractPredeploys()
l2EthClient, err := node.DialEthClient(cfg.RPCs.L2RPC) l2EthClient, err := node.DialEthClient(rpcsConfig.L2RPC)
if err != nil { if err != nil {
return nil, err return nil, err
} }
l2Processor, err := processor.NewL2Processor(cfg.Logger, l2EthClient, db, l2Contracts) l2Processor, err := processor.NewL2Processor(logger, l2EthClient, db, l2Contracts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
indexer := &Indexer{ indexer := &Indexer{
db: db, db: db,
log: cfg.Logger, log: logger,
L1Processor: l1Processor, L1Processor: l1Processor,
L2Processor: l2Processor, L2Processor: l2Processor,
} }
......
# Chain configures l1 chain addresses
# Can configure them manually or use a preset l2 ChainId for known chains including OP Mainnet, OP Goerli, Base, Base Goerli, Zora, and Zora goerli
[chain] [chain]
# OP Goerli
preset = 420 preset = 420
[rpcs] [rpcs]
l1-rpc = "${INDEXER_RPC_URL_L1}" l1-rpc = "${INDEXER_RPC_URL_L1}"
l2-rpc = "${INDEXER_RPC_URL_L2}" l2-rpc = "${INDEXER_RPC_URL_L2}"
...@@ -20,3 +22,11 @@ port = 8080 ...@@ -20,3 +22,11 @@ port = 8080
host = "127.0.0.1" host = "127.0.0.1"
port = 7300 port = 7300
[logger]
# Log level: trace, debug, info, warn, error, crit. Capitals are accepted too.
level = "info"
# Color the log output. Defaults to true if terminal is detected.
color = true
# Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty'
format = "terminal"
...@@ -6,8 +6,8 @@ import ( ...@@ -6,8 +6,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"reflect"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
...@@ -23,32 +23,6 @@ import ( ...@@ -23,32 +23,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type L1Contracts struct {
OptimismPortal common.Address
L2OutputOracle common.Address
L1CrossDomainMessenger common.Address
L1StandardBridge common.Address
L1ERC721Bridge common.Address
// Some more contracts -- ProxyAdmin, SystemConfig, etcc
// Ignore the auxiliary contracts?
// Legacy contracts? We'll add this in to index the legacy chain.
// Remove afterwards?
}
func (c L1Contracts) ToSlice() []common.Address {
fields := reflect.VisibleFields(reflect.TypeOf(c))
v := reflect.ValueOf(c)
contracts := make([]common.Address, len(fields))
for i, field := range fields {
contracts[i] = (v.FieldByName(field.Name).Interface()).(common.Address)
}
return contracts
}
type checkpointAbi struct { type checkpointAbi struct {
l2OutputOracle *abi.ABI l2OutputOracle *abi.ABI
legacyStateCommitmentChain *abi.ABI legacyStateCommitmentChain *abi.ABI
...@@ -58,7 +32,7 @@ type L1Processor struct { ...@@ -58,7 +32,7 @@ type L1Processor struct {
processor processor
} }
func NewL1Processor(logger log.Logger, ethClient node.EthClient, db *database.DB, l1Contracts L1Contracts) (*L1Processor, error) { func NewL1Processor(logger log.Logger, ethClient node.EthClient, db *database.DB, l1Contracts config.L1Contracts) (*L1Processor, error) {
l1ProcessLog := logger.New("processor", "l1") l1ProcessLog := logger.New("processor", "l1")
l1ProcessLog.Info("initializing processor") l1ProcessLog.Info("initializing processor")
...@@ -107,7 +81,7 @@ func NewL1Processor(logger log.Logger, ethClient node.EthClient, db *database.DB ...@@ -107,7 +81,7 @@ func NewL1Processor(logger log.Logger, ethClient node.EthClient, db *database.DB
return l1Processor, nil return l1Processor, nil
} }
func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1Contracts, checkpointAbi checkpointAbi) ProcessFn { func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts config.L1Contracts, checkpointAbi checkpointAbi) ProcessFn {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient()) rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())
contractAddrs := l1Contracts.ToSlice() contractAddrs := l1Contracts.ToSlice()
...@@ -261,7 +235,7 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1 ...@@ -261,7 +235,7 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1
} }
} }
func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *database.DB, l1Contracts L1Contracts, events *ProcessedContractEvents) error { func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *database.DB, l1Contracts config.L1Contracts, events *ProcessedContractEvents) error {
// (1) Process New Deposits // (1) Process New Deposits
portalDeposits, err := OptimismPortalTransactionDepositEvents(events) portalDeposits, err := OptimismPortalTransactionDepositEvents(events)
if err != nil { if err != nil {
...@@ -294,6 +268,7 @@ func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *databa ...@@ -294,6 +268,7 @@ func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *databa
TransactionSourceHash: depositTx.SourceHash, TransactionSourceHash: depositTx.SourceHash,
Tx: transactionDeposits[i].Tx, Tx: transactionDeposits[i].Tx,
TokenPair: database.TokenPair{ TokenPair: database.TokenPair{
// TODO index eth token if it doesn't exist
L1TokenAddress: predeploys.LegacyERC20ETHAddr, L1TokenAddress: predeploys.LegacyERC20ETHAddr,
L2TokenAddress: predeploys.LegacyERC20ETHAddr, L2TokenAddress: predeploys.LegacyERC20ETHAddr,
}, },
...@@ -492,6 +467,7 @@ func l1ProcessContractEventsStandardBridge(processLog log.Logger, db *database.D ...@@ -492,6 +467,7 @@ func l1ProcessContractEventsStandardBridge(processLog log.Logger, db *database.D
deposits[i] = &database.L1BridgeDeposit{ deposits[i] = &database.L1BridgeDeposit{
TransactionSourceHash: depositTx.SourceHash, TransactionSourceHash: depositTx.SourceHash,
CrossDomainMessengerNonce: &database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce}, CrossDomainMessengerNonce: &database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
// TODO index the tokens pairs if they don't exist
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken}, TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
Tx: database.Transaction{ Tx: database.Transaction{
FromAddress: initiatedBridgeEvent.From, FromAddress: initiatedBridgeEvent.From,
......
This diff is collapsed.
This diff is collapsed.
...@@ -11,7 +11,9 @@ import ( ...@@ -11,7 +11,9 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet" "github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -52,6 +54,7 @@ type FactoryHelper struct { ...@@ -52,6 +54,7 @@ type FactoryHelper struct {
require *require.Assertions require *require.Assertions
client *ethclient.Client client *ethclient.Client
opts *bind.TransactOpts opts *bind.TransactOpts
factoryAddr common.Address
factory *bindings.DisputeGameFactory factory *bindings.DisputeGameFactory
blockOracle *bindings.BlockOracle blockOracle *bindings.BlockOracle
l2oo *bindings.L2OutputOracleCaller l2oo *bindings.L2OutputOracleCaller
...@@ -65,7 +68,8 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1 ...@@ -65,7 +68,8 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1
require.NoError(err) require.NoError(err)
require.NotNil(deployments, "No deployments") require.NotNil(deployments, "No deployments")
factory, err := bindings.NewDisputeGameFactory(deployments.DisputeGameFactoryProxy, client) factoryAddr := deployments.DisputeGameFactoryProxy
factory, err := bindings.NewDisputeGameFactory(factoryAddr, client)
require.NoError(err) require.NoError(err)
blockOracle, err := bindings.NewBlockOracle(deployments.BlockOracle, client) blockOracle, err := bindings.NewBlockOracle(deployments.BlockOracle, client)
require.NoError(err) require.NoError(err)
...@@ -78,6 +82,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1 ...@@ -78,6 +82,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1
client: client, client: client,
opts: opts, opts: opts,
factory: factory, factory: factory,
factoryAddr: factoryAddr,
blockOracle: blockOracle, blockOracle: blockOracle,
l2oo: l2oo, l2oo: l2oo,
} }
...@@ -150,6 +155,21 @@ func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Ha ...@@ -150,6 +155,21 @@ func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Ha
}, },
} }
} }
func (h *FactoryHelper) StartChallenger(ctx context.Context, l1Endpoint string, name string, options ...challenger.Option) *challenger.Helper {
opts := []challenger.Option{
func(c *config.Config) {
// Uncomment when challenger actually supports setting the game factory address
//c.FactoryAddress = h.factoryAddr
c.TraceType = config.TraceTypeAlphabet
},
}
opts = append(opts, options...)
c := challenger.NewChallenger(h.t, ctx, l1Endpoint, name, opts...)
h.t.Cleanup(func() {
_ = c.Close()
})
return c
}
// waitForProposals waits until there are at least two proposals in the output oracle // waitForProposals waits until there are at least two proposals in the output oracle
// This is the minimum required for creating a game. // This is the minimum required for creating a game.
......
...@@ -13,6 +13,37 @@ import ( ...@@ -13,6 +13,37 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestCannonMultipleGames(t *testing.T) {
t.Skip("Challenger doesn't yet support multiple games")
InitParallel(t)
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
gameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
// Start a challenger with the correct alphabet trace
gameFactory.StartChallenger(ctx, sys.NodeEndpoint("l1"), "TowerDefense", func(c *config.Config) {
c.AgreeWithProposedOutput = true
c.AlphabetTrace = "abcdefg"
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice)
})
game1 := gameFactory.StartAlphabetGame(ctx, "abcxyz")
// Wait for the challenger to respond to the first game
game1.WaitForClaimCount(ctx, 2)
game2 := gameFactory.StartAlphabetGame(ctx, "zyxabc")
// Wait for the challenger to respond to the second game
game2.WaitForClaimCount(ctx, 2)
// Challenger should respond to new claims
game2.Attack(ctx, 1, common.Hash{0xaa})
game2.WaitForClaimCount(ctx, 4)
game1.Defend(ctx, 1, common.Hash{0xaa})
game1.WaitForClaimCount(ctx, 4)
}
func TestResolveDisputeGame(t *testing.T) { func TestResolveDisputeGame(t *testing.T) {
InitParallel(t) InitParallel(t)
......
...@@ -17,6 +17,10 @@ func (e *ErrFailedPermanently) Error() string { ...@@ -17,6 +17,10 @@ func (e *ErrFailedPermanently) Error() string {
return fmt.Sprintf("operation failed permanently after %d attempts: %v", e.attempts, e.LastErr) return fmt.Sprintf("operation failed permanently after %d attempts: %v", e.attempts, e.LastErr)
} }
func (e *ErrFailedPermanently) Unwrap() error {
return e.LastErr
}
type pair[T, U any] struct { type pair[T, U any] struct {
a T a T
b U b U
...@@ -35,37 +39,27 @@ func Do2[T, U any](ctx context.Context, maxAttempts int, strategy Strategy, op f ...@@ -35,37 +39,27 @@ func Do2[T, U any](ctx context.Context, maxAttempts int, strategy Strategy, op f
// with delays in between each retry according to the provided // with delays in between each retry according to the provided
// Strategy. // Strategy.
func Do[T any](ctx context.Context, maxAttempts int, strategy Strategy, op func() (T, error)) (T, error) { func Do[T any](ctx context.Context, maxAttempts int, strategy Strategy, op func() (T, error)) (T, error) {
var empty T var empty, ret T
var err error
if maxAttempts < 1 { if maxAttempts < 1 {
return empty, fmt.Errorf("need at least 1 attempt to run op, but have %d max attempts", maxAttempts) return empty, fmt.Errorf("need at least 1 attempt to run op, but have %d max attempts", maxAttempts)
} }
var attempt int
reattemptCh := make(chan struct{}, 1)
doReattempt := func() {
reattemptCh <- struct{}{}
}
doReattempt()
for { for i := 0; i < maxAttempts; i++ {
select { if ctx.Err() != nil {
case <-ctx.Done():
return empty, ctx.Err() return empty, ctx.Err()
case <-reattemptCh: }
attempt++ ret, err = op()
ret, err := op()
if err == nil { if err == nil {
return ret, nil return ret, nil
} }
// Don't sleep when we are about to exit the loop & return ErrFailedPermanently
if attempt == maxAttempts { if i != maxAttempts-1 {
time.Sleep(strategy.Duration(i))
}
}
return empty, &ErrFailedPermanently{ return empty, &ErrFailedPermanently{
attempts: maxAttempts, attempts: maxAttempts,
LastErr: err, LastErr: err,
} }
}
time.AfterFunc(strategy.Duration(attempt-1), doReattempt)
}
}
} }
...@@ -159,7 +159,7 @@ func TestSend(t *testing.T) { ...@@ -159,7 +159,7 @@ func TestSend(t *testing.T) {
{sendErr: true}, {sendErr: true},
{}, {},
}, },
nonces: []uint64{0, 1, 1}, nonces: []uint64{0, 1},
total: 3 * time.Second, total: 3 * time.Second,
}, },
} }
......
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics" "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
) )
...@@ -175,7 +176,13 @@ func (m *SimpleTxManager) send(ctx context.Context, candidate TxCandidate) (*typ ...@@ -175,7 +176,13 @@ func (m *SimpleTxManager) send(ctx context.Context, candidate TxCandidate) (*typ
ctx, cancel = context.WithTimeout(ctx, m.cfg.TxSendTimeout) ctx, cancel = context.WithTimeout(ctx, m.cfg.TxSendTimeout)
defer cancel() defer cancel()
} }
tx, err := backoff.Do(ctx, 30, backoff.Fixed(2*time.Second), func() (*types.Transaction, error) {
tx, err := m.craftTx(ctx, candidate) tx, err := m.craftTx(ctx, candidate)
if err != nil {
m.l.Warn("Failed to create a transaction, will retry", "err", err)
}
return tx, err
})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create the tx: %w", err) return nil, fmt.Errorf("failed to create the tx: %w", err)
} }
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
"markdownlint-cli2": "0.4.0", "markdownlint-cli2": "0.4.0",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"mocha": "^10.2.0", "mocha": "^10.2.0",
"nx": "15.6.0", "nx": "16.6.0",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"patch-package": "^6.4.7", "patch-package": "^6.4.7",
"prettier": "^2.8.0", "prettier": "^2.8.0",
......
...@@ -9,12 +9,12 @@ ...@@ -9,12 +9,12 @@
"dist/*" "dist/*"
], ],
"scripts": { "scripts": {
"start:balance-mon": "ts-node ./src/balance-mon/service.ts", "start:balance-mon": "tsx ./src/balance-mon/service.ts",
"start:wallet-mon": "ts-node ./src/wallet-mon/service.ts", "start:wallet-mon": "tsx ./src/wallet-mon/service.ts",
"start:drippie-mon": "ts-node ./src/drippie-mon/service.ts", "start:drippie-mon": "tsx ./src/drippie-mon/service.ts",
"start:wd-mon": "ts-node ./src/wd-mon/service.ts", "start:wd-mon": "tsx ./src/wd-mon/service.ts",
"start:fault-mon": "ts-node ./src/fault-mon/service.ts", "start:fault-mon": "tsx ./src/fault-mon/service.ts",
"start:replica-mon": "ts-node ./src/replica-mon/service.ts", "start:replica-mon": "tsx ./src/replica-mon/service.ts",
"test": "hardhat test", "test": "hardhat test",
"test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json", "test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json",
"build": "tsc -p ./tsconfig.json", "build": "tsc -p ./tsconfig.json",
...@@ -39,21 +39,22 @@ ...@@ -39,21 +39,22 @@
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.8.3", "@eth-optimism/common-ts": "0.8.3",
"@eth-optimism/contracts-periphery": "1.0.8",
"@eth-optimism/contracts-bedrock": "0.16.0", "@eth-optimism/contracts-bedrock": "0.16.0",
"@eth-optimism/contracts-periphery": "1.0.8",
"@eth-optimism/core-utils": "0.12.2", "@eth-optimism/core-utils": "0.12.2",
"@eth-optimism/sdk": "3.1.0", "@eth-optimism/sdk": "3.1.0",
"ethers": "^5.7.0",
"dotenv": "^16.1.4",
"@types/dateformat": "^5.0.0", "@types/dateformat": "^5.0.0",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"dateformat": "^4.5.1" "dateformat": "^4.5.1",
"dotenv": "^16.1.4",
"ethers": "^5.7.0"
}, },
"devDependencies": { "devDependencies": {
"@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-provider": "^5.7.0",
"@nomiclabs/hardhat-ethers": "^2.0.6", "@nomiclabs/hardhat-ethers": "^2.0.6",
"@nomiclabs/hardhat-waffle": "^2.0.3", "@nomiclabs/hardhat-waffle": "^2.0.3",
"hardhat": "^2.9.6", "hardhat": "^2.9.6",
"ts-node": "^10.9.1" "ts-node": "^10.9.1",
"tsx": "^3.12.7"
} }
} }
...@@ -48,7 +48,7 @@ Check the list of available metrics via `pnpm start --help`: ...@@ -48,7 +48,7 @@ Check the list of available metrics via `pnpm start --help`:
```sh ```sh
> pnpm start --help > pnpm start --help
$ ts-node ./src/service.ts --help $ tsx ./src/service.ts --help
Usage: service [options] Usage: service [options]
Options: Options:
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"pino": "^6.11.3", "pino": "^6.11.3",
"pino-multi-stream": "^5.3.0", "pino-multi-stream": "^5.3.0",
"pino-sentry": "^0.7.0", "pino-sentry": "^0.14.0",
"prom-client": "^13.1.0" "prom-client": "^13.1.0"
}, },
"devDependencies": { "devDependencies": {
......
...@@ -156,7 +156,7 @@ export abstract class BaseServiceV2< ...@@ -156,7 +156,7 @@ export abstract class BaseServiceV2<
} }
// Use commander as a way to communicate info about the service. We don't actually *use* // Use commander as a way to communicate info about the service. We don't actually *use*
// commander for anything besides the ability to run `ts-node ./service.ts --help`. // commander for anything besides the ability to run `tsx ./service.ts --help`.
const program = new Command().allowUnknownOption(true) const program = new Command().allowUnknownOption(true)
for (const [optionName, optionSpec] of Object.entries(params.optionsSpec)) { for (const [optionName, optionSpec] of Object.entries(params.optionsSpec)) {
// Skip options that are not meant to be used by the user. // Skip options that are not meant to be used by the user.
......
...@@ -102,7 +102,7 @@ All test contracts and functions should be organized and named according to the ...@@ -102,7 +102,7 @@ All test contracts and functions should be organized and named according to the
These guidelines are also encoded in a script which can be run with: These guidelines are also encoded in a script which can be run with:
``` ```
ts-node scripts/forge-test-names.ts tsx scripts/forge-test-names.ts
``` ```
_Note: This is a work in progress, not all test files are compliant with these guidelines._ _Note: This is a work in progress, not all test files are compliant with these guidelines._
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
"prebuild": "./scripts/verify-foundry-install.sh", "prebuild": "./scripts/verify-foundry-install.sh",
"build:differential": "go build -o ./scripts/differential-testing/differential-testing ./scripts/differential-testing", "build:differential": "go build -o ./scripts/differential-testing/differential-testing ./scripts/differential-testing",
"build:fuzz": "(cd test-case-generator && go build ./cmd/fuzz.go)", "build:fuzz": "(cd test-case-generator && go build ./cmd/fuzz.go)",
"autogen:invariant-docs": "ts-node scripts/invariant-doc-gen.ts", "autogen:invariant-docs": "tsx scripts/invariant-doc-gen.ts",
"test": "pnpm build:differential && pnpm build:fuzz && forge test", "test": "pnpm build:differential && pnpm build:fuzz && forge test",
"coverage": "pnpm build:differential && pnpm build:fuzz && forge coverage", "coverage": "pnpm build:differential && pnpm build:fuzz && forge coverage",
"coverage:lcov": "pnpm build:differential && pnpm build:fuzz && forge coverage --report lcov", "coverage:lcov": "pnpm build:differential && pnpm build:fuzz && forge coverage --report lcov",
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
"storage-snapshot": "./scripts/storage-snapshot.sh", "storage-snapshot": "./scripts/storage-snapshot.sh",
"semver-lock": "forge script scripts/SemverLock.s.sol", "semver-lock": "forge script scripts/SemverLock.s.sol",
"validate-deploy-configs": "./scripts/validate-deploy-configs.sh", "validate-deploy-configs": "./scripts/validate-deploy-configs.sh",
"validate-spacers": "pnpm build && npx ts-node scripts/validate-spacers.ts", "validate-spacers": "pnpm build && npx tsx scripts/validate-spacers.ts",
"slither": "./scripts/slither.sh", "slither": "./scripts/slither.sh",
"slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh", "slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh",
"clean": "rm -rf ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./test-case-generator/fuzz ./scripts/differential-testing/differential-testing", "clean": "rm -rf ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./test-case-generator/fuzz ./scripts/differential-testing/differential-testing",
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
"pre-pr": "pnpm clean && pnpm gas-snapshot && pnpm storage-snapshot && pnpm semver-lock && pnpm autogen:invariant-docs && pnpm lint && (cd ../../op-bindings && make)", "pre-pr": "pnpm clean && pnpm gas-snapshot && pnpm storage-snapshot && pnpm semver-lock && pnpm autogen:invariant-docs && pnpm lint && (cd ../../op-bindings && make)",
"pre-pr:full": "pnpm test && pnpm slither && pnpm validate-deploy-configs && pnpm validate-spacers && pnpm pre-pr", "pre-pr:full": "pnpm test && pnpm slither && pnpm validate-deploy-configs && pnpm validate-spacers && pnpm pre-pr",
"lint:ts:check": "eslint . --max-warnings=0", "lint:ts:check": "eslint . --max-warnings=0",
"lint:forge-tests:check": "ts-node scripts/forge-test-names.ts", "lint:forge-tests:check": "tsx scripts/forge-test-names.ts",
"lint:contracts:check": "pnpm lint:fix && git diff --exit-code", "lint:contracts:check": "pnpm lint:fix && git diff --exit-code",
"lint:check": "pnpm lint:contracts:check && pnpm lint:ts:check", "lint:check": "pnpm lint:contracts:check && pnpm lint:ts:check",
"lint:ts:fix": "eslint --fix .", "lint:ts:fix": "eslint --fix .",
...@@ -41,9 +41,9 @@ ...@@ -41,9 +41,9 @@
"lint": "pnpm lint:fix && pnpm lint:check" "lint": "pnpm lint:fix && pnpm lint:check"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^5.60.1", "@typescript-eslint/parser": "^6.4.0",
"ts-node": "^10.9.1", "tsx": "^3.12.7",
"typescript": "^5.1.6" "typescript": "^5.1.6"
} }
} }
...@@ -2,13 +2,16 @@ import fs from 'fs' ...@@ -2,13 +2,16 @@ import fs from 'fs'
import path from 'path' import path from 'path'
import { execSync } from 'child_process' import { execSync } from 'child_process'
type Check = (parts: string[]) => boolean
type Checks = Array<{
check: Check
error: string
}>
/** /**
* Series of function name checks. * Series of function name checks.
*/ */
const checks: Array<{ const checks: Checks = [
check: (parts: string[]) => boolean
error: string
}> = [
{ {
error: 'test name parts should be in camelCase', error: 'test name parts should be in camelCase',
check: (parts: string[]): boolean => { check: (parts: string[]): boolean => {
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
"node-fetch": "^2.6.7" "node-fetch": "^2.6.7"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.12.6", "@types/node": "^20.5.0",
"mocha": "^10.0.0" "mocha": "^10.0.0"
} }
} }
...@@ -10,5 +10,6 @@ ignores: [ ...@@ -10,5 +10,6 @@ ignores: [
"eslint-config-prettier", "eslint-config-prettier",
"eslint-plugin-prettier", "eslint-plugin-prettier",
"chai", "chai",
"ts-node",
"typedoc" "typedoc"
] ]
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"mocha": "^10.0.0", "mocha": "^10.0.0",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"ts-node": "^10.9.1",
"typedoc": "^0.22.13", "typedoc": "^0.22.13",
"viem": "^0.3.30", "viem": "^0.3.30",
"vitest": "^0.28.3", "vitest": "^0.28.3",
...@@ -58,6 +59,10 @@ ...@@ -58,6 +59,10 @@
"@eth-optimism/contracts": "0.6.0", "@eth-optimism/contracts": "0.6.0",
"@eth-optimism/contracts-bedrock": "0.16.0", "@eth-optimism/contracts-bedrock": "0.16.0",
"@eth-optimism/core-utils": "0.12.2", "@eth-optimism/core-utils": "0.12.2",
"@types/chai": "^4.2.18",
"@types/chai-as-promised": "^7.1.4",
"@types/mocha": "^10.0.1",
"@types/node": "^20.5.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merkletreejs": "^0.2.27", "merkletreejs": "^0.2.27",
"rlp": "^2.2.7" "rlp": "^2.2.7"
......
...@@ -60,6 +60,22 @@ task('finalize-withdrawal', 'Finalize a withdrawal') ...@@ -60,6 +60,22 @@ task('finalize-withdrawal', 'Finalize a withdrawal')
'OptimismPortalProxy' 'OptimismPortalProxy'
) )
if (Deployment__L1StandardBridgeProxy?.address === undefined) {
throw new Error('No L1StandardBridgeProxy deployment')
}
if (Deployment__L1CrossDomainMessengerProxy?.address === undefined) {
throw new Error('No L1CrossDomainMessengerProxy deployment')
}
if (Deployment__L2OutputOracleProxy?.address === undefined) {
throw new Error('No L2OutputOracleProxy deployment')
}
if (Deployment__OptimismPortalProxy?.address === undefined) {
throw new Error('No OptimismPortalProxy deployment')
}
const messenger = new CrossChainMessenger({ const messenger = new CrossChainMessenger({
l1SignerOrProvider: signer, l1SignerOrProvider: signer,
l2SignerOrProvider: l2Signer, l2SignerOrProvider: l2Signer,
......
This diff is collapsed.
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