Skip to content

Commit

Permalink
Merge pull request #7680 from epociask/indexer.api.supply_view
Browse files Browse the repository at this point in the history
feat(indexer) API Observability for Bridge Supplies
  • Loading branch information
hamdiallam authored Nov 8, 2023
2 parents a523909 + fd47aec commit 30cacfa
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 5 deletions.
4 changes: 4 additions & 0 deletions indexer/api-ts/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ export interface WithdrawalResponse {
hasNextPage: boolean;
items: WithdrawalItem[];
}
export interface BridgeSupplyView {
l1DepositSum: number /* float64 */;
l2WithdrawalSum: number /* float64 */;
}
5 changes: 3 additions & 2 deletions indexer/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const (
HealthPath = "/healthz"
DepositsPath = "/api/v0/deposits/"
WithdrawalsPath = "/api/v0/withdrawals/"

SupplyPath = "/api/v0/supply"
)

// Api ... Indexer API struct
Expand Down Expand Up @@ -140,15 +142,14 @@ func (a *APIService) initRouter(apiConfig config.ServerConfig) {

promRecorder := metrics.NewPromHTTPRecorder(a.metricsRegistry, MetricsNamespace)

// (2) Inject routing middleware
apiRouter.Use(chiMetricsMiddleware(promRecorder))
apiRouter.Use(middleware.Timeout(time.Duration(apiConfig.WriteTimeout) * time.Second))
apiRouter.Use(middleware.Recoverer)
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)
apiRouter.Get(SupplyPath, h.SupplyView)
a.router = apiRouter
}

Expand Down
8 changes: 8 additions & 0 deletions indexer/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.
},
}, nil
}

func (mbv *MockBridgeTransfersView) L1BridgeDepositSum() (float64, error) {
return 69, nil
}
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalSum() (float64, error) {
return 420, nil
}

func TestHealthz(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
cfg := &Config{
Expand Down
5 changes: 5 additions & 0 deletions indexer/api/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ type WithdrawalResponse struct {
Items []WithdrawalItem `json:"items"`
}

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 {
Expand Down
35 changes: 35 additions & 0 deletions indexer/api/routes/supply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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()
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())
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)
}
}
20 changes: 20 additions & 0 deletions indexer/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
healthz = "get_health"
deposits = "get_deposits"
withdrawals = "get_withdrawals"
sum = "get_sum"
)

// Option ... Provides configuration through callback injection
Expand Down Expand Up @@ -164,6 +165,25 @@ func (c *Client) GetAllDepositsByAddress(l1Address common.Address) ([]models.Dep

}

// GetSupplyAssessment ... Returns an assessment of the current supply
// on both L1 and L2. This includes the individual sums of
// (L1/L2) deposits and withdrawals
func (c *Client) GetSupplyAssessment() (*models.BridgeSupplyView, error) {
url := c.cfg.BaseURL + api.SupplyPath

resp, err := c.doRecordRequest(sum, url)
if err != nil {
return nil, err
}

var bsv *models.BridgeSupplyView
if err := json.Unmarshal(resp, &bsv); err != nil {
return nil, err
}

return bsv, nil
}

