Commit a5436689 authored by Ethen Pociask's avatar Ethen Pociask

[indexer.client] E2E tests and better documentation

parent a4f00f61
......@@ -20,9 +20,11 @@ import (
const ethereumAddressRegex = `^0x[a-fA-F0-9]{40}$`
// 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
......@@ -31,37 +33,48 @@ type Api struct {
const (
MetricsNamespace = "op_indexer"
addressParam = "{address:%s}"
// Endpoint paths
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)
}
}
// 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))
// (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}
}
// Start ... Starts the API server routines
func (a *Api) Start(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)
......@@ -81,9 +94,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
......@@ -96,9 +111,10 @@ func (a *Api) Start(ctx context.Context) error {
return err
}
// 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}
server := http.Server{Addr: fmt.Sprintf(":%d", a.serverConfig.Port), Handler: a.router}
err := httputil.ListenAndServeContext(ctx, &server)
if err != nil {
a.log.Error("api server stopped", "err", err)
......@@ -108,6 +124,7 @@ func (a *Api) startServer(ctx context.Context) error {
return err
}
// 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)
......
......@@ -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,7 +112,7 @@ 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)
......@@ -135,7 +135,7 @@ 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
err = json.Unmarshal(responseRecorder.Body.Bytes(), &resp)
......
......@@ -8,22 +8,18 @@ import (
)
const (
InternalServerError = "Internal server error"
// defaultPageLimit ... Default page limit for pagination
defaultPageLimit = 100
)
// // errorToJson ... Converts an error to a JSON map
// func errorToJson(err error) map[string]interface{} {
// return map[string]interface{}{
// "error": err.Error(),
// }
// }
// jsonResponse ... Marshals and writes a JSON response provided arbitrary data
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)
http.Error(w, InternalServerError, http.StatusInternalServerError)
logger.Error("Failed to marshal JSON: %v", err)
return
}
......@@ -31,7 +27,7 @@ func jsonResponse(w http.ResponseWriter, logger log.Logger, data interface{}, st
w.WriteHeader(statusCode)
_, err = w.Write(jsonData)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
http.Error(w, InternalServerError, http.StatusInternalServerError)
logger.Error("Failed to write JSON data", err)
return
}
......
......@@ -8,6 +8,7 @@ import (
"github.com/go-chi/chi/v5"
)
// DepositItem ... Deposit item model for API responses
type DepositItem struct {
Guid string `json:"guid"`
From string `json:"from"`
......@@ -27,6 +28,7 @@ type DepositResponse struct {
Items []DepositItem `json:"items"`
}
// newDepositResponse ... Converts a database.L1BridgeDepositsResponse to an api.DepositResponse
func newDepositResponse(deposits *database.L1BridgeDepositsResponse) DepositResponse {
items := make([]DepositItem, len(deposits.Deposits))
for i, deposit := range deposits.Deposits {
......@@ -52,6 +54,7 @@ func newDepositResponse(deposits *database.L1BridgeDepositsResponse) DepositResp
}
}
// 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"))
cursor := r.URL.Query().Get("cursor")
......@@ -60,7 +63,7 @@ func (h Routes) L1DepositsHandler(w http.ResponseWriter, r *http.Request) {
limit, err := h.v.ValidateLimit(limitQuery)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
h.Logger.Error("Invalid limit param")
h.Logger.Error("Invalid limit param", "param", limitQuery)
h.Logger.Error(err.Error())
return
}
......
......@@ -6,6 +6,7 @@ import (
"github.com/go-chi/chi/v5"
)
// Routes ... Route handler struct
type Routes struct {
Logger log.Logger
BridgeTransfersView database.BridgeTransfersView
......@@ -13,6 +14,7 @@ type Routes struct {
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,
......
package routes
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_ValidateLimit(t *testing.T) {
validator := Validator{}
// (1)
limit := "100"
_, err := validator.ValidateLimit(limit)
require.NoError(t, err, "limit should be valid")
// (2)
limit = "0"
_, err = validator.ValidateLimit(limit)
require.Error(t, err, "limit must be greater than 0")
// (3)
limit = "abc"
_, err = validator.ValidateLimit(limit)
require.Error(t, err, "limit must be an integer value")
}
......@@ -54,6 +54,7 @@ func newWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) Wi
}
}
// 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"))
cursor := r.URL.Query().Get("cursor")
......
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/indexer/api"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum/go-ethereum/common"
)
const (
......@@ -24,30 +25,97 @@ type Config struct {
URL string
}
// IndexerClient ... Indexer client struct
type IndexerClient struct {
// Client ... Indexer client struct
// TODO: Add metrics
// TODO: Add injectable context support
type Client struct {
cfg *Config
c *http.Client
m node.Metricer
}
// NewClient ... Construct a new indexer client
func NewClient(cfg *Config, m node.Metricer) (*IndexerClient, error) {
if cfg.PaginationLimit == 0 {
func NewClient(cfg *Config, m node.Metricer) (*Client, error) {
if cfg.PaginationLimit <= 0 {
cfg.PaginationLimit = defaultPagingLimit
}
c := &http.Client{}
return &IndexerClient{cfg: cfg, c: c, m: m}, nil
return &Client{cfg: cfg, c: c, m: m}, nil
}
// GetAllWithdrawalsByAddress ... Gets all withdrawals by address
func (ic *IndexerClient) GetAllWithdrawalsByAddress(l2Address string) ([]database.L2BridgeWithdrawalWithTransactionHashes, error) {
// HealthCheck ... Checks the health of the indexer
func (c *Client) HealthCheck() error {
resp, err := c.c.Get(c.cfg.URL + api.HealthPath)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("health check failed with status code %d", resp.StatusCode)
}
return nil
}
// GetDepositsByAddress ... Gets a deposit response object provided an L1 address and cursor
func (c *Client) GetDepositsByAddress(l1Address common.Address, cursor string) (*database.L1BridgeDepositsResponse, error) {
var dResponse *database.L1BridgeDepositsResponse
url := c.cfg.URL + api.DepositsPath + l1Address.String() + urlParams
endpoint := fmt.Sprintf(url, cursor, c.cfg.PaginationLimit)
resp, err := c.c.Get(endpoint)
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)
}
defer func() {
_ = resp.Body.Close()
}()
if err := json.Unmarshal(body, &dResponse); err != nil {
return nil, err
}
return dResponse, nil
}
// GetAllDepositsByAddress ... Gets all deposits provided a L1 address
func (c *Client) GetAllDepositsByAddress(l1Address common.Address) ([]database.L1BridgeDepositWithTransactionHashes, error) {
var deposits []database.L1BridgeDepositWithTransactionHashes
cursor := ""
for {
dResponse, err := c.GetDepositsByAddress(l1Address, cursor)
if err != nil {
return nil, err
}
deposits = append(deposits, dResponse.Deposits...)
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) ([]database.L2BridgeWithdrawalWithTransactionHashes, error) {
var withdrawals []database.L2BridgeWithdrawalWithTransactionHashes
cursor := ""
for {
wResponse, err := ic.GetWithdrawalsByAddress(l2Address, cursor)
wResponse, err := c.GetWithdrawalsByAddress(l2Address, cursor)
if err != nil {
return nil, err
}
......@@ -64,12 +132,13 @@ func (ic *IndexerClient) GetAllWithdrawalsByAddress(l2Address string) ([]databas
return withdrawals, nil
}
// GetWithdrawalsByAddress ... Gets a withdrawal response object provided an L2 address
func (ic *IndexerClient) GetWithdrawalsByAddress(l2Address string, cursor string) (*database.L2BridgeWithdrawalsResponse, error) {
// GetWithdrawalsByAddress ... Gets a withdrawal response object provided an L2 address and cursor
func (c *Client) GetWithdrawalsByAddress(l2Address common.Address, cursor string) (*database.L2BridgeWithdrawalsResponse, error) {
var wResponse *database.L2BridgeWithdrawalsResponse
url := c.cfg.URL + api.WithdrawalsPath + l2Address.String() + urlParams
endpoint := fmt.Sprintf(ic.cfg.URL+api.WithdrawalsPath+l2Address+urlParams, cursor, ic.cfg.PaginationLimit)
resp, err := ic.c.Get(endpoint)
endpoint := fmt.Sprintf(url, cursor, c.cfg.PaginationLimit)
resp, err := c.c.Get(endpoint)
if err != nil {
return nil, err
}
......
......@@ -131,9 +131,8 @@ type L1BridgeDepositsResponse struct {
// 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 := ""
......@@ -245,7 +244,11 @@ type L2BridgeWithdrawalsResponse struct {
// 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) {
if limit <= 0 {
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)
......@@ -257,6 +260,11 @@ 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()
......@@ -275,13 +283,8 @@ l2_transaction_withdrawals.timestamp, NULL AS cross_domain_message_hash, ? AS lo
ethTransactionWithdrawals = ethTransactionWithdrawals.Where(cursorClause)
}
ethTransactionWithdrawals.DryRun = true
ethTransactionWithdrawals.Find(&[]L2BridgeWithdrawalWithTransactionHashes{})
x := ethTransactionWithdrawals.Statement.SQL.String()
ethTransactionWithdrawals.DryRun = false
println(x)
withdrawalsQuery := db.gorm.Model(&L2BridgeWithdrawal{}).Where(&Transaction{FromAddress: address})
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")
......@@ -295,17 +298,12 @@ l2_bridge_withdrawals.timestamp, cross_domain_message_hash, local_token_address,
withdrawalsQuery = withdrawalsQuery.Where(cursorClause)
}
withdrawalsQuery.DryRun = true
withdrawalsQuery.Find(&[]L2BridgeWithdrawalWithTransactionHashes{})
x = withdrawalsQuery.Statement.SQL.String()
withdrawalsQuery.DryRun = false
println(x)
query := db.gorm.Table("(?) AS withdrawals", withdrawalsQuery)
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 {
......
......@@ -174,10 +174,4 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
require.NotNil(t, event)
require.Equal(t, event.TransactionHash, finalizedReceipt.TxHash)
// (3) Validate that withdrawal is extractable via API
withdrawalsResponse, err := testSuite.IClient.GetWithdrawalsByAddress(aliceAddr.String(), "")
require.NoError(t, err)
require.Equal(t, 1, len(withdrawalsResponse.Withdrawals))
}
......@@ -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"
......@@ -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)
......@@ -337,7 +339,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 +372,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,20 +416,20 @@ 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)
require.False(t, aliceWithdrawals.HasNextPage)
// Respects Limits & Supplied Cursors
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 2)
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 100)
require.NotNil(t, aliceWithdrawals)
require.NoError(t, err)
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, 100)
require.NotNil(t, aliceWithdrawals)
require.NoError(t, err)
require.Len(t, aliceWithdrawals.Withdrawals, 1)
......@@ -445,3 +447,76 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) {
require.Equal(t, int64(3-i)*params.Ether, withdrawal.L2BridgeWithdrawal.Tx.Amount.Int64())
}
}
func Test_ClientGetWithdrawals(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)
require.NoError(t, err)
// Use alice and bob to generate two deposits / withdrawals
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
bobAddr := testSuite.OpCfg.Secrets.Addresses().Bob
// Actor represents a user that will deposit and withdraw
type actor struct {
addr common.Address
priv *ecdsa.PrivateKey
hash common.Hash
}
actors := []actor{
{
addr: aliceAddr,
priv: testSuite.OpCfg.Secrets.Alice,
},
{
addr: bobAddr,
priv: testSuite.OpCfg.Secrets.Bob,
},
}
// 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)
// (1) 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)
// (2) 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)
// wait for processor catchup
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
}))
actor.hash = l2ToL1MessagePasserWithdrawTx.Hash()
}
// (2) Test that Alice's tx can be retrieved via API client
aliceWithdrawals, err := testSuite.Client.GetAllWithdrawalsByAddress(aliceAddr)
require.NoError(t, err)
require.Len(t, aliceWithdrawals, 1)
require.Equal(t, actors[0], aliceWithdrawals[0].L2TransactionHash[0])
// (3) Test that Bob's tx can be retrieved via API client
bobWithdrawals, err := testSuite.Client.GetAllWithdrawalsByAddress(bobAddr)
require.NoError(t, err)
require.Len(t, bobWithdrawals, 1)
require.Equal(t, actors[1], bobWithdrawals[0].L2TransactionHash[0])
}
......@@ -35,7 +35,7 @@ type E2ETestSuite struct {
// API
API *api.Api
IClient *client.IndexerClient
Client *client.Client
// Rollup
OpCfg *op_e2e.SystemConfig
......@@ -91,7 +91,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
L1ERC721BridgeProxy: opCfg.L1Deployments.L1ERC721BridgeProxy,
},
},
HTTPServer: config.ServerConfig{Host: "127.0.0.1", Port: 8080},
HTTPServer: config.ServerConfig{Host: "http://localhost", Port: 8777},
MetricsServer: config.ServerConfig{Host: "127.0.0.1", Port: 0},
}
......@@ -123,17 +123,17 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
cfg := &client.Config{
PaginationLimit: 100,
URL: fmt.Sprintf("http://%s:%d", indexerCfg.HTTPServer.Host, indexerCfg.HTTPServer.Port),
URL: fmt.Sprintf("%s:%d", indexerCfg.HTTPServer.Host, indexerCfg.HTTPServer.Port),
}
client, err := client.NewClient(cfg, nil)
ic, err := client.NewClient(cfg, nil)
require.NoError(t, err)
return E2ETestSuite{
t: t,
DB: db,
API: api,
IClient: ic,
Client: client,
Indexer: indexer,
OpCfg: &opCfg,
OpSys: opSys,
......
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