Commit a0ed931b authored by Ethen Pociask's avatar Ethen Pociask

[indexer.withdrawal_type_supplies] withdrawal type filtering for supply calculations

parent 079a99b0
......@@ -17,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/api/routes"
"github.com/ethereum-optimism/optimism/indexer/api/service"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-service/httputil"
......@@ -137,8 +138,11 @@ func (a *APIService) initDB(ctx context.Context, connector DBConnector) error {
}
func (a *APIService) initRouter(apiConfig config.ServerConfig) {
v := new(service.Validator)
svc := service.New(v, a.bv, a.log)
apiRouter := chi.NewRouter()
h := routes.NewRoutes(a.log, a.bv, apiRouter)
h := routes.NewRoutes(a.log, apiRouter, svc)
promRecorder := metrics.NewPromHTTPRecorder(a.metricsRegistry, MetricsNamespace)
......
......@@ -99,7 +99,7 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.
func (mbv *MockBridgeTransfersView) L1BridgeDepositSum() (float64, error) {
return 69, nil
}
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalSum() (float64, error) {
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalSum(models.WithdrawFilter) (float64, error) {
return 420, nil
}
......
package models
import (
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
import "github.com/ethereum/go-ethereum/common"
// Params ... Query params
type Params struct {
Address common.Address
Limit int
Cursor string
}
type WithdrawFilter uint8
const (
All WithdrawFilter = iota // Same as "initialized"
Proven
Finalized
)
// DepositItem ... Deposit item model for API responses
......@@ -50,41 +62,8 @@ type WithdrawalResponse struct {
}
type BridgeSupplyView struct {
L1DepositSum float64 `json:"l1DepositSum"`
L2WithdrawalSum float64 `json:"l2WithdrawalSum"`
}
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse
// newWithdrawalResponse ... Converts a database.L2BridgeWithdrawalsResponse to an api.WithdrawalResponse
func CreateWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse {
items := make([]WithdrawalItem, len(withdrawals.Withdrawals))
for i, withdrawal := range withdrawals.Withdrawals {
cdh := withdrawal.L2BridgeWithdrawal.CrossDomainMessageHash
if cdh == nil { // Zero value indicates that the withdrawal didn't have a cross domain message
cdh = &common.Hash{0}
}
item := WithdrawalItem{
Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(),
L2BlockHash: withdrawal.L2BlockHash.String(),
Timestamp: withdrawal.L2BridgeWithdrawal.Tx.Timestamp,
From: withdrawal.L2BridgeWithdrawal.Tx.FromAddress.String(),
To: withdrawal.L2BridgeWithdrawal.Tx.ToAddress.String(),
TransactionHash: withdrawal.L2TransactionHash.String(),
Amount: withdrawal.L2BridgeWithdrawal.Tx.Amount.String(),
CrossDomainMessageHash: cdh.String(),
L1ProvenTxHash: withdrawal.ProvenL1TransactionHash.String(),
L1FinalizedTxHash: withdrawal.FinalizedL1TransactionHash.String(),
L1TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.RemoteTokenAddress.String(),
L2TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.LocalTokenAddress.String(),
}
items[i] = item
}
return WithdrawalResponse{
Cursor: withdrawals.Cursor,
HasNextPage: withdrawals.HasNextPage,
Items: items,
}
L1DepositSum float64 `json:"l1DepositSum"`
InitWithdrawalSum float64 `json:"l2WithdrawalSum"`
ProvenWithdrawSum float64 `json:"provenSum"`
FinalizedWithdrawSum float64 `json:"finalizedSum"`
}
package models_test
import (
"fmt"
"reflect"
"testing"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestCreateWithdrawal(t *testing.T) {
// (1) Create a dummy database response object
cdh := common.HexToHash("0x2")
dbWithdrawals := &database.L2BridgeWithdrawalsResponse{
Withdrawals: []database.L2BridgeWithdrawalWithTransactionHashes{
{
L2BridgeWithdrawal: database.L2BridgeWithdrawal{
TransactionWithdrawalHash: common.HexToHash("0x1"),
BridgeTransfer: database.BridgeTransfer{
CrossDomainMessageHash: &cdh,
Tx: database.Transaction{
FromAddress: common.HexToAddress("0x3"),
ToAddress: common.HexToAddress("0x4"),
Timestamp: 5,
},
TokenPair: database.TokenPair{
LocalTokenAddress: common.HexToAddress("0x6"),
RemoteTokenAddress: common.HexToAddress("0x7"),
},
},
},
},
},
}
// (2) Create and validate response object
response := models.CreateWithdrawalResponse(dbWithdrawals)
require.NotEmpty(t, response.Items)
require.Len(t, response.Items, 1)
// (3) Use reflection to check that all fields in WithdrawalItem are populated correctly
item := response.Items[0]
structType := reflect.TypeOf(item)
structVal := reflect.ValueOf(item)
fieldNum := structVal.NumField()
for i := 0; i < fieldNum; i++ {
field := structVal.Field(i)
fieldName := structType.Field(i).Name
isSet := field.IsValid() && !field.IsZero()
require.True(t, isSet, fmt.Sprintf("%s in not set", fieldName))
}
}
......@@ -3,76 +3,32 @@ package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/go-chi/chi/v5"
)
// 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 := models.DepositItem{
Guid: deposit.L1BridgeDeposit.TransactionSourceHash.String(),
L1BlockHash: deposit.L1BlockHash.String(),
Timestamp: deposit.L1BridgeDeposit.Tx.Timestamp,
L1TxHash: deposit.L1TransactionHash.String(),
L2TxHash: deposit.L2TransactionHash.String(),
From: deposit.L1BridgeDeposit.Tx.FromAddress.String(),
To: deposit.L1BridgeDeposit.Tx.ToAddress.String(),
Amount: deposit.L1BridgeDeposit.Tx.Amount.String(),
L1TokenAddress: deposit.L1BridgeDeposit.TokenPair.LocalTokenAddress.String(),
L2TokenAddress: deposit.L1BridgeDeposit.TokenPair.RemoteTokenAddress.String(),
}
items[i] = item
}
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) {
addressValue := chi.URLParam(r, "address")
address := chi.URLParam(r, "address")
cursor := r.URL.Query().Get("cursor")
limitQuery := r.URL.Query().Get("limit")
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
}
limit := r.URL.Query().Get("limit")
err = h.v.ValidateCursor(cursor)
params, err := h.svc.QueryParams(address, cursor, limit)
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())
http.Error(w, "invalid query params", http.StatusBadRequest)
h.logger.Error("error reading request params", "err", err.Error())
return
}
deposits, err := h.view.L1BridgeDepositsByAddress(address, cursor, limit)
deposits, err := h.svc.GetDeposits(params)
if err != nil {
http.Error(w, "Internal server error reading deposits", http.StatusInternalServerError)
h.logger.Error("Unable to read deposits from DB", "err", err.Error())
http.Error(w, "internal server error", http.StatusInternalServerError)
h.logger.Error("error fetching deposits", "err", err.Error())
return
}
response := newDepositResponse(deposits)
err = jsonResponse(w, response, http.StatusOK)
resp := h.svc.DepositResponse(deposits)
err = jsonResponse(w, resp, http.StatusOK)
if err != nil {
h.logger.Error("Error writing response", "err", err)
h.logger.Error("error writing response", "err", err)
}
}
package routes
import (
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/api/service"
"github.com/ethereum/go-ethereum/log"
"github.com/go-chi/chi/v5"
)
// Routes ... Route handler struct
type Routes struct {
logger log.Logger
view database.BridgeTransfersView
router *chi.Mux
v *Validator
svc service.Service
}
// NewRoutes ... Construct a new route handler instance
func NewRoutes(logger log.Logger, bv database.BridgeTransfersView, r *chi.Mux) Routes {
func NewRoutes(l log.Logger, r *chi.Mux, svc service.Service) Routes {
return Routes{
logger: logger,
view: bv,
logger: l,
router: r,
svc: svc,
}
}
......@@ -2,32 +2,18 @@ package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/api/models"
)
// SupplyView ... Handles /api/v0/supply GET requests
func (h Routes) SupplyView(w http.ResponseWriter, r *http.Request) {
depositSum, err := h.view.L1BridgeDepositSum()
view, err := h.svc.GetSupplyInfo()
if err != nil {
http.Error(w, "internal server error reading deposits", http.StatusInternalServerError)
h.logger.Error("unable to read deposits from DB", "err", err.Error())
http.Error(w, "Internal server error", http.StatusInternalServerError)
h.logger.Error("error getting supply info", "err", err)
return
}
withdrawalSum, err := h.view.L2BridgeWithdrawalSum()
if err != nil {
http.Error(w, "internal server error reading withdrawals", http.StatusInternalServerError)
h.logger.Error("unable to read withdrawals from DB", "err", err.Error())
return
}
view := models.BridgeSupplyView{
L1DepositSum: depositSum,
L2WithdrawalSum: withdrawalSum,
}
err = jsonResponse(w, view, http.StatusOK)
if err != nil {
h.logger.Error("error writing response", "err", err)
......
......@@ -3,46 +3,31 @@ package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/go-chi/chi/v5"
)
// L2WithdrawalsHandler ... Handles /api/v0/withdrawals/{address} GET requests
func (h Routes) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
addressValue := chi.URLParam(r, "address")
address := chi.URLParam(r, "address")
cursor := r.URL.Query().Get("cursor")
limitQuery := r.URL.Query().Get("limit")
limit := r.URL.Query().Get("limit")
address, err := h.v.ParseValidateAddress(addressValue)
params, err := h.svc.QueryParams(address, cursor, limit)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
h.logger.Error("Invalid address param", "param", addressValue, "err", err)
http.Error(w, "Invalid query params", http.StatusBadRequest)
h.logger.Error("Invalid query params", "err", err.Error())
return
}
err = h.v.ValidateCursor(cursor)
withdrawals, err := h.svc.GetWithdrawals(params)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
h.logger.Error("Invalid cursor param", "param", cursor, "err", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
h.logger.Error("Error getting withdrawals", "err", err.Error())
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", "err", err.Error())
return
}
response := models.CreateWithdrawalResponse(withdrawals)
err = jsonResponse(w, response, http.StatusOK)
resp := h.svc.WithdrawResponse(withdrawals)
err = jsonResponse(w, resp, http.StatusOK)
if err != nil {
h.logger.Error("Error writing response", "err", err.Error())
}
......
package service
import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
)
type Service interface {
GetDeposits(*models.Params) (*database.L1BridgeDepositsResponse, error)
DepositResponse(*database.L1BridgeDepositsResponse) models.DepositResponse
GetWithdrawals(params *models.Params) (*database.L2BridgeWithdrawalsResponse, error)
WithdrawResponse(*database.L2BridgeWithdrawalsResponse) models.WithdrawalResponse
GetSupplyInfo() (*models.BridgeSupplyView, error)
QueryParams(a, l, c string) (*models.Params, error)
}
type ApiSvc struct {
v *Validator
db database.BridgeTransfersView
logger log.Logger
}
func New(v *Validator, db database.BridgeTransfersView, l log.Logger) Service {
return &ApiSvc{
logger: l,
v: v,
db: db,
}
}
func (svc *ApiSvc) QueryParams(a, c, l string) (*models.Params, error) {
address, err := svc.v.ParseValidateAddress(a)
if err != nil {
svc.logger.Error("invalid address param", "param", a, "err", err)
return nil, err
}
err = svc.v.ValidateCursor(c)
if err != nil {
svc.logger.Error("invalid cursor param", "cursor", c, "err", err)
return nil, err
}
limit, err := svc.v.ParseValidateLimit(l)
if err != nil {
svc.logger.Error("invalid query param", "cursor", c, "err", err)
return nil, err
}
return &models.Params{
Address: address,
Cursor: c,
Limit: limit,
}, nil
}
func (svc *ApiSvc) GetWithdrawals(params *models.Params) (*database.L2BridgeWithdrawalsResponse, error) {
withdrawals, err := svc.db.L2BridgeWithdrawalsByAddress(params.Address, params.Cursor, params.Limit)
if err != nil {
svc.logger.Error("error getting withdrawals", "err", err.Error(), "address", params.Address.String())
return nil, err
}
svc.logger.Debug("read withdrawals from db", "count", len(withdrawals.Withdrawals), "address", params.Address.String())
return withdrawals, nil
}
func (svc *ApiSvc) WithdrawResponse(withdrawals *database.L2BridgeWithdrawalsResponse) models.WithdrawalResponse {
items := make([]models.WithdrawalItem, len(withdrawals.Withdrawals))
for i, withdrawal := range withdrawals.Withdrawals {
cdh := withdrawal.L2BridgeWithdrawal.CrossDomainMessageHash
if cdh == nil { // Zero value indicates that the withdrawal didn't have a cross domain message
cdh = &common.Hash{0}
}
item := models.WithdrawalItem{
Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(),
L2BlockHash: withdrawal.L2BlockHash.String(),
Timestamp: withdrawal.L2BridgeWithdrawal.Tx.Timestamp,
From: withdrawal.L2BridgeWithdrawal.Tx.FromAddress.String(),
To: withdrawal.L2BridgeWithdrawal.Tx.ToAddress.String(),
TransactionHash: withdrawal.L2TransactionHash.String(),
Amount: withdrawal.L2BridgeWithdrawal.Tx.Amount.String(),
CrossDomainMessageHash: cdh.String(),
L1ProvenTxHash: withdrawal.ProvenL1TransactionHash.String(),
L1FinalizedTxHash: withdrawal.FinalizedL1TransactionHash.String(),
L1TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.RemoteTokenAddress.String(),
L2TokenAddress: withdrawal.L2BridgeWithdrawal.TokenPair.LocalTokenAddress.String(),
}
items[i] = item
}
return models.WithdrawalResponse{
Cursor: withdrawals.Cursor,
HasNextPage: withdrawals.HasNextPage,
Items: items,
}
}
func (svc *ApiSvc) GetDeposits(params *models.Params) (*database.L1BridgeDepositsResponse, error) {
deposits, err := svc.db.L1BridgeDepositsByAddress(params.Address, params.Cursor, params.Limit)
if err != nil {
svc.logger.Error("error getting deposits", "err", err.Error(), "address", params.Address.String())
return nil, err
}
svc.logger.Debug("read deposits from db", "count", len(deposits.Deposits), "address", params.Address.String())
return deposits, nil
}
// DepositResponse ... Converts a database.L1BridgeDepositsResponse to an api.DepositResponse
func (svc *ApiSvc) DepositResponse(deposits *database.L1BridgeDepositsResponse) models.DepositResponse {
items := make([]models.DepositItem, len(deposits.Deposits))
for i, deposit := range deposits.Deposits {
item := models.DepositItem{
Guid: deposit.L1BridgeDeposit.TransactionSourceHash.String(),
L1BlockHash: deposit.L1BlockHash.String(),
Timestamp: deposit.L1BridgeDeposit.Tx.Timestamp,
L1TxHash: deposit.L1TransactionHash.String(),
L2TxHash: deposit.L2TransactionHash.String(),
From: deposit.L1BridgeDeposit.Tx.FromAddress.String(),
To: deposit.L1BridgeDeposit.Tx.ToAddress.String(),
Amount: deposit.L1BridgeDeposit.Tx.Amount.String(),
L1TokenAddress: deposit.L1BridgeDeposit.TokenPair.LocalTokenAddress.String(),
L2TokenAddress: deposit.L1BridgeDeposit.TokenPair.RemoteTokenAddress.String(),
}
items[i] = item
}
return models.DepositResponse{
Cursor: deposits.Cursor,
HasNextPage: deposits.HasNextPage,
Items: items,
}
}
// GetSupplyInfo ... Fetch native bridge supply info
func (svc *ApiSvc) GetSupplyInfo() (*models.BridgeSupplyView, error) {
depositSum, err := svc.db.L1BridgeDepositSum()
if err != nil {
svc.logger.Error("error getting deposit sum", "err", err)
return nil, err
}
initSum, err := svc.db.L2BridgeWithdrawalSum(models.All)
if err != nil {
svc.logger.Error("error getting init sum", "err", err)
return nil, err
}
provenSum, err := svc.db.L2BridgeWithdrawalSum(models.Proven)
if err != nil {
svc.logger.Error("error getting proven sum", "err", err)
return nil, err
}
finalizedSum, err := svc.db.L2BridgeWithdrawalSum(models.Finalized)
if err != nil {
svc.logger.Error("error getting finalized sum", "err", err)
return nil, err
}
return &models.BridgeSupplyView{
L1DepositSum: depositSum,
InitWithdrawalSum: initSum,
ProvenWithdrawSum: provenSum,
FinalizedWithdrawSum: finalizedSum,
}, nil
}
package service_test
import (
"fmt"
"reflect"
"testing"
"github.com/ethereum-optimism/optimism/indexer/api/service"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestCreateWithdrawal(t *testing.T) {
svc := service.New(nil, nil, nil)
// (1) Create a dummy database response object
cdh := common.HexToHash("0x2")
dbWithdrawals := &database.L2BridgeWithdrawalsResponse{
Withdrawals: []database.L2BridgeWithdrawalWithTransactionHashes{
{
L2BridgeWithdrawal: database.L2BridgeWithdrawal{
TransactionWithdrawalHash: common.HexToHash("0x1"),
BridgeTransfer: database.BridgeTransfer{
CrossDomainMessageHash: &cdh,
Tx: database.Transaction{
FromAddress: common.HexToAddress("0x3"),
ToAddress: common.HexToAddress("0x4"),
Timestamp: 5,
},
TokenPair: database.TokenPair{
LocalTokenAddress: common.HexToAddress("0x6"),
RemoteTokenAddress: common.HexToAddress("0x7"),
},
},
},
},
},
}
// (2) Create and validate response object
response := svc.WithdrawResponse(dbWithdrawals)
require.NotEmpty(t, response.Items)
require.Len(t, response.Items, 1)
// (3) Use reflection to check that all fields in WithdrawalItem are populated correctly
item := response.Items[0]
structType := reflect.TypeOf(item)
structVal := reflect.ValueOf(item)
fieldNum := structVal.NumField()
for i := 0; i < fieldNum; i++ {
field := structVal.Field(i)
fieldName := structType.Field(i).Name
isSet := field.IsValid() && !field.IsZero()
require.True(t, isSet, fmt.Sprintf("%s in not set", fieldName))
}
}
package routes
package service
import (
"strconv"
......@@ -14,7 +14,7 @@ type Validator struct{}
// ParseValidateLimit ... Validates and parses the limit query parameters
func (v *Validator) ParseValidateLimit(limit string) (int, error) {
if limit == "" {
return defaultPageLimit, nil
return 100, nil
}
val, err := strconv.Atoi(limit)
......
......@@ -125,7 +125,7 @@ func (c *Client) HealthCheck() error {
// 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
var response models.DepositResponse
url := c.cfg.BaseURL + api.DepositsPath + l1Address.String() + urlParams
endpoint := fmt.Sprintf(url, cursor, c.cfg.PaginationLimit)
......@@ -134,11 +134,11 @@ func (c *Client) GetDepositsByAddress(l1Address common.Address, cursor string) (
return nil, err
}
if err := json.Unmarshal(resp, &dResponse); err != nil {
if err := json.Unmarshal(resp, &response); err != nil {
return nil, err
}
return dResponse, nil
return &response, nil
}
// GetAllDepositsByAddress ... Gets all deposits provided a L1 address
......
......@@ -7,6 +7,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/ethereum-optimism/optimism/indexer/api/models"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
......@@ -66,7 +67,7 @@ type BridgeTransfersView interface {
L1BridgeDepositsByAddress(common.Address, string, int) (*L1BridgeDepositsResponse, error)
L2BridgeWithdrawal(common.Hash) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalSum() (float64, error)
L2BridgeWithdrawalSum(filter models.WithdrawFilter) (float64, error)
L2BridgeWithdrawalWithFilter(BridgeTransfer) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalsByAddress(common.Address, string, int) (*L2BridgeWithdrawalsResponse, error)
}
......@@ -246,9 +247,25 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawal(txWithdrawalHash common.Hash) (*
return &withdrawal, nil
}
func (db *bridgeTransfersDB) L2BridgeWithdrawalSum() (float64, error) {
func (db *bridgeTransfersDB) L2BridgeWithdrawalSum(filter models.WithdrawFilter) (float64, error) {
// Determine where filter
var clause string
switch filter {
case models.All:
clause = ""
case models.Finalized:
clause = "finalized_l1_transaction_hash IS NOT NULL"
case models.Proven:
clause = "proven_l1_transaction_hash IS NOT NULL"
default:
return 0, fmt.Errorf("unknown filter argument: %d", filter)
}
var sum float64
result := db.gorm.Model(&L2TransactionWithdrawal{}).Select("sum(amount)").Scan(&sum)
result := db.gorm.Model(&L2TransactionWithdrawal{}).Select("sum(amount)").Scan(&sum).Where(clause)
if result.Error != nil {
return 0, result.Error
}
......
......@@ -463,30 +463,48 @@ func TestClientBridgeFunctions(t *testing.T) {
malAddr := testSuite.OpCfg.Secrets.Addresses().Mallory
type actor struct {
addr common.Address
priv *ecdsa.PrivateKey
addr common.Address
priv *ecdsa.PrivateKey
amt *big.Int
receipt types.Receipt
}
mintSum := bigint.Zero
withdrawSum := bigint.Zero
actors := []actor{
{
addr: aliceAddr,
priv: testSuite.OpCfg.Secrets.Alice,
amt: big.NewInt(0),
},
{
addr: bobAddr,
priv: testSuite.OpCfg.Secrets.Bob,
amt: big.NewInt(0),
},
{
addr: malAddr,
priv: testSuite.OpCfg.Secrets.Mallory,
amt: big.NewInt(0),
},
}
type supplies struct {
all *big.Int
proven *big.Int
finalized *big.Int
}
s := supplies{
all: big.NewInt(0),
proven: big.NewInt(0),
finalized: big.NewInt(0),
}
// (3) Iterate over each actor and deposit / withdraw
for _, actor := range actors {
t.Logf("simulating deposit/withdrawal flow for %s", actor.addr.String())
l2Opts, err := bind.NewKeyedTransactorWithChainID(actor.priv, testSuite.OpCfg.L2ChainIDBig())
require.NoError(t, err)
l2Opts.Value = big.NewInt(params.Ether)
......@@ -517,7 +535,9 @@ func TestClientBridgeFunctions(t *testing.T) {
return seenL1 && seenL2, nil
}))
withdrawSum = new(big.Int).Add(withdrawSum, l2ToL1MessagePasserWithdrawTx.Value())
s.all = new(big.Int).Add(s.all, l2ToL1MessagePasserWithdrawTx.Value())
actor.receipt = *l2ToL1WithdrawReceipt
actor.amt = l2ToL1MessagePasserWithdrawTx.Value()
// (3.d) Ensure that withdrawal and deposit txs are retrievable via API
deposits, err := testSuite.Client.GetAllDepositsByAddress(actor.addr)
......@@ -539,7 +559,27 @@ func TestClientBridgeFunctions(t *testing.T) {
mintFloat, _ := mintSum.Float64()
require.Equal(t, mintFloat, assessment.L1DepositSum)
withdrawFloat, _ := withdrawSum.Float64()
require.Equal(t, withdrawFloat, assessment.L2WithdrawalSum)
withdrawFloat, _ := s.all.Float64()
require.Equal(t, withdrawFloat, assessment.FinalizedWithdrawSum)
// (5) Prove withdrawal for two actors and verify supplies
for i := 0; i < 2; i++ {
_, proveReceipt := op_e2e.ProveWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.EthInstances["sequencer"], actors[i].priv, &actors[i].receipt)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LastL1Header
seen := l1Header != nil && l1Header.Number.Uint64() >= proveReceipt.BlockNumber.Uint64()
return seen, nil
}))
s.proven = new(big.Int).Add(s.proven, actors[i].amt)
}
assessment, err = testSuite.Client.GetSupplyAssessment()
require.NoError(t, err)
provenFloat, _ := s.proven.Float64()
require.Equal(t, provenFloat, assessment.FinalizedWithdrawSum)
// (6) Finalize withdrawal for one actor and verify supplies
}
go test -v ./...
? github.com/ethereum-optimism/optimism/indexer [no test files]
? github.com/ethereum-optimism/optimism/indexer/client [no test files]
=== RUN TestHealthz
api.go:180: INFO [11-13|01:32:04.888] Metrics server started addr=127.0.0.1:54716
api.go:168: INFO [11-13|01:32:04.889] API server started addr=127.0.0.1:54717
--- PASS: TestHealthz (0.00s)
=== RUN TestL1BridgeDepositsHandler
api.go:180: INFO [11-13|01:32:04.890] Metrics server started addr=127.0.0.1:54718
api.go:168: INFO [11-13|01:32:04.890] API server started addr=127.0.0.1:54719
--- PASS: TestL1BridgeDepositsHandler (0.00s)
=== RUN TestL2BridgeWithdrawalsByAddressHandler
api.go:180: INFO [11-13|01:32:04.891] Metrics server started addr=127.0.0.1:54720
api.go:168: INFO [11-13|01:32:04.892] API server started addr=127.0.0.1:54721
--- PASS: TestL2BridgeWithdrawalsByAddressHandler (0.00s)
PASS
ok github.com/ethereum-optimism/optimism/indexer/api (cached)
testing: warning: no tests to run
PASS
ok github.com/ethereum-optimism/optimism/indexer/api/models (cached) [no tests to run]
testing: warning: no tests to run
PASS
ok github.com/ethereum-optimism/optimism/indexer/api/routes (cached) [no tests to run]
=== RUN TestParseValidateLimit
--- PASS: TestParseValidateLimit (0.00s)
=== RUN TestParseValidateAddress
--- PASS: TestParseValidateAddress (0.00s)
=== RUN Test_ParseValidateCursor
--- PASS: Test_ParseValidateCursor (0.00s)
=== RUN TestCreateWithdrawal
--- PASS: TestCreateWithdrawal (0.00s)
PASS
ok github.com/ethereum-optimism/optimism/indexer/api/service (cached)
=== RUN TestClamp
--- PASS: TestClamp (0.00s)
PASS
ok github.com/ethereum-optimism/optimism/indexer/bigint (cached)
? github.com/ethereum-optimism/optimism/indexer/cmd/indexer [no test files]
? github.com/ethereum-optimism/optimism/indexer/database [no test files]
? github.com/ethereum-optimism/optimism/indexer/database/serializers [no test files]
=== RUN TestLoadConfig
config.go:183: INFO [11-12|03:06:40.585] detected preset preset=420 name="Optimism Goerli"
config.go:217: INFO [11-12|03:06:40.585] loaded chain config config="{Preset:420 L1StartingHeight:7017096 L1Contracts:{AddressManager:0xa6f73589243a6A7a9023b1Fa0651b1d89c177111 SystemConfigProxy:0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60 OptimismPortalProxy:0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383 L2OutputOracleProxy:0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0 L1CrossDomainMessengerProxy:0x5086d1eEF304eb5284A0f6720f79403b4e9bE294 L1StandardBridgeProxy:0x636Af16bf2f682dD3109e60102b8E1A089FedAa8 L1ERC721BridgeProxy:0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9 LegacyCanonicalTransactionChain:0x607F755149cFEB3a14E1Dc3A4E2450Cde7dfb04D LegacyStateCommitmentChain:0x9c945aC97Baf48cB784AbBB61399beB71aF7A378} L2Contracts:{L2ToL1MessagePasser:0x4200000000000000000000000000000000000016 L2CrossDomainMessenger:0x4200000000000000000000000000000000000007 L2StandardBridge:0x4200000000000000000000000000000000000010 L2ERC721Bridge:0x4200000000000000000000000000000000000014} L1BedrockStartingHeight:8300214 L2BedrockStartingHeight:4061224 L1ConfirmationDepth:0 L2ConfirmationDepth:0 L1PollingInterval:5000 L2PollingInterval:5000 L1HeaderBufferSize:500 L2HeaderBufferSize:500}"
--- PASS: TestLoadConfig (0.00s)
=== RUN TestLoadConfigWithoutPreset
config.go:217: INFO [11-12|03:06:40.586] loaded chain config config="{Preset:0 L1StartingHeight:0 L1Contracts:{AddressManager:0x0000000000000000000000000000000000000000 SystemConfigProxy:0x0000000000000000000000000000000000000000 OptimismPortalProxy:0x4205fC579115071764C7423a4F12eddE41F106eD L2OutputOracleProxy:0x42097868233D1aA22e815A266982f2cF17685a27 L1CrossDomainMessengerProxy:0x420Ce71c97b33cC4729cf772AE268934f7Ab5fA1 L1StandardBridgeProxy:0x4209FC46F92E8A1c0DEc1b1747D010903e884be1 L1ERC721BridgeProxy:0x0000000000000000000000000000000000000000 LegacyCanonicalTransactionChain:0x0000000000000000000000000000000000000000 LegacyStateCommitmentChain:0x0000000000000000000000000000000000000000} L2Contracts:{L2ToL1MessagePasser:0x4200000000000000000000000000000000000016 L2CrossDomainMessenger:0x4200000000000000000000000000000000000007 L2StandardBridge:0x4200000000000000000000000000000000000010 L2ERC721Bridge:0x4200000000000000000000000000000000000014} L1BedrockStartingHeight:0 L2BedrockStartingHeight:0 L1ConfirmationDepth:0 L2ConfirmationDepth:0 L1PollingInterval:5000 L2PollingInterval:5000 L1HeaderBufferSize:500 L2HeaderBufferSize:500}"
--- PASS: TestLoadConfigWithoutPreset (0.00s)
=== RUN TestLoadConfigWithUnknownPreset
--- PASS: TestLoadConfigWithUnknownPreset (0.00s)
=== RUN TestLoadConfigPollingValues
config.go:217: INFO [11-12|03:06:40.588] loaded chain config config="{Preset:0 L1StartingHeight:0 L1Contracts:{AddressManager:0x0000000000000000000000000000000000000000 SystemConfigProxy:0x0000000000000000000000000000000000000000 OptimismPortalProxy:0x0000000000000000000000000000000000000000 L2OutputOracleProxy:0x0000000000000000000000000000000000000000 L1CrossDomainMessengerProxy:0x0000000000000000000000000000000000000000 L1StandardBridgeProxy:0x0000000000000000000000000000000000000000 L1ERC721BridgeProxy:0x0000000000000000000000000000000000000000 LegacyCanonicalTransactionChain:0x0000000000000000000000000000000000000000 LegacyStateCommitmentChain:0x0000000000000000000000000000000000000000} L2Contracts:{L2ToL1MessagePasser:0x4200000000000000000000000000000000000016 L2CrossDomainMessenger:0x4200000000000000000000000000000000000007 L2StandardBridge:0x4200000000000000000000000000000000000010 L2ERC721Bridge:0x4200000000000000000000000000000000000014} L1BedrockStartingHeight:0 L2BedrockStartingHeight:0 L1ConfirmationDepth:0 L2ConfirmationDepth:0 L1PollingInterval:1000 L2PollingInterval:1005 L1HeaderBufferSize:100 L2HeaderBufferSize:105}"
--- PASS: TestLoadConfigPollingValues (0.00s)
=== RUN TestLoadedConfigPresetPrecendence
config.go:183: INFO [11-12|03:06:40.589] detected preset preset=10 name=Optimism
config.go:217: INFO [11-12|03:06:40.589] loaded chain config config="{Preset:10 L1StartingHeight:13596466 L1Contracts:{AddressManager:0xdE1FCfB0851916CA5101820A69b13a4E276bd81F SystemConfigProxy:0x229047fed2591dbec1eF1118d64F7aF3dB9EB290 OptimismPortalProxy:0x0000000000000000000000000000000000000001 L2OutputOracleProxy:0xdfe97868233d1aa22e815a266982f2cf17685a27 L1CrossDomainMessengerProxy:0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1 L1StandardBridgeProxy:0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1 L1ERC721BridgeProxy:0x5a7749f83b81B301cAb5f48EB8516B986DAef23D LegacyCanonicalTransactionChain:0x5E4e65926BA27467555EB562121fac00D24E9dD2 LegacyStateCommitmentChain:0xBe5dAb4A2e9cd0F27300dB4aB94BeE3A233AEB19} L2Contracts:{L2ToL1MessagePasser:0x4200000000000000000000000000000000000016 L2CrossDomainMessenger:0x4200000000000000000000000000000000000007 L2StandardBridge:0x4200000000000000000000000000000000000010 L2ERC721Bridge:0x4200000000000000000000000000000000000014} L1BedrockStartingHeight:17422590 L2BedrockStartingHeight:105235063 L1ConfirmationDepth:50 L2ConfirmationDepth:100 L1PollingInterval:5000 L2PollingInterval:5000 L1HeaderBufferSize:500 L2HeaderBufferSize:500}"
--- PASS: TestLoadedConfigPresetPrecendence (0.00s)
=== RUN TestLocalDevnet
config.go:175: INFO [11-12|03:06:40.591] detected preset preset=901 name="Local Devnet"
config.go:217: INFO [11-12|03:06:40.591] loaded chain config config="{Preset:901 L1StartingHeight:0 L1Contracts:{AddressManager:0x6eC7C55a2B65dC2dF45018a61540aAdcbD0e6A02 SystemConfigProxy:0xcB1C61f11F1F0ccf5022B6AE7527a0d352F24287 OptimismPortalProxy:0x3aa760bb410F2D912E17CFdd0936657c84cc4c69 L2OutputOracleProxy:0x78950A0F116577A7DC4F0005Ca5E0B671bd4e0C8 L1CrossDomainMessengerProxy:0xF6B9C9Bb93fBd5E391dE2825b7a968ceBADDEfB7 L1StandardBridgeProxy:0x87D5c6f1d55a0a7d2278A11037A97593904260d5 L1ERC721BridgeProxy:0xC6152238194923E897156c5b95d9AAD78AA2F568 LegacyCanonicalTransactionChain:0x0000000000000000000000000000000000000000 LegacyStateCommitmentChain:0x0000000000000000000000000000000000000000} L2Contracts:{L2ToL1MessagePasser:0x4200000000000000000000000000000000000016 L2CrossDomainMessenger:0x4200000000000000000000000000000000000007 L2StandardBridge:0x4200000000000000000000000000000000000010 L2ERC721Bridge:0x4200000000000000000000000000000000000014} L1BedrockStartingHeight:0 L2BedrockStartingHeight:0 L1ConfirmationDepth:0 L2ConfirmationDepth:0 L1PollingInterval:5000 L2PollingInterval:5000 L1HeaderBufferSize:500 L2HeaderBufferSize:500}"
--- PASS: TestLocalDevnet (0.00s)
=== RUN TestThrowsOnUnknownKeys
config.go:164: ERROR[11-12|03:06:40.592] unknown fields in config file fields="[chain.unknown_key db.another_unknownKey]"
--- PASS: TestThrowsOnUnknownKeys (0.00s)
PASS
ok github.com/ethereum-optimism/optimism/indexer/config (cached)
? github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils [no test files]
? github.com/ethereum-optimism/optimism/indexer/processors [no test files]
? github.com/ethereum-optimism/optimism/indexer/processors/bridge [no test files]
? github.com/ethereum-optimism/optimism/indexer/processors/contracts [no test files]
Subproject commit 37a37ab73364d6644bfe11edf88a07880f99bd56
Subproject commit e8a047e3f40f13fa37af6fe14e6e06283d9a060e
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