// GetAllWithdrawalsByAddress ... Gets all withdrawals provided a L2 address
func (c *Client) GetAllWithdrawalsByAddress(l2Address common.Address) ([]models.WithdrawalItem, error) {
var withdrawals []models.WithdrawalItem
Expand Down
23 changes: 23 additions & 0 deletions indexer/database/bridge_transfers.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ type L2BridgeWithdrawalWithTransactionHashes struct {

type BridgeTransfersView interface {
L1BridgeDeposit(common.Hash) (*L1BridgeDeposit, error)
L1BridgeDepositSum() (float64, error)
L1BridgeDepositWithFilter(BridgeTransfer) (*L1BridgeDeposit, error)
L1BridgeDepositsByAddress(common.Address, string, int) (*L1BridgeDepositsResponse, error)

L2BridgeWithdrawal(common.Hash) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalSum() (float64, error)
L2BridgeWithdrawalWithFilter(BridgeTransfer) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalsByAddress(common.Address, string, int) (*L2BridgeWithdrawalsResponse, error)
}
Expand Down Expand Up @@ -136,6 +138,17 @@ type L1BridgeDepositsResponse struct {
HasNextPage bool
}

// L1BridgeDepositSum ... returns the sum of all l1 bridge deposit mints in gwei
func (db *bridgeTransfersDB) L1BridgeDepositSum() (float64, error) {
var sum float64
result := db.gorm.Model(&L1TransactionDeposit{}).Select("sum(amount)").Scan(&sum)
if result.Error != nil {
return 0, result.Error
}

return sum, nil
}

// 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) {
Expand Down Expand Up @@ -233,6 +246,16 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawal(txWithdrawalHash common.Hash) (*
return &withdrawal, nil
}

func (db *bridgeTransfersDB) L2BridgeWithdrawalSum() (float64, error) {
var sum float64
result := db.gorm.Model(&L2TransactionWithdrawal{}).Select("sum(amount)").Scan(&sum)
if result.Error != nil {
return 0, result.Error
}

return sum, nil
}

// L2BridgeWithdrawalWithFilter queries for a bridge withdrawal with set fields in the `BridgeTransfer` filter
func (db *bridgeTransfersDB) L2BridgeWithdrawalWithFilter(filter BridgeTransfer) (*L2BridgeWithdrawal, error) {
var withdrawal L2BridgeWithdrawal
Expand Down
26 changes: 25 additions & 1 deletion indexer/e2e_tests/bridge_transfers_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"
"time"

"github.com/ethereum-optimism/optimism/indexer/bigint"
e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
Expand Down Expand Up @@ -447,7 +448,7 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) {
}
}

func TestClientGetWithdrawals(t *testing.T) {
func TestClientBridgeFunctions(t *testing.T) {
testSuite := createE2ETestSuite(t)

// (1) Generate contract bindings for the L1 and L2 standard bridges
Expand All @@ -459,12 +460,16 @@ func TestClientGetWithdrawals(t *testing.T) {
// (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
malAddr := testSuite.OpCfg.Secrets.Addresses().Mallory

type actor struct {
addr common.Address
priv *ecdsa.PrivateKey
}

mintSum := bigint.Zero
withdrawSum := bigint.Zero

actors := []actor{
{
addr: aliceAddr,
Expand All @@ -474,6 +479,10 @@ func TestClientGetWithdrawals(t *testing.T) {
addr: bobAddr,
priv: testSuite.OpCfg.Secrets.Bob,
},
{
addr: malAddr,
priv: testSuite.OpCfg.Secrets.Mallory,
},
}

// (3) Iterate over each actor and deposit / withdraw
Expand All @@ -491,6 +500,8 @@ func TestClientGetWithdrawals(t *testing.T) {
_, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)

mintSum = new(big.Int).Add(mintSum, depositTx.Value())

// (3.b) Initiate withdrawal transaction via L2ToL1MessagePasser contract
l2ToL1MessagePasserWithdrawTx, err := l2ToL1MessagePasser.Receive(l2Opts)
require.NoError(t, err)
Expand All @@ -503,6 +514,8 @@ func TestClientGetWithdrawals(t *testing.T) {
return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil
}))

withdrawSum = new(big.Int).Add(withdrawSum, l2ToL1MessagePasserWithdrawTx.Value())

// (3.d) Ensure that withdrawal and deposit txs are retrievable via API
deposits, err := testSuite.Client.GetAllDepositsByAddress(actor.addr)
require.NoError(t, err)
Expand All @@ -513,6 +526,17 @@ func TestClientGetWithdrawals(t *testing.T) {
require.NoError(t, err)
require.Len(t, withdrawals, 1)
require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash().String(), withdrawals[0].TransactionHash)

}

// (4) Ensure that supply assessment is correct
assessment, err := testSuite.Client.GetSupplyAssessment()
require.NoError(t, err)

mintFloat, _ := mintSum.Float64()
require.Equal(t, mintFloat, assessment.L1DepositSum)

withdrawFloat, _ := withdrawSum.Float64()
require.Equal(t, withdrawFloat, assessment.L2WithdrawalSum)

}
4 changes: 2 additions & 2 deletions indexer/migrations/20230523_create_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ CREATE TABLE IF NOT EXISTS l1_transaction_deposits (

-- transaction data. NOTE: `to_address` is the recipient of funds transferred in value field of the
-- L2 deposit transaction and not the amount minted on L1 from the source address. Hence the `amount`
-- column in this table does NOT indiciate the amount transferred to the recipient but instead funds
-- bridged from L1 into `from_address`.
-- column in this table does NOT indicate the amount transferred to the recipient but instead funds
-- bridged from L1 by the `from_address`.
from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL,

Expand Down

0 comments on commit 30cacfa

Please sign in to comment.