Commit a3bde3fb authored by Tarun Khasnavis's avatar Tarun Khasnavis Committed by GitHub

Merge branch 'develop' into develop

parents 0ca300ad 0025fff1
......@@ -1380,6 +1380,22 @@ workflows:
only: /^(proxyd|indexer|ci-builder|op-[a-z0-9\-]*)\/v.*/
branches:
ignore: /.*/
- docker-release:
name: op-heartbeat-release
filters:
tags:
only: /^op-heartbeat\/v.*/
branches:
ignore: /.*/
docker_file: op-heartbeat/Dockerfile
docker_name: op-heartbeat
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
docker_context: .
platforms: "linux/amd64,linux/arm64"
context:
- oplabs-gcr-release
requires:
- hold
- docker-release:
name: op-node-docker-release
filters:
......
......@@ -21,7 +21,6 @@
/op-program @ethereum-optimism/go-reviewers
/op-proposer @ethereum-optimism/go-reviewers
/op-service @ethereum-optimism/go-reviewers
/op-signer @ethereum-optimism/go-reviewers
/op-wheel @ethereum-optimism/go-reviewers
/ops-bedrock @ethereum-optimism/go-reviewers
......@@ -29,7 +28,6 @@
/.circleci @ethereum-optimism/infra-reviewers
/.github @ethereum-optimism/infra-reviewers
/ops @ethereum-optimism/infra-reviewers
/op-signer @ethereum-optimism/infra-reviewers
# Misc
/proxyd @ethereum-optimism/infra-reviewers
......
......@@ -197,14 +197,6 @@ pull_request_rules:
label:
add:
- A-op-service
- name: Add A-op-signer label
conditions:
- 'files~=^op-signer/'
- '#label<5'
actions:
label:
add:
- A-op-signer
- name: Add A-op-wheel label
conditions:
- 'files~=^op-wheel/'
......
......@@ -20,6 +20,7 @@ on:
options:
- ci-builder
- indexer
- op-heartbeat
- chain-mon
- op-node
- op-batcher
......
......@@ -9,4 +9,4 @@
"juanblanco.solidity",
"golang.go",
],
}
}
\ No newline at end of file
......@@ -61,7 +61,6 @@ Refer to the Directory Structure section below to understand which packages are
├── <a href="./op-program">op-program</a>: Fault proof program
├── <a href="./op-proposer">op-proposer</a>: L2-Output Submitter, submits proposals to L1
├── <a href="./op-service">op-service</a>: Common codebase utilities
├── <a href="./op-signer">op-signer</a>: Client signer
├── <a href="./op-wheel">op-wheel</a>: Database utilities
├── <a href="./ops-bedrock">ops-bedrock</a>: Bedrock devnet work
├── <a href="./packages">packages</a>
......
......@@ -8,7 +8,7 @@ require (
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20230921190252-f29074de9e36
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231001123245-7b48d3818686
github.com/ethereum/go-ethereum v1.13.1
github.com/fsnotify/fsnotify v1.6.0
github.com/go-chi/chi/v5 v5.0.10
......@@ -208,7 +208,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect
)
replace github.com/ethereum/go-ethereum v1.13.1 => github.com/ethereum-optimism/op-geth v1.101301.0-rc.2
replace github.com/ethereum/go-ethereum v1.13.1 => github.com/ethereum-optimism/op-geth v1.101301.0-rc.2.0.20231002141926-1e6910b91798
//replace github.com/ethereum-optimism/superchain-registry/superchain => ../superchain-registry/superchain
//replace github.com/ethereum/go-ethereum v1.13.1 => ../go-ethereum
......@@ -175,10 +175,10 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101301.0-rc.2 h1:CoZuN0uuEqoXlKu3iirnGlfsO1nKcIvvbyFZqUVmYN4=
github.com/ethereum-optimism/op-geth v1.101301.0-rc.2/go.mod h1:N++/klTCpY0UAU9tXFOyOEtvkjgHlNZkzzXm5I/1kzM=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20230921190252-f29074de9e36 h1:HGDz8DcAkHvZy+iPnBa8yr4MdLqKpb7oAks01P08JOg=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20230921190252-f29074de9e36/go.mod h1:q0u2UbyOr1q/y94AgMOj/V8b1KO05ZwILTR/qKt7Auo=
github.com/ethereum-optimism/op-geth v1.101301.0-rc.2.0.20231002141926-1e6910b91798 h1:WRaF/uniRnlxTVlMfFWPtMe9NefzZWg/8Fc93Nao76w=
github.com/ethereum-optimism/op-geth v1.101301.0-rc.2.0.20231002141926-1e6910b91798/go.mod h1:p02vxGt8jcF8pCwkUU5Oy56X8/JsM1Js+KC+fwihVgk=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231001123245-7b48d3818686 h1:f57hd8G96c8ORWd4ameFpveSnHcb0hA2D1VatviwoDc=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231001123245-7b48d3818686/go.mod h1:q0u2UbyOr1q/y94AgMOj/V8b1KO05ZwILTR/qKt7Auo=
github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg=
github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
......
docker-compose.dev.yml
.env
/indexer
api-ts/yarn.lock
api-ts/package-lock.json
\ No newline at end of file
......@@ -54,7 +54,11 @@ The indexer service is responsible for polling and processing real-time batches
* Process and persist new bridge events
* Synchronize L1 proven/finalized withdrawals with their L2 initialization counterparts
#### API
The indexer service runs a lightweight health server adjacently to the main service. The health server exposes a single endpoint `/healthz` that can be used to check the health of the indexer service. The health assessment doesn't check dependency health (ie. database) but rather checks the health of the indexer service itself.
### Database
The indexer service currently supports a Postgres database for storing L1/L2 OP Stack chain data. The most up-to-date database schemas can be found in the `./migrations` directory.
**NOTE:** The indexer service implementation currently does not natively support database migration. Because of this a database must be manually updated to ensure forward compatibility with the latest indexer service implementation.
\ No newline at end of file
## Metrics
The indexer services exposes a set of Prometheus metrics that can be used to monitor the health of the service. The metrics are exposed via the `/metrics` endpoint on the health server.
\ No newline at end of file
// Code generated by tygo. DO NOT EDIT.
//////////
// source: deposits.go
// source: models.go
/**
* DepositItem ... Deposit item model for API responses
*/
export interface DepositItem {
guid: string;
from: string;
......@@ -15,29 +18,23 @@ export interface DepositItem {
l1TokenAddress: string;
l2TokenAddress: string;
}
/**
* DepositResponse ... Data model for API JSON response
*/
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
/**
* WithdrawalItem ... Data model for API JSON response
*/
export interface WithdrawalItem {
guid: string;
from: string;
to: string;
transactionHash: string;
messageHash: string;
timestamp: number /* uint64 */;
l2BlockHash: string;
amount: string;
......@@ -46,6 +43,9 @@ export interface WithdrawalItem {
l1TokenAddress: string;
l2TokenAddress: string;
}
/**
* WithdrawalResponse ... Data model for API JSON response
*/
export interface WithdrawalResponse {
cursor: string;
hasNextPage: boolean;
......
......@@ -17,7 +17,7 @@
],
"scripts": {
"clean": "rm -rf generated.ts indexer.cjs indexer.js",
"generate": "npm run clean && tygo generate && mv ../api/routes/index.ts generated.ts && tsup",
"generate": "npm run clean && tygo generate && mv ../api/models/index.ts generated.ts && tsup",
"test": "vitest"
},
"keywords": [
......
packages:
- path: "github.com/ethereum-optimism/optimism/indexer/api/routes"
- path: "github.com/ethereum-optimism/optimism/indexer/api/models"
......@@ -3,6 +3,7 @@ package api
import (
"context"
"fmt"
"net"
"net/http"
"runtime/debug"
"sync"
......@@ -10,7 +11,6 @@ import (
"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-service/httputil"
"github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum/go-ethereum/log"
"github.com/go-chi/chi/v5"
......@@ -20,45 +20,63 @@ import (
const ethereumAddressRegex = `^0x[a-fA-F0-9]{40}$`
type Api struct {
// Api ... Indexer API struct
// TODO : Structured error responses
type API struct {
log log.Logger
Router *chi.Mux
router *chi.Mux
serverConfig config.ServerConfig
metricsConfig config.ServerConfig
metricsRegistry *prometheus.Registry
}
const (
MetricsNamespace = "op_indexer"
MetricsNamespace = "op_indexer_api"
addressParam = "{address:%s}"
// Endpoint paths
// NOTE - This can be further broken out over time as new version iterations
// are implemented
HealthPath = "/healthz"
DepositsPath = "/api/v0/deposits/"
WithdrawalsPath = "/api/v0/withdrawals/"
)
// chiMetricsMiddleware ... Injects a metrics recorder into request processing middleware
func chiMetricsMiddleware(rec metrics.HTTPRecorder) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return metrics.NewHTTPRecordingMiddleware(rec, next)
}
}
func NewApi(logger log.Logger, bv database.BridgeTransfersView, serverConfig config.ServerConfig, metricsConfig config.ServerConfig) *Api {
// NewApi ... Construct a new api instance
func NewApi(logger log.Logger, bv database.BridgeTransfersView, serverConfig config.ServerConfig, metricsConfig config.ServerConfig) *API {
// (1) Initialize dependencies
apiRouter := chi.NewRouter()
h := routes.NewRoutes(logger, bv, apiRouter)
mr := metrics.NewRegistry()
promRecorder := metrics.NewPromHTTPRecorder(mr, MetricsNamespace)
// (2) Inject routing middleware
apiRouter.Use(chiMetricsMiddleware(promRecorder))
apiRouter.Use(middleware.Recoverer)
apiRouter.Use(middleware.Heartbeat("/healthz"))
apiRouter.Use(middleware.Heartbeat(HealthPath))
apiRouter.Get(fmt.Sprintf("/api/v0/deposits/{address:%s}", ethereumAddressRegex), h.L1DepositsHandler)
apiRouter.Get(fmt.Sprintf("/api/v0/withdrawals/{address:%s}", ethereumAddressRegex), h.L2WithdrawalsHandler)
// (3) Set GET routes
apiRouter.Get(fmt.Sprintf(DepositsPath+addressParam, ethereumAddressRegex), h.L1DepositsHandler)
apiRouter.Get(fmt.Sprintf(WithdrawalsPath+addressParam, ethereumAddressRegex), h.L2WithdrawalsHandler)
return &Api{log: logger, Router: apiRouter, metricsRegistry: mr, serverConfig: serverConfig, metricsConfig: metricsConfig}
return &API{log: logger, router: apiRouter, metricsRegistry: mr, serverConfig: serverConfig, metricsConfig: metricsConfig}
}
func (a *Api) Start(ctx context.Context) error {
// Run ... Runs the API server routines
func (a *API) Run(ctx context.Context) error {
var wg sync.WaitGroup
errCh := make(chan error, 2)
// (1) Construct an inner function that will start a goroutine
// and handle any panics that occur on a shared error channel
processCtx, processCancel := context.WithCancel(ctx)
runProcess := func(start func(ctx context.Context) error) {
wg.Add(1)
......@@ -78,9 +96,11 @@ func (a *Api) Start(ctx context.Context) error {
}()
}
// (2) Start the API and metrics servers
runProcess(a.startServer)
runProcess(a.startMetricsServer)
// (3) Wait for all processes to complete
wg.Wait()
err := <-errCh
......@@ -93,19 +113,42 @@ func (a *Api) Start(ctx context.Context) error {
return err
}
func (a *Api) startServer(ctx context.Context) error {
// Port ... Returns the the port that server is listening on
func (a *API) Port() int {
return a.serverConfig.Port
}
// startServer ... Starts the API server
func (a *API) startServer(ctx context.Context) error {
a.log.Info("api server listening...", "port", a.serverConfig.Port)
server := http.Server{Addr: fmt.Sprintf(":%d", a.serverConfig.Port), Handler: a.Router}
err := httputil.ListenAndServeContext(ctx, &server)
server := http.Server{Addr: fmt.Sprintf(":%d", a.serverConfig.Port), Handler: a.router}
addr := fmt.Sprintf(":%d", a.serverConfig.Port)
listener, err := net.Listen("tcp", addr)
if err != nil {
a.log.Error("Listen:", err)
return err
}
tcpAddr, ok := listener.Addr().(*net.TCPAddr)
if !ok {
return fmt.Errorf("failed to get TCP address from network listener")
}
// Update the port in the config in case the OS chose a different port
// than the one we requested (e.g. using port 0 to fetch a random open port)
a.serverConfig.Port = tcpAddr.Port
err = http.Serve(listener, server.Handler)
if err != nil {
a.log.Error("api server stopped", "err", err)
a.log.Error("api server stopped with error", "err", err)
} else {
a.log.Info("api server stopped")
}
return err
}
func (a *Api) startMetricsServer(ctx context.Context) error {
// startMetricsServer ... Starts the metrics server
func (a *API) startMetricsServer(ctx context.Context) error {
a.log.Info("starting metrics server...", "port", a.metricsConfig.Port)
err := metrics.ListenAndServe(ctx, a.metricsRegistry, a.metricsConfig.Host, a.metricsConfig.Port)
if err != nil {
......
......@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"github.com/ethereum-optimism/optimism/indexer/api/routes"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-service/testlog"
......@@ -100,7 +100,7 @@ func TestHealthz(t *testing.T) {
assert.Nil(t, err)
responseRecorder := httptest.NewRecorder()
api.Router.ServeHTTP(responseRecorder, request)
api.router.ServeHTTP(responseRecorder, request)
assert.Equal(t, http.StatusOK, responseRecorder.Code)
}
......@@ -112,11 +112,11 @@ func TestL1BridgeDepositsHandler(t *testing.T) {
assert.Nil(t, err)
responseRecorder := httptest.NewRecorder()
api.Router.ServeHTTP(responseRecorder, request)
api.router.ServeHTTP(responseRecorder, request)
assert.Equal(t, http.StatusOK, responseRecorder.Code)
var resp routes.DepositResponse
var resp models.DepositResponse
err = json.Unmarshal(responseRecorder.Body.Bytes(), &resp)
assert.Nil(t, err)
......@@ -135,9 +135,9 @@ func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) {
assert.Nil(t, err)
responseRecorder := httptest.NewRecorder()
api.Router.ServeHTTP(responseRecorder, request)
api.router.ServeHTTP(responseRecorder, request)
var resp routes.WithdrawalResponse
var resp models.WithdrawalResponse
err = json.Unmarshal(responseRecorder.Body.Bytes(), &resp)
assert.Nil(t, err)
......
package models
// DepositItem ... Deposit item model for API responses
type DepositItem struct {
Guid string `json:"guid"`
From string `json:"from"`
To string `json:"to"`
Timestamp uint64 `json:"timestamp"`
L1BlockHash string `json:"l1BlockHash"`
L1TxHash string `json:"l1TxHash"`
L2TxHash string `json:"l2TxHash"`
Amount string `json:"amount"`
L1TokenAddress string `json:"l1TokenAddress"`
L2TokenAddress string `json:"l2TokenAddress"`
}
// DepositResponse ... Data model for API JSON response
type DepositResponse struct {
Cursor string `json:"cursor"`
HasNextPage bool `json:"hasNextPage"`
Items []DepositItem `json:"items"`
}
// WithdrawalItem ... Data model for API JSON response
type WithdrawalItem struct {
Guid string `json:"guid"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
MessageHash string `json:"messageHash"`
Timestamp uint64 `json:"timestamp"`
L2BlockHash string `json:"l2BlockHash"`
Amount string `json:"amount"`
ProofTransactionHash string `json:"proofTransactionHash"`
ClaimTransactionHash string `json:"claimTransactionHash"`
L1TokenAddress string `json:"l1TokenAddress"`
L2TokenAddress string `json:"l2TokenAddress"`
}
// WithdrawalResponse ... Data model for API JSON response
type WithdrawalResponse struct {
Cursor string `json:"cursor"`
HasNextPage bool `json:"hasNextPage"`
Items []WithdrawalItem `json:"items"`
}
......@@ -3,24 +3,30 @@ package routes
import (
"encoding/json"
"net/http"
)
const (
InternalServerError = "Internal server error"
"github.com/ethereum/go-ethereum/log"
// defaultPageLimit ... Default page limit for pagination
defaultPageLimit = 100
)
func jsonResponse(w http.ResponseWriter, logger log.Logger, data interface{}, statusCode int) {
// jsonResponse ... Marshals and writes a JSON response provided arbitrary data
func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) error {
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
http.Error(w, InternalServerError, http.StatusInternalServerError)
return err
}
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
http.Error(w, InternalServerError, http.StatusInternalServerError)
return err
}
return nil
}
......@@ -2,36 +2,18 @@ package routes
import (
"net/http"
"strconv"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"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"`
Timestamp uint64 `json:"timestamp"`
L1BlockHash string `json:"l1BlockHash"`
L1TxHash string `json:"l1TxHash"`
L2TxHash string `json:"l2TxHash"`
Amount string `json:"amount"`
L1TokenAddress string `json:"l1TokenAddress"`
L2TokenAddress string `json:"l2TokenAddress"`
}
type DepositResponse struct {
Cursor string `json:"cursor"`
HasNextPage bool `json:"hasNextPage"`
Items []DepositItem `json:"items"`
}
func newDepositResponse(deposits *database.L1BridgeDepositsResponse) DepositResponse {
items := make([]DepositItem, len(deposits.Deposits))
// newDepositResponse ... Converts a database.L1BridgeDepositsResponse to an api.DepositResponse
func newDepositResponse(deposits *database.L1BridgeDepositsResponse) models.DepositResponse {
items := make([]models.DepositItem, len(deposits.Deposits))
for i, deposit := range deposits.Deposits {
item := DepositItem{
item := models.DepositItem{
Guid: deposit.L1BridgeDeposit.TransactionSourceHash.String(),
L1BlockHash: deposit.L1BlockHash.String(),
Timestamp: deposit.L1BridgeDeposit.Tx.Timestamp,
......@@ -46,39 +28,51 @@ func newDepositResponse(deposits *database.L1BridgeDepositsResponse) DepositResp
items[i] = item
}
return DepositResponse{
return models.DepositResponse{
Cursor: deposits.Cursor,
HasNextPage: deposits.HasNextPage,
Items: items,
}
}
// L1DepositsHandler ... Handles /api/v0/deposits/{address} GET requests
func (h Routes) L1DepositsHandler(w http.ResponseWriter, r *http.Request) {
address := common.HexToAddress(chi.URLParam(r, "address"))
addressValue := chi.URLParam(r, "address")
cursor := r.URL.Query().Get("cursor")
limitQuery := r.URL.Query().Get("limit")
defaultLimit := 100
limit := defaultLimit
if limitQuery != "" {
parsedLimit, err := strconv.Atoi(limitQuery)
if err != nil {
http.Error(w, "Limit could not be parsed into a number", http.StatusBadRequest)
h.Logger.Error("Invalid limit")
h.Logger.Error(err.Error())
}
limit = parsedLimit
address, err := h.v.ParseValidateAddress(addressValue)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
h.logger.Error("Invalid address param", "param", addressValue)
h.logger.Error(err.Error())
return
}
deposits, err := h.BridgeTransfersView.L1BridgeDepositsByAddress(address, cursor, limit)
err = h.v.ValidateCursor(cursor)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
h.logger.Error("Invalid cursor param", "param", cursor, "err", err.Error())
}
limit, err := h.v.ParseValidateLimit(limitQuery)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
h.logger.Error("Invalid limit param", "param", limitQuery, "err", err.Error())
return
}
deposits, err := h.view.L1BridgeDepositsByAddress(address, cursor, limit)
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())
h.logger.Error("Unable to read deposits from DB", "err", err.Error())
return
}
response := newDepositResponse(deposits)
jsonResponse(w, h.Logger, response, http.StatusOK)
err = jsonResponse(w, response, http.StatusOK)
if err != nil {
h.logger.Error("Error writing response", "err", err)
}
}
......@@ -9,14 +9,14 @@ import (
func (h Routes) DocsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
docs := docgen.MarkdownRoutesDoc(h.Router, docgen.MarkdownOpts{
docs := docgen.MarkdownRoutesDoc(h.router, docgen.MarkdownOpts{
ProjectPath: "github.com/ethereum-optimism/optimism/indexer",
// Intro text included at the top of the generated markdown file.
Intro: "Generated documentation for Optimism indexer",
})
_, err := w.Write([]byte(docs))
if err != nil {
h.Logger.Error("error writing docs", "err", err)
h.logger.Error("error writing docs", "err", err)
http.Error(w, "Internal server error fetching docs", http.StatusInternalServerError)
}
}
......@@ -6,16 +6,19 @@ import (
"github.com/go-chi/chi/v5"
)
// Routes ... Route handler struct
type Routes struct {
Logger log.Logger
BridgeTransfersView database.BridgeTransfersView
Router *chi.Mux
logger log.Logger
view database.BridgeTransfersView
router *chi.Mux
v *Validator
}
// NewRoutes ... Construct a new route handler instance
func NewRoutes(logger log.Logger, bv database.BridgeTransfersView, r *chi.Mux) Routes {
return Routes{
Logger: logger,
BridgeTransfersView: bv,
Router: r,
logger: logger,
view: bv,
router: r,
}
}
package routes
import (
"strconv"
"errors"
"github.com/ethereum/go-ethereum/common"
)
// Validator ... Validates API user request parameters
type Validator struct{}
// ParseValidateLimit ... Validates and parses the limit query parameters
func (v *Validator) ParseValidateLimit(limit string) (int, error) {
if limit == "" {
return defaultPageLimit, nil
}
val, err := strconv.Atoi(limit)
if err != nil {
return 0, errors.New("limit must be an integer value")
}
if val <= 0 {
return 0, errors.New("limit must be greater than 0")
}
// TODO - Add a check against a max limit value
return val, nil
}
// ParseValidateAddress ... Validates and parses the address query parameter
func (v *Validator) ParseValidateAddress(addr string) (common.Address, error) {
if !common.IsHexAddress(addr) {
return common.Address{}, errors.New("address must be represented as a valid hexadecimal string")
}
parsedAddr := common.HexToAddress(addr)
if parsedAddr == common.HexToAddress("0x0") {
return common.Address{}, errors.New("address cannot be the zero address")
}
return parsedAddr, nil
}
// ValidateCursor ... Validates and parses the cursor query parameter
func (v *Validator) ValidateCursor(cursor string) error {
if cursor == "" {
return nil
}
if len(cursor) != 66 { // 0x + 64 chars
return errors.New("cursor must be a 32 byte hex string")
}
if cursor[:2] != "0x" {
return errors.New("cursor must begin with 0x")
}
return nil
}
package routes
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParseValidateLimit(t *testing.T) {
v := Validator{}
// (1) Happy case
limit := "100"
_, err := v.ParseValidateLimit(limit)
require.NoError(t, err, "limit should be valid")
// (2) Boundary validation
limit = "0"
_, err = v.ParseValidateLimit(limit)
require.Error(t, err, "limit must be greater than 0")
// (3) Type validation
limit = "abc"
_, err = v.ParseValidateLimit(limit)
require.Error(t, err, "limit must be an integer value")
}
func TestParseValidateAddress(t *testing.T) {
v := Validator{}
// (1) Happy case
addr := "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5"
_, err := v.ParseValidateAddress(addr)
require.NoError(t, err, "address should be pass")
// (2) Invalid hex
addr = "🫡"
_, err = v.ParseValidateAddress(addr)
require.Error(t, err, "address must be represented as a valid hexadecimal string")
// (3) Zero address
addr = "0x0000000000000000000000000000000000000000"
_, err = v.ParseValidateAddress(addr)
require.Error(t, err, "address cannot be black-hole value")
}
func Test_ParseValidateCursor(t *testing.T) {
v := Validator{}
// (1) Happy case
cursor := "0xf3fd2eb696dab4263550b938726f9b3606e334cce6ebe27446bc26cb700b94e0"
err := v.ValidateCursor(cursor)
require.NoError(t, err, "cursor should be pass")
// (2) Invalid length
cursor = "0x000"
err = v.ValidateCursor(cursor)
require.Error(t, err, "cursor must be 32 byte hex string")
// (3) Invalid hex
cursor = "0🫡"
err = v.ValidateCursor(cursor)
require.Error(t, err, "cursor must start with 0x")
}
......@@ -2,38 +2,18 @@ package routes
import (
"net/http"
"strconv"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/go-chi/chi/v5"
)
type WithdrawalItem struct {
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:"proofTransactionHash"`
ClaimTransactionHash string `json:"claimTransactionHash"`
L1TokenAddress string `json:"l1TokenAddress"`
L2TokenAddress string `json:"l2TokenAddress"`
}
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.L2BridgeWithdrawalsResponse) WithdrawalResponse {
items := make([]WithdrawalItem, len(withdrawals.Withdrawals))
// newWithdrawalResponse ... Converts a database.L2BridgeWithdrawalsResponse to an api.WithdrawalResponse
func newWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) models.WithdrawalResponse {
items := make([]models.WithdrawalItem, len(withdrawals.Withdrawals))
for i, withdrawal := range withdrawals.Withdrawals {
item := WithdrawalItem{
item := models.WithdrawalItem{
Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(),
L2BlockHash: withdrawal.L2BlockHash.String(),
From: withdrawal.L2BridgeWithdrawal.Tx.FromAddress.String(),
......@@ -48,36 +28,50 @@ func newWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) Wi
items[i] = item
}
return WithdrawalResponse{
return models.WithdrawalResponse{
Cursor: withdrawals.Cursor,
HasNextPage: withdrawals.HasNextPage,
Items: items,
}
}
// L2WithdrawalsHandler ... Handles /api/v0/withdrawals/{address} GET requests
func (h Routes) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
address := common.HexToAddress(chi.URLParam(r, "address"))
addressValue := chi.URLParam(r, "address")
cursor := r.URL.Query().Get("cursor")
limitQuery := r.URL.Query().Get("limit")
defaultLimit := 100
limit := defaultLimit
if limitQuery != "" {
parsedLimit, err := strconv.Atoi(limitQuery)
if err != nil {
http.Error(w, "Limit could not be parsed into a number", http.StatusBadRequest)
h.Logger.Error("Invalid limit")
h.Logger.Error(err.Error())
}
limit = parsedLimit
address, err := h.v.ParseValidateAddress(addressValue)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
h.logger.Error("Invalid address param", "param", addressValue, "err", err)
return
}
withdrawals, err := h.BridgeTransfersView.L2BridgeWithdrawalsByAddress(address, cursor, limit)
err = h.v.ValidateCursor(cursor)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
h.logger.Error("Invalid cursor param", "param", cursor, "err", err)
return
}
limit, err := h.v.ParseValidateLimit(limitQuery)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
h.logger.Error("Invalid query params", "err", err)
return
}
withdrawals, err := h.view.L2BridgeWithdrawalsByAddress(address, cursor, limit)
if err != nil {
http.Error(w, "Internal server error reading withdrawals", http.StatusInternalServerError)
h.Logger.Error("Unable to read withdrawals from DB")
h.Logger.Error(err.Error())
h.logger.Error("Unable to read withdrawals from DB", "err", err.Error())
return
}
response := newWithdrawalResponse(withdrawals)
jsonResponse(w, h.Logger, response, http.StatusOK)
err = jsonResponse(w, response, http.StatusOK)
if err != nil {
h.logger.Error("Error writing response", "err", err.Error())
}
}
package client
import (
"fmt"
"io"
"net/http"
"time"
"encoding/json"
"github.com/ethereum-optimism/optimism/indexer/api"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum/go-ethereum/common"
)
const (
urlParams = "?cursor=%s&limit=%d"
defaultPagingLimit = 100
// method names
healthz = "get_health"
deposits = "get_deposits"
withdrawals = "get_withdrawals"
)
// Option ... Provides configuration through callback injection
type Option func(*Client) error
// WithMetrics ... Triggers metric optionality
func WithMetrics(m node.Metricer) Option {
return func(c *Client) error {
c.metrics = m
return nil
}
}
// WithTimeout ... Embeds a timeout limit to request
func WithTimeout(t time.Duration) Option {
return func(c *Client) error {
c.c.Timeout = t
return nil
}
}
// Config ... Indexer client config struct
type Config struct {
PaginationLimit int
BaseURL string
}
// Client ... Indexer client struct
type Client struct {
cfg *Config
c *http.Client
metrics node.Metricer
}
// NewClient ... Construct a new indexer client
func NewClient(cfg *Config, opts ...Option) (*Client, error) {
if cfg.PaginationLimit <= 0 {
cfg.PaginationLimit = defaultPagingLimit
}
c := &http.Client{}
client := &Client{cfg: cfg, c: c}
for _, opt := range opts {
err := opt(client)
if err != nil {
return nil, err
}
}
return client, nil
}
// doRecordRequest ... Performs a read request on a provided endpoint w/ telemetry
func (c *Client) doRecordRequest(method string, endpoint string) ([]byte, error) {
var record func(error) = nil
if c.metrics != nil {
record = c.metrics.RecordRPCClientRequest(method)
}
resp, err := c.c.Get(endpoint)
if record != nil {
record(err)
}
if err != nil {
return nil, err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
err = resp.Body.Close()
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("endpoint failed with status code %d", resp.StatusCode)
}
return body, resp.Body.Close()
}
// HealthCheck ... Checks the health of the indexer API
func (c *Client) HealthCheck() error {
_, err := c.doRecordRequest(healthz, c.cfg.BaseURL+api.HealthPath)
if err != nil {
return err
}
return nil
}
// GetDepositsByAddress ... Gets a deposit response object provided an L1 address and cursor
func (c *Client) GetDepositsByAddress(l1Address common.Address, cursor string) (*models.DepositResponse, error) {
var dResponse *models.DepositResponse
url := c.cfg.BaseURL + api.DepositsPath + l1Address.String() + urlParams
endpoint := fmt.Sprintf(url, cursor, c.cfg.PaginationLimit)
resp, err := c.doRecordRequest(deposits, endpoint)
if err != nil {
return nil, err
}
if err := json.Unmarshal(resp, &dResponse); err != nil {
return nil, err
}
return dResponse, nil
}
// GetAllDepositsByAddress ... Gets all deposits provided a L1 address
func (c *Client) GetAllDepositsByAddress(l1Address common.Address) ([]models.DepositItem, error) {
var deposits []models.DepositItem
cursor := ""
for {
dResponse, err := c.GetDepositsByAddress(l1Address, cursor)
if err != nil {
return nil, err
}
deposits = append(deposits, dResponse.Items...)
if !dResponse.HasNextPage {
break
}
cursor = dResponse.Cursor
}
return deposits, nil
}
// GetAllWithdrawalsByAddress ... Gets all withdrawals provided a L2 address
func (c *Client) GetAllWithdrawalsByAddress(l2Address common.Address) ([]models.WithdrawalItem, error) {
var withdrawals []models.WithdrawalItem
cursor := ""
for {
wResponse, err := c.GetWithdrawalsByAddress(l2Address, cursor)
if err != nil {
return nil, err
}
withdrawals = append(withdrawals, wResponse.Items...)
if !wResponse.HasNextPage {
break
}
cursor = wResponse.Cursor
}
return withdrawals, nil
}
// GetWithdrawalsByAddress ... Gets a withdrawal response object provided an L2 address and cursor
func (c *Client) GetWithdrawalsByAddress(l2Address common.Address, cursor string) (*models.WithdrawalResponse, error) {
var wResponse *models.WithdrawalResponse
url := c.cfg.BaseURL + api.WithdrawalsPath + l2Address.String() + urlParams
endpoint := fmt.Sprintf(url, cursor, c.cfg.PaginationLimit)
resp, err := c.doRecordRequest(withdrawals, endpoint)
if err != nil {
return nil, err
}
if err := json.Unmarshal(resp, &wResponse); err != nil {
return nil, err
}
return wResponse, nil
}
......@@ -69,7 +69,7 @@ func runApi(ctx *cli.Context) error {
defer db.Close()
api := api.NewApi(log, db.BridgeTransfers, cfg.HTTPServer, cfg.MetricsServer)
return api.Start(ctx.Context)
return api.Run(ctx.Context)
}
func runMigrations(ctx *cli.Context) error {
......
......@@ -4,6 +4,7 @@ import (
"context"
"os"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/opio"
"github.com/ethereum/go-ethereum/log"
)
......@@ -22,6 +23,7 @@ func main() {
cancel()
}()
oplog.SetupDefaults()
app := newCli(GitCommit, GitDate)
if err := app.RunContext(ctx, os.Args); err != nil {
log.Error("application failed", "err", err)
......
......@@ -89,6 +89,22 @@ var Presets = map[int]Preset{
L1StartingHeight: 8410981,
},
},
84532: {
Name: "Base Sepolia",
ChainConfig: ChainConfig{
Preset: 84532,
L1Contracts: L1Contracts{
AddressManager: common.HexToAddress("0x709c2B8ef4A9feFc629A8a2C1AF424Dc5BD6ad1B"),
SystemConfigProxy: common.HexToAddress("0xf272670eb55e895584501d564AfEB048bEd26194"),
OptimismPortalProxy: common.HexToAddress("0x49f53e41452C74589E85cA1677426Ba426459e85"),
L2OutputOracleProxy: common.HexToAddress("0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254"),
L1CrossDomainMessengerProxy: common.HexToAddress("0xC34855F4De64F1840e5686e64278da901e261f20"),
L1StandardBridgeProxy: common.HexToAddress("0xfd0Bf71F60660E2f608ed56e1659C450eB113120"),
L1ERC721BridgeProxy: common.HexToAddress("0x21eFD066e581FA55Ef105170Cc04d74386a09190"),
},
L1StartingHeight: 4370868,
},
},
7777777: {
Name: "Zora",
ChainConfig: ChainConfig{
......
......@@ -128,12 +128,11 @@ type L1BridgeDepositsResponse struct {
HasNextPage bool
}
// L1BridgeDepositsByAddress retrieves a list of deposits intiated by the specified address,
// L1BridgeDepositsByAddress retrieves a list of deposits initiated by the specified address,
// coupled with the L1/L2 transaction hashes that complete the bridge transaction.
func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address, cursor string, limit int) (*L1BridgeDepositsResponse, error) {
defaultLimit := 100
if limit <= 0 {
limit = defaultLimit
return nil, fmt.Errorf("limit must be greater than 0")
}
cursorClause := ""
......@@ -152,7 +151,7 @@ func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address, c
// Coalesce l1 transaction deposits that are simply ETH sends
ethTransactionDeposits := db.gorm.Model(&L1TransactionDeposit{})
ethTransactionDeposits = ethTransactionDeposits.Where(Transaction{FromAddress: address}).Where("data = '0x' AND amount > 0")
ethTransactionDeposits = ethTransactionDeposits.Where(&Transaction{FromAddress: address}).Where("data = '0x' AND amount > 0")
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,
......@@ -164,6 +163,7 @@ l1_transaction_deposits.timestamp, NULL AS cross_domain_message_hash, ? AS local
}
depositsQuery := db.gorm.Model(&L1BridgeDeposit{})
depositsQuery = depositsQuery.Where(&Transaction{FromAddress: address})
depositsQuery = depositsQuery.Joins("INNER JOIN l1_transaction_deposits ON l1_transaction_deposits.source_hash = transaction_source_hash")
depositsQuery = depositsQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l1_transaction_deposits.initiated_l1_event_guid")
depositsQuery = depositsQuery.Select(`
......@@ -241,14 +241,14 @@ type L2BridgeWithdrawalsResponse struct {
HasNextPage bool
}
// L2BridgeDepositsByAddress retrieves a list of deposits intiated by the specified address, coupled with the L1/L2 transaction hashes
// that complete the bridge transaction. The hashes that correspond to with the Bedrock multistep withdrawal process are also surfaced
// L2BridgeDepositsByAddress retrieves a list of deposits initiated by the specified address, coupled with the L1/L2 transaction hashes
// that complete the bridge transaction. The hashes that correspond with the Bedrock multi-step withdrawal process are also surfaced
func (db *bridgeTransfersDB) L2BridgeWithdrawalsByAddress(address common.Address, cursor string, limit int) (*L2BridgeWithdrawalsResponse, error) {
defaultLimit := 100
if limit <= 0 {
limit = defaultLimit
return nil, fmt.Errorf("limit must be greater than 0")
}
// (1) Generate cursor clause provided a cursor tx hash
cursorClause := ""
if cursor != "" {
withdrawalHash := common.HexToHash(cursor)
......@@ -260,12 +260,17 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawalsByAddress(address common.Address
cursorClause = fmt.Sprintf("l2_transaction_withdrawals.timestamp <= %d", txWithdrawal.Tx.Timestamp)
}
// (2) Generate query for fetching ETH withdrawal data
// This query is a UNION (A | B) of two sub-queries:
// - (A) ETH sends from L2 to L1
// - (B) Bridge withdrawals from L2 to L1
// TODO join with l1_bridged_tokens and l2_bridged_tokens
ethAddressString := predeploys.LegacyERC20ETHAddr.String()
// Coalesce l2 transaction withdrawals that are simply ETH sends
ethTransactionWithdrawals := db.gorm.Model(&L2TransactionWithdrawal{})
ethTransactionWithdrawals = ethTransactionWithdrawals.Where(Transaction{FromAddress: address}).Where(`data = '0x' AND amount > 0`)
ethTransactionWithdrawals = ethTransactionWithdrawals.Where(&Transaction{FromAddress: address}).Where(`data = '0x' AND amount > 0`)
ethTransactionWithdrawals = ethTransactionWithdrawals.Joins("INNER JOIN l2_contract_events ON l2_contract_events.guid = l2_transaction_withdrawals.initiated_l2_event_guid")
ethTransactionWithdrawals = ethTransactionWithdrawals.Joins("LEFT JOIN l1_contract_events AS proven_l1_events ON proven_l1_events.guid = l2_transaction_withdrawals.proven_l1_event_guid")
ethTransactionWithdrawals = ethTransactionWithdrawals.Joins("LEFT JOIN l1_contract_events AS finalized_l1_events ON finalized_l1_events.guid = l2_transaction_withdrawals.finalized_l1_event_guid")
......@@ -279,6 +284,7 @@ l2_transaction_withdrawals.timestamp, NULL AS cross_domain_message_hash, ? AS lo
}
withdrawalsQuery := db.gorm.Model(&L2BridgeWithdrawal{})
withdrawalsQuery = withdrawalsQuery.Where(&Transaction{FromAddress: address})
withdrawalsQuery = withdrawalsQuery.Joins("INNER JOIN l2_transaction_withdrawals ON withdrawal_hash = l2_bridge_withdrawals.transaction_withdrawal_hash")
withdrawalsQuery = withdrawalsQuery.Joins("INNER JOIN l2_contract_events ON l2_contract_events.guid = l2_transaction_withdrawals.initiated_l2_event_guid")
withdrawalsQuery = withdrawalsQuery.Joins("LEFT JOIN l1_contract_events AS proven_l1_events ON proven_l1_events.guid = l2_transaction_withdrawals.proven_l1_event_guid")
......@@ -296,7 +302,10 @@ l2_bridge_withdrawals.timestamp, cross_domain_message_hash, local_token_address,
query = query.Joins("UNION (?)", ethTransactionWithdrawals)
query = query.Select("*").Order("timestamp DESC").Limit(limit + 1)
withdrawals := []L2BridgeWithdrawalWithTransactionHashes{}
// (3) Execute query and process results
result := query.Find(&withdrawals)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
......
......@@ -173,4 +173,5 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, event)
require.Equal(t, event.TransactionHash, finalizedReceipt.TxHash)
}
......@@ -2,6 +2,7 @@ package e2e_tests
import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"
"testing"
......@@ -16,6 +17,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
......@@ -50,7 +52,7 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
}))
cursor := ""
limit := 0
limit := 100
aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, cursor, limit)
......@@ -116,7 +118,7 @@ func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) {
return l1Header != nil && l1Header.Number.Uint64() >= portalDepositReceipt.BlockNumber.Uint64(), nil
}))
aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 0)
aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 1)
require.NoError(t, err)
require.NotNil(t, aliceDeposits)
require.Len(t, aliceDeposits.Deposits, 1)
......@@ -143,7 +145,7 @@ func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) {
}))
// Still nil as the withdrawal did not occur through the standard bridge
aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 0)
aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 1)
require.NoError(t, err)
require.Nil(t, aliceDeposits.Deposits[0].L1BridgeDeposit.CrossDomainMessageHash)
}
......@@ -185,7 +187,7 @@ func TestE2EBridgeTransfersCursoredDeposits(t *testing.T) {
}))
// Get All
aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 0)
aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 3)
require.NotNil(t, aliceDeposits)
require.NoError(t, err)
require.Len(t, aliceDeposits.Deposits, 3)
......@@ -198,14 +200,14 @@ func TestE2EBridgeTransfersCursoredDeposits(t *testing.T) {
require.Len(t, aliceDeposits.Deposits, 2)
require.True(t, aliceDeposits.HasNextPage)
aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, aliceDeposits.Cursor, 2)
aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, aliceDeposits.Cursor, 1)
require.NoError(t, err)
require.NotNil(t, aliceDeposits)
require.Len(t, aliceDeposits.Deposits, 1)
require.False(t, aliceDeposits.HasNextPage)
// Returns the results in the right order
aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 100)
aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 3)
require.NotNil(t, aliceDeposits)
require.NoError(t, err)
for i := 0; i < 3; i++ {
......@@ -252,7 +254,7 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 0)
aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 3)
require.NoError(t, err)
require.Len(t, aliceWithdrawals.Withdrawals, 1)
require.Equal(t, withdrawTx.Hash().String(), aliceWithdrawals.Withdrawals[0].L2TransactionHash.String())
......@@ -290,7 +292,7 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 0)
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 100)
require.NoError(t, err)
require.Equal(t, proveReceipt.TxHash, aliceWithdrawals.Withdrawals[0].ProvenL1TransactionHash)
require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals.Withdrawals[0].FinalizedL1TransactionHash)
......@@ -304,7 +306,6 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
func TestE2EBridgeTransfersL2ToL1MessagePasserETHReceive(t *testing.T) {
testSuite := createE2ETestSuite(t)
optimismPortal, err := bindings.NewOptimismPortal(testSuite.OpCfg.L1Deployments.OptimismPortalProxy, testSuite.L1Client)
require.NoError(t, err)
l2ToL1MessagePasser, err := bindings.NewOptimismPortal(predeploys.L2ToL1MessagePasserAddr, testSuite.L2Client)
......@@ -337,7 +338,7 @@ func TestE2EBridgeTransfersL2ToL1MessagePasserETHReceive(t *testing.T) {
return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 0)
aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 100)
require.NoError(t, err)
require.Len(t, aliceWithdrawals.Withdrawals, 1)
require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash().String(), aliceWithdrawals.Withdrawals[0].L2TransactionHash.String())
......@@ -370,7 +371,7 @@ func TestE2EBridgeTransfersL2ToL1MessagePasserETHReceive(t *testing.T) {
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 0)
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 100)
require.NoError(t, err)
require.Equal(t, proveReceipt.TxHash, aliceWithdrawals.Withdrawals[0].ProvenL1TransactionHash)
require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals.Withdrawals[0].FinalizedL1TransactionHash)
......@@ -414,7 +415,7 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) {
}))
// Get All
aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 0)
aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 100)
require.NotNil(t, aliceWithdrawals)
require.NoError(t, err)
require.Len(t, aliceWithdrawals.Withdrawals, 3)
......@@ -427,7 +428,7 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) {
require.Len(t, aliceWithdrawals.Withdrawals, 2)
require.True(t, aliceWithdrawals.HasNextPage)
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, aliceWithdrawals.Cursor, 2)
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, aliceWithdrawals.Cursor, 1)
require.NotNil(t, aliceWithdrawals)
require.NoError(t, err)
require.Len(t, aliceWithdrawals.Withdrawals, 1)
......@@ -445,3 +446,73 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) {
require.Equal(t, int64(3-i)*params.Ether, withdrawal.L2BridgeWithdrawal.Tx.Amount.Int64())
}
}
func TestClientGetWithdrawals(t *testing.T) {
testSuite := createE2ETestSuite(t)
// (1) Generate contract bindings for the L1 and L2 standard bridges
optimismPortal, err := bindings.NewOptimismPortal(testSuite.OpCfg.L1Deployments.OptimismPortalProxy, testSuite.L1Client)
require.NoError(t, err)
l2ToL1MessagePasser, err := bindings.NewOptimismPortal(predeploys.L2ToL1MessagePasserAddr, testSuite.L2Client)
require.NoError(t, err)
// (2) Create test actors that will deposit and withdraw using the standard bridge
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
bobAddr := testSuite.OpCfg.Secrets.Addresses().Bob
type actor struct {
addr common.Address
priv *ecdsa.PrivateKey
}
actors := []actor{
{
addr: aliceAddr,
priv: testSuite.OpCfg.Secrets.Alice,
},
{
addr: bobAddr,
priv: testSuite.OpCfg.Secrets.Bob,
},
}
// (3) Iterate over each actor and deposit / withdraw
for _, actor := range actors {
l2Opts, err := bind.NewKeyedTransactorWithChainID(actor.priv, testSuite.OpCfg.L2ChainIDBig())
require.NoError(t, err)
l2Opts.Value = big.NewInt(params.Ether)
// (3.a) Deposit user funds into L2 via OptimismPortal contract
l1Opts, err := bind.NewKeyedTransactorWithChainID(actor.priv, testSuite.OpCfg.L1ChainIDBig())
require.NoError(t, err)
l1Opts.Value = l2Opts.Value
depositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
// (3.b) Initiate withdrawal transaction via L2ToL1MessagePasser contract
l2ToL1MessagePasserWithdrawTx, err := l2ToL1MessagePasser.Receive(l2Opts)
require.NoError(t, err)
l2ToL1WithdrawReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, l2ToL1MessagePasserWithdrawTx.Hash())
require.NoError(t, err)
// (3.c) wait for indexer processor to catchup with the L2 block containing the withdrawal tx
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil
}))
// (3.d) Ensure that withdrawal and deposit txs are retrievable via API
deposits, err := testSuite.Client.GetAllDepositsByAddress(actor.addr)
require.NoError(t, err)
require.Len(t, deposits, 1)
require.Equal(t, depositTx.Hash().String(), deposits[0].L1TxHash)
withdrawals, err := testSuite.Client.GetAllWithdrawalsByAddress(actor.addr)
require.NoError(t, err)
require.Len(t, withdrawals, 1)
require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash().String(), withdrawals[0].TransactionHash)
}
}
......@@ -9,6 +9,8 @@ import (
"time"
"github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/api"
"github.com/ethereum-optimism/optimism/indexer/client"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
......@@ -21,9 +23,19 @@ import (
"github.com/stretchr/testify/require"
)
/*
NOTE - Most of the current bridge tests fetch chain data via direct database queries. These could all
be transitioned to use the API client instead to better simulate/validate real-world usage.
Supporting this would potentially require adding new API endpoints for the specific query lookup types.
*/
type E2ETestSuite struct {
t *testing.T
// API
Client *client.Client
API *api.API
// Indexer
DB *database.DB
Indexer *indexer.Indexer
......@@ -37,6 +49,7 @@ type E2ETestSuite struct {
L2Client *ethclient.Client
}
// createE2ETestSuite ... Create a new E2E test suite
func createE2ETestSuite(t *testing.T) E2ETestSuite {
dbUser := os.Getenv("DB_USER")
dbName := setupTestDatabase(t)
......@@ -105,14 +118,54 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
require.NoError(t, err)
indexerCtx, indexerStop := context.WithCancel(context.Background())
t.Cleanup(func() { indexerStop() })
go func() {
err := indexer.Run(indexerCtx)
require.NoError(t, err)
if err != nil { // panicking here ensures that the test will exit
// during service failure. Using t.Fail() wouldn't be caught
// until all awaiting routines finish which would never happen.
panic(err)
}
}()
apiLog := testlog.Logger(t, log.LvlInfo).New("role", "indexer_api")
apiCfg := config.ServerConfig{
Host: "127.0.0.1",
Port: 0,
}
mCfg := config.ServerConfig{
Host: "127.0.0.1",
Port: 0,
}
api := api.NewApi(apiLog, db.BridgeTransfers, apiCfg, mCfg)
apiCtx, apiStop := context.WithCancel(context.Background())
go func() {
err := api.Run(apiCtx)
if err != nil {
panic(err)
}
}()
t.Cleanup(func() {
apiStop()
indexerStop()
})
// Wait for the API to start listening
time.Sleep(1 * time.Second)
client, err := client.NewClient(&client.Config{
PaginationLimit: 100,
BaseURL: fmt.Sprintf("http://%s:%d", apiCfg.Host, api.Port()),
})
require.NoError(t, err)
return E2ETestSuite{
t: t,
Client: client,
DB: db,
Indexer: indexer,
OpCfg: &opCfg,
......
......@@ -48,7 +48,7 @@ func DialEthClient(rpcUrl string, metrics Metricer) (EthClient, error) {
return nil, err
}
client := &client{rpc: newRPC(rpcClient, metrics)}
client := &client{rpc: NewRPC(rpcClient, metrics)}
return client, nil
}
......@@ -207,7 +207,7 @@ type rpcClient struct {
metrics Metricer
}
func newRPC(client *rpc.Client, metrics Metricer) RPC {
func NewRPC(client *rpc.Client, metrics Metricer) RPC {
return &rpcClient{client, metrics}
}
......
......@@ -16,7 +16,6 @@ COPY ./op-batcher /app/op-batcher
COPY ./op-bindings /app/op-bindings
COPY ./op-node /app/op-node
COPY ./op-service /app/op-service
COPY ./op-signer /app/op-signer
COPY ./.git /app/.git
......
......@@ -36,8 +36,8 @@ type BlockOracleBlockInfo struct {
// BlockOracleMetaData contains all meta data concerning the BlockOracle contract.
var BlockOracleMetaData = &bind.MetaData{
ABI: "[{\"inputs\":[],\"name\":\"BlockHashNotPresent\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"Hash\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"Timestamp\",\"name\":\"childTimestamp\",\"type\":\"uint64\"}],\"name\":\"Checkpoint\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"checkpoint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber_\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_blockNumber\",\"type\":\"uint256\"}],\"name\":\"load\",\"outputs\":[{\"components\":[{\"internalType\":\"Hash\",\"name\":\"hash\",\"type\":\"bytes32\"},{\"internalType\":\"Timestamp\",\"name\":\"childTimestamp\",\"type\":\"uint64\"}],\"internalType\":\"structBlockOracle.BlockInfo\",\"name\":\"blockInfo_\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
Bin: "0x608060405234801561001057600080fd5b5061021c806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806399d548aa1461003b578063c2c4c5c114610078575b600080fd5b61004e6100493660046101b8565b61008e565b604080518251815260209283015167ffffffffffffffff1692810192909252015b60405180910390f35b61008061010d565b60405190815260200161006f565b604080518082018252600080825260209182018190528381528082528281208351808501909452805480855260019091015467ffffffffffffffff169284019290925203610108576040517f37cf270500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b919050565b600061011a6001436101d1565b60408051808201825282408082524267ffffffffffffffff81811660208086018281526000898152918290528782209651875551600190960180547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016969093169590951790915593519495509093909291849186917fb67ff58b33060fd371a35ae2d9f1c3cdaec9b8197969f6efe2594a1ff4ba68c691a4505090565b6000602082840312156101ca57600080fd5b5035919050565b60008282101561020a577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50039056fea164736f6c634300080f000a",
ABI: "[{\"inputs\":[],\"name\":\"BlockHashNotPresent\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"Hash\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"Timestamp\",\"name\":\"childTimestamp\",\"type\":\"uint64\"}],\"name\":\"Checkpoint\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"checkpoint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber_\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_blockNumber\",\"type\":\"uint256\"}],\"name\":\"load\",\"outputs\":[{\"components\":[{\"internalType\":\"Hash\",\"name\":\"hash\",\"type\":\"bytes32\"},{\"internalType\":\"Timestamp\",\"name\":\"childTimestamp\",\"type\":\"uint64\"}],\"internalType\":\"structBlockOracle.BlockInfo\",\"name\":\"blockInfo_\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
Bin: "0x608060405234801561001057600080fd5b506102e7806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d501461004657806399d548aa14610098578063c2c4c5c1146100d0575b600080fd5b6100826040518060400160405280600581526020017f302e302e3100000000000000000000000000000000000000000000000000000081525081565b60405161008f9190610210565b60405180910390f35b6100ab6100a6366004610283565b6100e6565b604080518251815260209283015167ffffffffffffffff16928101929092520161008f565b6100d8610165565b60405190815260200161008f565b604080518082018252600080825260209182018190528381528082528281208351808501909452805480855260019091015467ffffffffffffffff169284019290925203610160576040517f37cf270500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b919050565b600061017260014361029c565b60408051808201825282408082524267ffffffffffffffff81811660208086018281526000898152918290528782209651875551600190960180547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016969093169590951790915593519495509093909291849186917fb67ff58b33060fd371a35ae2d9f1c3cdaec9b8197969f6efe2594a1ff4ba68c691a4505090565b600060208083528351808285015260005b8181101561023d57858101830151858201604001528201610221565b8181111561024f576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561029557600080fd5b5035919050565b6000828210156102d5577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50039056fea164736f6c634300080f000a",
}
// BlockOracleABI is the input ABI used to generate the binding from.
......@@ -238,6 +238,37 @@ func (_BlockOracle *BlockOracleCallerSession) Load(_blockNumber *big.Int) (Block
return _BlockOracle.Contract.Load(&_BlockOracle.CallOpts, _blockNumber)
}
// Version is a free data retrieval call binding the contract method 0x54fd4d50.
//
// Solidity: function version() view returns(string)
func (_BlockOracle *BlockOracleCaller) Version(opts *bind.CallOpts) (string, error) {
var out []interface{}
err := _BlockOracle.contract.Call(opts, &out, "version")
if err != nil {
return *new(string), err
}
out0 := *abi.ConvertType(out[0], new(string)).(*string)
return out0, err
}
// Version is a free data retrieval call binding the contract method 0x54fd4d50.
//
// Solidity: function version() view returns(string)
func (_BlockOracle *BlockOracleSession) Version() (string, error) {
return _BlockOracle.Contract.Version(&_BlockOracle.CallOpts)
}
// Version is a free data retrieval call binding the contract method 0x54fd4d50.
//
// Solidity: function version() view returns(string)
func (_BlockOracle *BlockOracleCallerSession) Version() (string, error) {
return _BlockOracle.Contract.Version(&_BlockOracle.CallOpts)
}
// Checkpoint is a paid mutator transaction binding the contract method 0xc2c4c5c1.
//
// Solidity: function checkpoint() returns(uint256 blockNumber_)
......
......@@ -13,7 +13,7 @@ const BlockOracleStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\"
var BlockOracleStorageLayout = new(solc.StorageLayout)
var BlockOracleDeployedBin = "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806399d548aa1461003b578063c2c4c5c114610078575b600080fd5b61004e6100493660046101b8565b61008e565b604080518251815260209283015167ffffffffffffffff1692810192909252015b60405180910390f35b61008061010d565b60405190815260200161006f565b604080518082018252600080825260209182018190528381528082528281208351808501909452805480855260019091015467ffffffffffffffff169284019290925203610108576040517f37cf270500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b919050565b600061011a6001436101d1565b60408051808201825282408082524267ffffffffffffffff81811660208086018281526000898152918290528782209651875551600190960180547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016969093169590951790915593519495509093909291849186917fb67ff58b33060fd371a35ae2d9f1c3cdaec9b8197969f6efe2594a1ff4ba68c691a4505090565b6000602082840312156101ca57600080fd5b5035919050565b60008282101561020a577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50039056fea164736f6c634300080f000a"
var BlockOracleDeployedBin = "0x608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d501461004657806399d548aa14610098578063c2c4c5c1146100d0575b600080fd5b6100826040518060400160405280600581526020017f302e302e3100000000000000000000000000000000000000000000000000000081525081565b60405161008f9190610210565b60405180910390f35b6100ab6100a6366004610283565b6100e6565b604080518251815260209283015167ffffffffffffffff16928101929092520161008f565b6100d8610165565b60405190815260200161008f565b604080518082018252600080825260209182018190528381528082528281208351808501909452805480855260019091015467ffffffffffffffff169284019290925203610160576040517f37cf270500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b919050565b600061017260014361029c565b60408051808201825282408082524267ffffffffffffffff81811660208086018281526000898152918290528782209651875551600190960180547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016969093169590951790915593519495509093909291849186917fb67ff58b33060fd371a35ae2d9f1c3cdaec9b8197969f6efe2594a1ff4ba68c691a4505090565b600060208083528351808285015260005b8181101561023d57858101830151858201604001528201610221565b8181111561024f576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561029557600080fd5b5035919050565b6000828210156102d5577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50039056fea164736f6c634300080f000a"
func init() {
if err := json.Unmarshal([]byte(BlockOracleStorageLayoutJSON), BlockOracleStorageLayout); err != nil {
......
This diff is collapsed.
......@@ -18,7 +18,6 @@ COPY ./op-preimage /app/op-preimage
COPY ./op-bindings /app/op-bindings
COPY ./op-node /app/op-node
COPY ./op-service /app/op-service
COPY ./op-signer /app/op-signer
COPY ./.git /app/.git
# Copy cannon and its dependencies
......
......@@ -84,16 +84,17 @@ var goerliCfg = rollup.Config{
GasLimit: 25_000_000,
},
},
BlockTime: 2,
MaxSequencerDrift: 600,
SeqWindowSize: 3600,
ChannelTimeout: 300,
L1ChainID: big.NewInt(5),
L2ChainID: big.NewInt(420),
BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000420"),
DepositContractAddress: common.HexToAddress("0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"),
L1SystemConfigAddress: common.HexToAddress("0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60"),
RegolithTime: u64Ptr(1679079600),
BlockTime: 2,
MaxSequencerDrift: 600,
SeqWindowSize: 3600,
ChannelTimeout: 300,
L1ChainID: big.NewInt(5),
L2ChainID: big.NewInt(420),
BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000420"),
DepositContractAddress: common.HexToAddress("0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"),
L1SystemConfigAddress: common.HexToAddress("0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60"),
RegolithTime: u64Ptr(1679079600),
ProtocolVersionsAddress: common.HexToAddress("0x0C24F5098774aA366827D667494e9F889f7cFc08"),
}
var sepoliaCfg = rollup.Config{
......
......@@ -25,6 +25,7 @@ const (
zoraGoerli = 999
zoraMainnet = 7777777
labsDevnet = 997
chaosnet = 888
)
// LoadOPStackRollupConfig loads the rollup configuration of the requested chain ID from the superchain-registry.
......@@ -69,6 +70,8 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) {
regolithTime = 1679079600
case labsDevnet:
regolithTime = 1677984480
case chaosnet:
regolithTime = 1692156862
}
cfg := &Config{
......@@ -102,7 +105,7 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) {
if superChain.Config.ProtocolVersionsAddr != nil { // Set optional protocol versions address
cfg.ProtocolVersionsAddress = common.Address(*superChain.Config.ProtocolVersionsAddr)
}
if chainID == labsDevnet {
if chainID == labsDevnet || chainID == chaosnet {
cfg.ChannelTimeout = 120
cfg.MaxSequencerDrift = 1200
}
......
......@@ -16,7 +16,6 @@ COPY ./op-proposer /app/op-proposer
COPY ./op-bindings /app/op-bindings
COPY ./op-node /app/op-node
COPY ./op-service /app/op-service
COPY ./op-signer /app/op-signer
COPY ./.git /app/.git
WORKDIR /app/op-proposer
......
......@@ -17,7 +17,7 @@ import (
"github.com/ethereum/go-ethereum/log"
hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
)
func PrivateKeySignerFn(key *ecdsa.PrivateKey, chainID *big.Int) bind.SignerFn {
......
package client
package signer
import (
"errors"
......
package client
package signer
import (
"context"
......
......@@ -9,7 +9,7 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-signer/client"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
......@@ -19,7 +19,7 @@ import (
const (
// Duplicated L1 RPC flag
L1RPCFlagName = "l1-eth-rpc"
// Key Management Flags (also have op-signer client flags)
// Key Management Flags (also have signer client flags)
MnemonicFlagName = "mnemonic"
HDPathFlagName = "hd-path"
PrivateKeyFlagName = "private-key"
......@@ -145,7 +145,7 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl
Value: defaults.ReceiptQueryInterval,
EnvVars: prefixEnvVars("TXMGR_RECEIPT_QUERY_INTERVAL"),
},
}, client.CLIFlags(envPrefix)...)
}, opsigner.CLIFlags(envPrefix)...)
}
type CLIConfig struct {
......@@ -155,7 +155,7 @@ type CLIConfig struct {
SequencerHDPath string
L2OutputHDPath string
PrivateKey string
SignerCLIConfig client.CLIConfig
SignerCLIConfig opsigner.CLIConfig
NumConfirmations uint64
SafeAbortNonceTooLowCount uint64
ResubmissionTimeout time.Duration
......@@ -175,7 +175,7 @@ func NewCLIConfig(l1RPCURL string, defaults DefaultFlagValues) CLIConfig {
TxSendTimeout: defaults.TxSendTimeout,
TxNotInMempoolTimeout: defaults.TxNotInMempoolTimeout,
ReceiptQueryInterval: defaults.ReceiptQueryInterval,
SignerCLIConfig: client.NewCLIConfig(),
SignerCLIConfig: opsigner.NewCLIConfig(),
}
}
......@@ -215,7 +215,7 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig {
SequencerHDPath: ctx.String(SequencerHDPathFlag.Name),
L2OutputHDPath: ctx.String(L2OutputHDPathFlag.Name),
PrivateKey: ctx.String(PrivateKeyFlagName),
SignerCLIConfig: client.ReadCLIConfig(ctx),
SignerCLIConfig: opsigner.ReadCLIConfig(ctx),
NumConfirmations: ctx.Uint64(NumConfirmationsFlagName),
SafeAbortNonceTooLowCount: ctx.Uint64(SafeAbortNonceTooLowCountFlagName),
ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName),
......
# op-signer
op-signer service client
......@@ -6,7 +6,6 @@ require (
cloud.google.com/go/kms v1.12.1
github.com/BurntSushi/toml v1.3.2
github.com/ethereum-optimism/optimism/op-service v0.10.14
github.com/ethereum-optimism/optimism/op-signer v0.1.1
github.com/ethereum/go-ethereum v1.12.1
github.com/gorilla/mux v1.8.0
github.com/pkg/errors v0.9.1
......
......@@ -98,8 +98,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/ethereum-optimism/optimism/op-service v0.10.14 h1:MC+rVwtPfX1aPAKA3855DQaCnhjjp4uFcSr8PY7HmaE=
github.com/ethereum-optimism/optimism/op-service v0.10.14/go.mod h1:8ay6Bs3YHaX+FbJRUGSbxBnXRtEbKXNHMhtJqATrBmY=
github.com/ethereum-optimism/optimism/op-signer v0.1.1 h1:Ts6aWd5/nTQ8ZRFZhpOrrcWpnVmdp4fRfff1mvybnGs=
github.com/ethereum-optimism/optimism/op-signer v0.1.1/go.mod h1:HK+9do1IJmDr9aEc44ECweYbCLDfGGYO7PBwTqDNrqI=
github.com/ethereum/c-kzg-4844 v0.3.0 h1:3Y3hD6l5i0dEYsBL50C+Om644kve3pNqoAcvE26o9zI=
github.com/ethereum/c-kzg-4844 v0.3.0/go.mod h1:WI2Nd82DMZAAZI1wV2neKGost9EKjvbpQR9OqE5Qqa8=
github.com/ethereum/go-ethereum v1.12.1 h1:1kXDPxhLfyySuQYIfRxVBGYuaHdxNNxevA73vjIwsgk=
......
......@@ -8,7 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics"
optls "github.com/ethereum-optimism/optimism/op-service/tls"
signer "github.com/ethereum-optimism/optimism/op-signer/client"
signer "github.com/ethereum-optimism/optimism/op-service/signer"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
......
......@@ -17,7 +17,8 @@ MIN_VERSIONS = {
'op-challenger': '0.0.4',
'op-proposer': '0.10.14',
'op-ufm': '0.1.0',
'proxyd': '3.16.0'
'proxyd': '3.16.0',
'op-heartbeat': '0.1.0'
}
VALID_BUMPS = ('major', 'minor', 'patch', 'prerelease', 'finalize-prerelease')
......
......@@ -35,7 +35,7 @@
},
"dependencies": {
"@eth-optimism/core-utils": "workspace:*",
"@sentry/node": "^7.64.0",
"@sentry/node": "^7.73.0",
"bcfg": "^0.2.1",
"body-parser": "^1.20.2",
"commander": "^11.0.0",
......@@ -46,7 +46,7 @@
"express-prom-bundle": "^6.6.0",
"lodash": "^4.17.21",
"morgan": "^1.10.0",
"pino": "^8.15.0",
"pino": "^8.15.3",
"pino-multi-stream": "^6.0.0",
"pino-sentry": "^0.14.0",
"prom-client": "^14.2.0"
......
......@@ -10,3 +10,9 @@ export DEPLOYMENT_CONTEXT=getting-started
# Optional Tenderly details for a simulation link during deployment
export TENDERLY_PROJECT=
export TENDERLY_USERNAME=
export ETHERSCAN_API_KEY=
# Optional create2 salt for deterministic deployment of
# contract implementations
export IMPL_SALT=$(openssl rand -hex 32)
......@@ -108,7 +108,7 @@ FaultDisputeGame_Test:test_gameData_succeeds() (gas: 32755)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8265)
FaultDisputeGame_Test:test_initialize_correctData_succeeds() (gas: 57712)
FaultDisputeGame_Test:test_initialize_firstOutput_reverts() (gas: 210563)
FaultDisputeGame_Test:test_initialize_l1HeadTooOld_reverts() (gas: 228368)
FaultDisputeGame_Test:test_initialize_l1HeadTooOld_reverts() (gas: 228401)
FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 594268)
FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 23175)
FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13366)
......
......@@ -53,5 +53,8 @@
"eip1559Denominator": 50,
"eip1559Elasticity": 10,
"systemConfigStartBlock": 0
"systemConfigStartBlock": 0,
"requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
......@@ -18,6 +18,7 @@
"src/L2/L2StandardBridge.sol": "0xfe01bcb1ddc947b9b8a7093d0971854b9fa8d49da5bd933a3dd106167907f882",
"src/L2/L2ToL1MessagePasser.sol": "0xafc710b4d320ef450586d96a61cbd58cac814cb3b0c4fdc280eace3efdcdf321",
"src/L2/SequencerFeeVault.sol": "0xc2f733c1128d06ad60bf1e1d98c8f684a4825b11875ccdf2376ede33f5aad4e6",
"src/dispute/BlockOracle.sol": "0x7e724b1ee0116dfd744f556e6237af449c2f40c6426d6f1462ae2a47589283bb",
"src/dispute/DisputeGameFactory.sol": "0xfdfa141408d7f8de7e230ff4bef088e30d0e4d569ca743d60d292abdd21ff270",
"src/dispute/FaultDisputeGame.sol": "0xfdf4be4d6ed4bcbf6492c43fdbfd04d0c62ebee11b8fe9ee2e7757bde8f7383d",
"src/legacy/DeployerWhitelist.sol": "0x0a6840074734c9d167321d3299be18ef911a415e4c471fa92af7d6cfaa8336d4",
......
......@@ -77,7 +77,7 @@ contract MIPS {
}
/// @notice Computes the hash of the MIPS state.
/// @return out_ The hash of the MIPS state.
/// @return out_ The hashed MIPS state.
function outputState() internal returns (bytes32 out_) {
assembly {
// copies 'size' bytes, right-aligned in word at 'from', to 'to', incl. trailing data
......@@ -141,6 +141,7 @@ contract MIPS {
}
/// @notice Handles a syscall.
/// @return out_ The hashed MIPS state.
function handleSyscall() internal returns (bytes32 out_) {
unchecked {
// Load state from memory
......@@ -372,7 +373,7 @@ contract MIPS {
/// @param _rs The value of the RS register.
/// @param _rt The value of the RT register.
/// @param _storeReg The register to store the result in.
/// @return out_ The hash of the resulting MIPS state.
/// @return out_ The hashed MIPS state.
function handleHiLo(uint32 _func, uint32 _rs, uint32 _rt, uint32 _storeReg) internal returns (bytes32 out_) {
unchecked {
// Load state from memory
......
......@@ -3,10 +3,11 @@ pragma solidity 0.8.15;
import "src/libraries/DisputeTypes.sol";
import "src/libraries/DisputeErrors.sol";
import { ISemver } from "src/universal/ISemver.sol";
/// @title BlockOracle
/// @notice Stores a map of block numbers => block hashes for use in dispute resolution
contract BlockOracle {
contract BlockOracle is ISemver {
/// @notice The BlockInfo struct contains a block's hash and child timestamp.
struct BlockInfo {
Hash hash;
......@@ -19,6 +20,10 @@ contract BlockOracle {
/// @notice Maps block numbers to block hashes and timestamps
mapping(uint256 => BlockInfo) internal blocks;
/// @notice Semantic version.
/// @custom:semver 0.0.1
string public constant version = "0.0.1";
/// @notice Loads a block hash for a given block number, assuming that the block number
/// has been stored in the oracle.
/// @param _blockNumber The block number to load the block hash and timestamp for.
......
......@@ -53,7 +53,7 @@
"@types/glob": "^8.1.0",
"@vitest/coverage-istanbul": "^0.34.6",
"@wagmi/cli": "^1.5.2",
"@wagmi/core": "^1.3.8",
"@wagmi/core": "^1.4.3",
"abitype": "^0.9.3",
"glob": "^10.3.10",
"isomorphic-fetch": "^3.0.0",
......
......@@ -49,7 +49,7 @@
"node-fetch": "^2.6.7"
},
"devDependencies": {
"@types/node": "^20.7.1",
"@types/node": "^20.7.2",
"mocha": "^10.2.0"
}
}
......@@ -44,7 +44,7 @@
"@types/chai": "^4.3.6",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^10.0.2",
"@types/node": "^20.7.1",
"@types/node": "^20.7.2",
"chai-as-promised": "^7.1.1",
"ethereum-waffle": "^4.0.10",
"ethers": "^5.7.2",
......
......@@ -31,6 +31,7 @@ export enum L2ChainID {
OPTIMISM_HARDHAT_DEVNET = 17,
OPTIMISM_BEDROCK_ALPHA_TESTNET = 28528,
BASE_GOERLI = 84531,
BASE_SEPOLIA = 84532,
BASE_MAINNET = 8453,
ZORA_GOERLI = 999,
ZORA_MAINNET = 7777777,
......
......@@ -83,8 +83,9 @@ export const DEPOSIT_CONFIRMATION_BLOCKS: {
[L2ChainID.OPTIMISM_HARDHAT_LOCAL]: 2 as const,
[L2ChainID.OPTIMISM_HARDHAT_DEVNET]: 2 as const,
[L2ChainID.OPTIMISM_BEDROCK_ALPHA_TESTNET]: 12 as const,
[L2ChainID.BASE_GOERLI]: 12 as const,
[L2ChainID.BASE_MAINNET]: 50 as const,
[L2ChainID.BASE_GOERLI]: 25 as const,
[L2ChainID.BASE_SEPOLIA]: 25 as const,
[L2ChainID.BASE_MAINNET]: 10 as const,
[L2ChainID.ZORA_GOERLI]: 12 as const,
[L2ChainID.ZORA_MAINNET]: 50 as const,
}
......@@ -220,6 +221,22 @@ export const CONTRACT_ADDRESSES: {
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
[L2ChainID.BASE_SEPOLIA]: {
l1: {
AddressManager: '0x709c2B8ef4A9feFc629A8a2C1AF424Dc5BD6ad1B' as const,
L1CrossDomainMessenger:
'0xC34855F4De64F1840e5686e64278da901e261f20' as const,
L1StandardBridge: '0xfd0Bf71F60660E2f608ed56e1659C450eB113120' as const,
StateCommitmentChain:
'0x0000000000000000000000000000000000000000' as const,
CanonicalTransactionChain:
'0x0000000000000000000000000000000000000000' as const,
BondManager: '0x0000000000000000000000000000000000000000' as const,
OptimismPortal: '0x49f53e41452C74589E85cA1677426Ba426459e85' as const,
L2OutputOracle: '0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254' as const,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
[L2ChainID.BASE_MAINNET]: {
l1: {
AddressManager: '0x8EfB6B5c4767B09Dc9AA6Af4eAA89F749522BaE2' as const,
......
This diff is collapsed.
......@@ -7,14 +7,16 @@ require (
github.com/alicebob/miniredis v2.5.0+incompatible
github.com/emirpasic/gods v1.18.1
github.com/ethereum/go-ethereum v1.12.1
github.com/go-redsync/redsync/v4 v4.9.4
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.14.0
github.com/redis/go-redis/v9 v9.1.0
github.com/rs/cors v1.8.2
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.2
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
golang.org/x/sync v0.3.0
gopkg.in/yaml.v3 v3.0.1
......@@ -34,7 +36,7 @@ require (
github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect
github.com/cockroachdb/redact v1.1.3 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/consensys/gnark-crypto v0.10.0 // indirect
github.com/consensys/gnark-crypto v0.12.0 // indirect
github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
......@@ -43,7 +45,6 @@ require (
github.com/ethereum/c-kzg-4844 v0.3.0 // indirect
github.com/getsentry/sentry-go v0.18.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-redsync/redsync/v4 v4.9.4 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
......@@ -64,7 +65,6 @@ require (
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.39.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/redis/go-redis/v9 v9.1.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
......@@ -73,10 +73,10 @@ require (
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
rsc.io/tmplfunc v0.0.3 // indirect
......
......@@ -26,7 +26,9 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo=
github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0=
github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo=
github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
......@@ -65,8 +67,8 @@ github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
github.com/consensys/gnark-crypto v0.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA=
github.com/consensys/gnark-crypto v0.10.0/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU=
github.com/consensys/gnark-crypto v0.12.0 h1:1OnSpOykNkUIBIBJKdhwy2p0JlW5o+Az02ICzZmvvdg=
github.com/consensys/gnark-crypto v0.12.0/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
......@@ -122,8 +124,11 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=
github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
github.com/go-redsync/redsync/v4 v4.9.4 h1:vRmYusI+qF95XSpApHAdeu+RjyDvxBXbMthbc/x148c=
github.com/go-redsync/redsync/v4 v4.9.4/go.mod h1:RqBDXUw0q+u9FJTeD2gMzGtHeSVV93DiqGl10B9Hn/4=
......@@ -163,8 +168,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E=
github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
......@@ -301,7 +304,6 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
......@@ -343,6 +345,7 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rueian/rueidis v0.0.93 h1:cG905akj2+QyHx0x9y4mN0K8vLi6M94QiyoLulXS3l0=
github.com/rueian/rueidis v0.0.93/go.mod h1:lo6LBci0D986usi5Wxjb4RVNaWENKYbHZSnufGJ9bTE=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
......@@ -369,8 +372,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
......@@ -425,8 +430,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
......@@ -549,8 +554,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
......
......@@ -199,7 +199,7 @@ This means the start and end of a read/write operation must fall within the same
If an operation were to violate this, the input `count` of the read/write syscall must be
truncated such that the effective address of the last byte read/writtten matches the input effective address.
The VM must read/write the maximum amount of bytes possible without crossing the input adress alignment boundary.
The VM must read/write the maximum amount of bytes possible without crossing the input address alignment boundary.
For example, the effect of a write request for a 3-byte aligned buffer must be exactly 3 bytes.
If the buffer is misaligned, then the VM may write less than 3 bytes depending on the size of the misalignment.
......
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