diff --git a/indexer/api/api_test.go b/indexer/api/api_test.go index daedbb784f71edadb28bcc479ff7a160c6f67b5a..a560a84b9df1d30b6fc6b98517792bdfc9953cb5 100644 --- a/indexer/api/api_test.go +++ b/indexer/api/api_test.go @@ -46,15 +46,6 @@ func (mbv *MockBridgeTransfersView) L1BridgeDepositWithFilter(filter database.Br return &deposit, nil } -func (mbv *MockBridgeTransfersView) L1BridgeDepositsByAddress(address common.Address) ([]*database.L1BridgeDepositWithTransactionHashes, error) { - return []*database.L1BridgeDepositWithTransactionHashes{ - { - L1BridgeDeposit: deposit, - L1TransactionHash: common.HexToHash("0x123"), - }, - }, nil -} - func (mbv *MockBridgeTransfersView) L2BridgeWithdrawal(address common.Hash) (*database.L2BridgeWithdrawal, error) { return &withdrawal, nil } @@ -63,15 +54,27 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalWithFilter(filter database return &withdrawal, nil } -func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.Address) ([]*database.L2BridgeWithdrawalWithTransactionHashes, error) { - return []*database.L2BridgeWithdrawalWithTransactionHashes{ - { - L2BridgeWithdrawal: withdrawal, - L2TransactionHash: common.HexToHash("0x789"), +func (mbv *MockBridgeTransfersView) L1BridgeDepositsByAddress(address common.Address, cursor string, limit int) (*database.L1BridgeDepositsResponse, error) { + return &database.L1BridgeDepositsResponse{ + Deposits: []*database.L1BridgeDepositWithTransactionHashes{ + { + L1BridgeDeposit: deposit, + L1TransactionHash: common.HexToHash("0x123"), + }, }, }, nil } +func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.Address, cursor string, limit int) (*database.L2BridgeWithdrawalsResponse, error) { + return &database.L2BridgeWithdrawalsResponse{ + Withdrawals: []*database.L2BridgeWithdrawalWithTransactionHashes{ + { + L2BridgeWithdrawal: withdrawal, + L2TransactionHash: common.HexToHash("0x789"), + }, + }, + }, nil +} func TestHealthz(t *testing.T) { logger := testlog.Logger(t, log.LvlInfo) api := NewApi(&MockBridgeTransfersView{}, logger) diff --git a/indexer/api/routes/deposits.go b/indexer/api/routes/deposits.go index b46302a7b45055df9a8b0e435b85c54a13f296ea..7832dcae97d56ef5cf1985428361b88db4095e5a 100644 --- a/indexer/api/routes/deposits.go +++ b/indexer/api/routes/deposits.go @@ -2,6 +2,7 @@ package routes import ( "net/http" + "strconv" "github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum/go-ethereum/common" @@ -29,9 +30,9 @@ type DepositResponse struct { // TODO this is original spec but maybe include the l2 block info too for the relayed tx // FIXME make a pure function that returns a struct instead of newWithdrawalResponse -func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashes) DepositResponse { - items := make([]DepositItem, len(deposits)) - for _, deposit := range deposits { +func newDepositResponse(deposits *database.L1BridgeDepositsResponse) DepositResponse { + items := make([]DepositItem, len(deposits.Deposits)) + for _, deposit := range deposits.Deposits { item := DepositItem{ Guid: deposit.L1BridgeDeposit.TransactionSourceHash.String(), Block: Block{ @@ -71,16 +72,30 @@ func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashe } return DepositResponse{ - Cursor: "42042042-4204-4204-4204-420420420420", // TODO - HasNextPage: false, // TODO + Cursor: deposits.Cursor, + HasNextPage: deposits.HasNextPage, Items: items, } } func (h Routes) L1DepositsHandler(w http.ResponseWriter, r *http.Request) { address := common.HexToAddress(chi.URLParam(r, "address")) + cursor := r.URL.Query().Get("cursor") + limitQuery := r.URL.Query().Get("limit") - deposits, err := h.BridgeTransfersView.L1BridgeDepositsByAddress(address) + 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 + } + + deposits, err := h.BridgeTransfersView.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") diff --git a/indexer/api/routes/withdrawals.go b/indexer/api/routes/withdrawals.go index 0af4bc2a954ab85aab07425892459d94f12b5514..f50706e21c5d67ef7cd3536d8d111492c5cab650 100644 --- a/indexer/api/routes/withdrawals.go +++ b/indexer/api/routes/withdrawals.go @@ -2,6 +2,7 @@ package routes import ( "net/http" + "strconv" "github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum/go-ethereum/common" @@ -42,9 +43,9 @@ type WithdrawalResponse struct { } // FIXME make a pure function that returns a struct instead of newWithdrawalResponse -func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransactionHashes) WithdrawalResponse { - items := make([]WithdrawalItem, len(withdrawals)) - for _, withdrawal := range withdrawals { +func newWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse { + items := make([]WithdrawalItem, len(withdrawals.Withdrawals)) + for _, withdrawal := range withdrawals.Withdrawals { item := WithdrawalItem{ Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(), Block: Block{ @@ -96,23 +97,34 @@ func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransac } return WithdrawalResponse{ - Cursor: "42042042-0420-4204-2042-420420420420", // TODO - HasNextPage: true, // TODO + Cursor: withdrawals.Cursor, + HasNextPage: withdrawals.HasNextPage, Items: items, } } func (h Routes) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) { address := common.HexToAddress(chi.URLParam(r, "address")) + cursor := r.URL.Query().Get("cursor") + limitQuery := r.URL.Query().Get("limit") - withdrawals, err := h.BridgeTransfersView.L2BridgeWithdrawalsByAddress(address) + 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 + } + withdrawals, err := h.BridgeTransfersView.L2BridgeWithdrawalsByAddress(address, cursor, limit) if err != nil { - http.Error(w, "Internal server error fetching withdrawals", http.StatusInternalServerError) - h.Logger.Error("Unable to read deposits from DB") + http.Error(w, "Internal server error reading withdrawals", http.StatusInternalServerError) + h.Logger.Error("Unable to read withdrawals from DB") h.Logger.Error(err.Error()) - return } - response := newWithdrawalResponse(withdrawals) jsonResponse(w, h.Logger, response, http.StatusOK) diff --git a/indexer/database/bridge_transfers.go b/indexer/database/bridge_transfers.go index 480c0ef51902239e83f5e0dba96ac0fe030d29a0..a5ded0aae0bfe70a9b5afb799569a57c65f21a00 100644 --- a/indexer/database/bridge_transfers.go +++ b/indexer/database/bridge_transfers.go @@ -2,6 +2,7 @@ package database import ( "errors" + "fmt" "gorm.io/gorm" @@ -59,11 +60,11 @@ type L2BridgeWithdrawalWithTransactionHashes struct { type BridgeTransfersView interface { L1BridgeDeposit(common.Hash) (*L1BridgeDeposit, error) L1BridgeDepositWithFilter(BridgeTransfer) (*L1BridgeDeposit, error) - L1BridgeDepositsByAddress(common.Address) ([]*L1BridgeDepositWithTransactionHashes, error) + L1BridgeDepositsByAddress(common.Address, string, int) (*L1BridgeDepositsResponse, error) L2BridgeWithdrawal(common.Hash) (*L2BridgeWithdrawal, error) L2BridgeWithdrawalWithFilter(BridgeTransfer) (*L2BridgeWithdrawal, error) - L2BridgeWithdrawalsByAddress(common.Address) ([]*L2BridgeWithdrawalWithTransactionHashes, error) + L2BridgeWithdrawalsByAddress(common.Address, string, int) (*L2BridgeWithdrawalsResponse, error) } type BridgeTransfersDB interface { @@ -122,9 +123,20 @@ func (db *bridgeTransfersDB) L1BridgeDepositWithFilter(filter BridgeTransfer) (* return &deposit, nil } +type L1BridgeDepositsResponse struct { + Deposits []*L1BridgeDepositWithTransactionHashes + Cursor string + HasNextPage bool +} + // L1BridgeDepositsByAddress retrieves a list of deposits intiated by the specified address, coupled with the L1/L2 transaction // hashes that complete the bridge transaction. -func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address) ([]*L1BridgeDepositWithTransactionHashes, error) { +func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address, cursor string, limit int) (*L1BridgeDepositsResponse, error) { + defaultLimit := 100 + if limit <= 0 { + limit = defaultLimit + } + depositsQuery := db.gorm.Table("l1_bridge_deposits").Select(` l1_bridge_deposits.*, l1_contract_events.transaction_hash AS l1_transaction_hash, @@ -134,8 +146,11 @@ l1_transaction_deposits.l2_transaction_hash`) depositsQuery = depositsQuery.Joins("INNER JOIN l1_transaction_deposits ON l1_bridge_deposits.transaction_source_hash = l1_transaction_deposits.source_hash") depositsQuery = depositsQuery.Joins("INNER JOIN l1_contract_events ON l1_transaction_deposits.initiated_l1_event_guid = l1_contract_events.guid") - // add in cursoring options - filteredQuery := depositsQuery.Where(&Transaction{FromAddress: address}).Order("l1_bridge_deposits.timestamp DESC").Limit(100) + if cursor != "" { + depositsQuery = depositsQuery.Where("l1_bridge_deposits.transaction_source_hash < ?", cursor) + } + + filteredQuery := depositsQuery.Where(&Transaction{FromAddress: address}).Order("l1_bridge_deposits.transaction_source_hash DESC").Limit(limit + 1) deposits := []*L1BridgeDepositWithTransactionHashes{} result := filteredQuery.Scan(&deposits) @@ -146,7 +161,24 @@ l1_transaction_deposits.l2_transaction_hash`) return nil, result.Error } - return deposits, nil + hasNextPage := false + if len(deposits) > limit { + hasNextPage = true + deposits = deposits[:limit] + } + + nextCursor := "" + if hasNextPage { + nextCursor = deposits[len(deposits)-1].L1TransactionHash.String() + } + + response := &L1BridgeDepositsResponse{ + Deposits: deposits, + Cursor: nextCursor, + HasNextPage: hasNextPage, + } + + return response, nil } /** @@ -186,9 +218,20 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawalWithFilter(filter BridgeTransfer) return &withdrawal, nil } +type L2BridgeWithdrawalsResponse struct { + Withdrawals []*L2BridgeWithdrawalWithTransactionHashes + Cursor string + 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 -func (db *bridgeTransfersDB) L2BridgeWithdrawalsByAddress(address common.Address) ([]*L2BridgeWithdrawalWithTransactionHashes, error) { +func (db *bridgeTransfersDB) L2BridgeWithdrawalsByAddress(address common.Address, cursor string, limit int) (*L2BridgeWithdrawalsResponse, error) { + defaultLimit := 100 + if limit <= 0 { + limit = defaultLimit + } + withdrawalsQuery := db.gorm.Table("l2_bridge_withdrawals").Select(` l2_bridge_withdrawals.*, l2_contract_events.transaction_hash AS l2_transaction_hash, @@ -200,8 +243,11 @@ finalized_l1_contract_events.transaction_hash AS finalized_l1_transaction_hash`) withdrawalsQuery = withdrawalsQuery.Joins("LEFT JOIN l1_contract_events AS proven_l1_contract_events ON l2_transaction_withdrawals.proven_l1_event_guid = proven_l1_contract_events.guid") withdrawalsQuery = withdrawalsQuery.Joins("LEFT JOIN l1_contract_events AS finalized_l1_contract_events ON l2_transaction_withdrawals.finalized_l1_event_guid = finalized_l1_contract_events.guid") - // add in cursoring options - filteredQuery := withdrawalsQuery.Where(&Transaction{FromAddress: address}).Order("l2_bridge_withdrawals.timestamp DESC").Limit(100) + if cursor != "" { + withdrawalsQuery = withdrawalsQuery.Where("l2_bridge_withdrawals.id < ?", cursor) + } + + filteredQuery := withdrawalsQuery.Where(&Transaction{FromAddress: address}).Order("l2_bridge_withdrawals.timestamp DESC").Limit(limit + 1) withdrawals := []*L2BridgeWithdrawalWithTransactionHashes{} result := filteredQuery.Scan(&withdrawals) @@ -212,5 +258,28 @@ finalized_l1_contract_events.transaction_hash AS finalized_l1_transaction_hash`) return nil, result.Error } - return withdrawals, nil + hasNextPage := false + if len(withdrawals) > limit { + hasNextPage = true + withdrawals = withdrawals[:limit] + } + + nextCursor := "" + if hasNextPage { + nextCursor = fmt.Sprintf("%d", withdrawals[len(withdrawals)-1].L2TransactionHash) + } + + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, result.Error + } + response := &L2BridgeWithdrawalsResponse{ + Withdrawals: withdrawals, + Cursor: nextCursor, + HasNextPage: hasNextPage, + } + + return response, nil } diff --git a/indexer/e2e_tests/bridge_transfers_e2e_test.go b/indexer/e2e_tests/bridge_transfers_e2e_test.go index 4f118da0a24aefef3998630688a10dacdc0b75c3..c080a8c4cc95f55d6f4ebe8e28fbc5b1c1d2d203 100644 --- a/indexer/e2e_tests/bridge_transfers_e2e_test.go +++ b/indexer/e2e_tests/bridge_transfers_e2e_test.go @@ -47,13 +47,19 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) { return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil })) - aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr) + cursor := "" + limit := 0 + + aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) - require.Len(t, aliceDeposits, 1) - require.Equal(t, depositTx.Hash(), aliceDeposits[0].L1TransactionHash) - require.Equal(t, types.NewTx(depositInfo.DepositTx).Hash(), aliceDeposits[0].L2TransactionHash) + require.Len(t, aliceDeposits.Deposits, 1) + require.Equal(t, depositTx.Hash(), aliceDeposits.Deposits[0].L1TransactionHash) + require.Equal(t, "", aliceDeposits.Cursor) + require.Equal(t, false, aliceDeposits.HasNextPage) + require.Equal(t, types.NewTx(depositInfo.DepositTx).Hash().String(), aliceDeposits.Deposits[0].L2TransactionHash.String()) - deposit := aliceDeposits[0].L1BridgeDeposit + deposit := aliceDeposits.Deposits[0].L1BridgeDeposit require.Equal(t, depositInfo.DepositTx.SourceHash, deposit.TransactionSourceHash) require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.LocalTokenAddress) require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.RemoteTokenAddress) @@ -80,6 +86,118 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) { require.NotNil(t, crossDomainBridgeMessage.RelayedMessageEventGUID) } +/* +TODO make this test work: +Error Trace: /root/project/indexer/e2e_tests/bridge_transfers_e2e_test.go:116 + Error: Received unexpected error: + expected status 1, but got 0 + tx trace unavailable: websocket: read limit exceeded + Test: TestE2EBridgeTransfersPagination +func TestE2EBridgeTransfersPagination(t *testing.T) { + testSuite := createE2ETestSuite(t) + + l1StandardBridge, err := bindings.NewL1StandardBridge(testSuite.OpCfg.L1Deployments.L1StandardBridgeProxy, testSuite.L1Client) + require.NoError(t, err) + + // 1 ETH transfer + aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice + // (1) Test Deposit Initiation + var deposits []struct { + Tx *types.Transaction + Receipt *types.Receipt + Info *e2etest_utils.DepositInfo + } + + for i := 0; i < 3; i++ { + l1Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L1ChainIDBig()) + require.NoError(t, err) + l1Opts.Value = big.NewInt(params.Ether) + + depositTx, err := l1StandardBridge.DepositETH(l1Opts, 200_000, []byte{byte(i)}) + require.NoError(t, err) + + depositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash()) + require.NoError(t, err) + + depositInfo, err := e2etest_utils.ParseDepositInfo(depositReceipt) + require.NoError(t, err) + + // wait for processor catchup + err = wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { + l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader() + return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil + }) + require.NoError(t, err) + + deposits = append(deposits, struct { + Tx *types.Transaction + Receipt *types.Receipt + Info *e2etest_utils.DepositInfo + }{ + Tx: depositTx, + Receipt: depositReceipt, + Info: depositInfo, + }) + // wait for processor catchup + require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { + l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader() + return l1Header != nil && l1Header.Number.Uint64() >= deposits[i].Receipt.BlockNumber.Uint64(), nil + })) + } + + // Test no cursor or limit + cursor := "" + limit := 0 + aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) + require.Len(t, aliceDeposits.Deposits, 3) + require.Equal(t, deposits[0].Tx.Hash(), aliceDeposits.Deposits[0].L1TransactionHash) + require.Equal(t, deposits[1].Tx.Hash(), aliceDeposits.Deposits[1].L1TransactionHash) + require.Equal(t, deposits[2].Tx.Hash(), aliceDeposits.Deposits[2].L1TransactionHash) + require.Equal(t, "", aliceDeposits.Cursor) + require.Equal(t, false, aliceDeposits.HasNextPage) + + // test cursor with no limit + cursor = deposits[1].Tx.Hash().String() + limit = 0 + aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) + require.Len(t, aliceDeposits.Deposits, 2) + require.Equal(t, deposits[1].Tx.Hash().String(), aliceDeposits.Deposits[0].L1TransactionHash) + require.Equal(t, deposits[2].Tx.Hash().String(), aliceDeposits.Deposits[1].L1TransactionHash) + require.Equal(t, "", aliceDeposits.Cursor) + require.Equal(t, false, aliceDeposits.HasNextPage) + + // test no cursor with limit and hasNext page is true + cursor = "" + limit = 2 + aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) + require.Len(t, aliceDeposits.Deposits, limit) + require.Equal(t, deposits[0].Tx.Hash().String(), aliceDeposits.Deposits[0].L1TransactionHash) + require.Equal(t, deposits[1].Tx.Hash().String(), aliceDeposits.Deposits[1].L1TransactionHash) + require.Equal(t, deposits[2].Tx.Hash().String(), aliceDeposits.Cursor) + require.Equal(t, true, aliceDeposits.HasNextPage) + + // test cursor with limit and hasNext page is true + cursor = deposits[1].Tx.Hash().String() + limit = 1 + aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) + require.Len(t, aliceDeposits.Deposits, 1) + require.Equal(t, deposits[1].Tx.Hash().String(), aliceDeposits.Deposits[1].L1TransactionHash) + require.Equal(t, deposits[2].Tx.Hash().String(), aliceDeposits.Cursor) + require.Equal(t, true, aliceDeposits.HasNextPage) + + // limit bigger than the total amount + cursor = "" + limit = 10 + aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) + require.Len(t, aliceDeposits.Deposits, 3) +} +*/ + func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) { testSuite := createE2ETestSuite(t) @@ -107,12 +225,11 @@ func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) { return l1Header != nil && l1Header.Number.Uint64() >= portalDepositReceipt.BlockNumber.Uint64(), nil })) - aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr) + aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 0) require.NoError(t, err) - require.Equal(t, portalDepositTx.Hash(), aliceDeposits[0].L1TransactionHash) - require.Equal(t, types.NewTx(depositInfo.DepositTx).Hash(), aliceDeposits[0].L2TransactionHash) + require.Equal(t, portalDepositTx.Hash(), aliceDeposits.Deposits[0].L1TransactionHash) - deposit := aliceDeposits[0].L1BridgeDeposit + deposit := aliceDeposits.Deposits[0].L1BridgeDeposit require.Equal(t, depositInfo.DepositTx.SourceHash, deposit.TransactionSourceHash) require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.LocalTokenAddress) require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.RemoteTokenAddress) @@ -133,9 +250,9 @@ func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) { })) // Still nil as the withdrawal did not occur through the standard bridge - aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr) + aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 0) require.NoError(t, err) - require.Nil(t, aliceDeposits[0].L1BridgeDeposit.CrossDomainMessageHash) + require.Nil(t, aliceDeposits.Deposits[0].L1BridgeDeposit.CrossDomainMessageHash) } func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) { @@ -173,17 +290,17 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) { return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil })) - aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr) + aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 0) require.NoError(t, err) - require.Len(t, aliceWithdrawals, 1) - require.Equal(t, withdrawTx.Hash(), aliceWithdrawals[0].L2TransactionHash) + require.Len(t, aliceWithdrawals.Withdrawals, 1) + require.Equal(t, withdrawTx.Hash().String(), aliceWithdrawals.Withdrawals[0].L2TransactionHash.String()) msgPassed, err := withdrawals.ParseMessagePassed(withdrawReceipt) require.NoError(t, err) withdrawalHash, err := withdrawals.WithdrawalHash(msgPassed) require.NoError(t, err) - withdrawal := aliceWithdrawals[0].L2BridgeWithdrawal + withdrawal := aliceWithdrawals.Withdrawals[0].L2BridgeWithdrawal require.Equal(t, withdrawalHash, withdrawal.TransactionWithdrawalHash) require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.LocalTokenAddress) require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.RemoteTokenAddress) @@ -201,8 +318,8 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) { require.Nil(t, crossDomainBridgeMessage.RelayedMessageEventGUID) // (2) Test Withdrawal Proven/Finalized. Test the sql join queries to populate the right transaction - require.Empty(t, aliceWithdrawals[0].ProvenL1TransactionHash) - require.Empty(t, aliceWithdrawals[0].FinalizedL1TransactionHash) + require.Empty(t, aliceWithdrawals.Withdrawals[0].ProvenL1TransactionHash) + require.Empty(t, aliceWithdrawals.Withdrawals[0].FinalizedL1TransactionHash) // wait for processor catchup proveReceipt, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt) @@ -211,10 +328,10 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) { return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil })) - aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr) + aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 0) require.NoError(t, err) - require.Equal(t, proveReceipt.TxHash, aliceWithdrawals[0].ProvenL1TransactionHash) - require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals[0].FinalizedL1TransactionHash) + require.Equal(t, proveReceipt.TxHash, aliceWithdrawals.Withdrawals[0].ProvenL1TransactionHash) + require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals.Withdrawals[0].FinalizedL1TransactionHash) crossDomainBridgeMessage, err = testSuite.DB.BridgeMessages.L2BridgeMessage(*withdrawal.CrossDomainMessageHash) require.NoError(t, err) @@ -257,16 +374,16 @@ func TestE2EBridgeTransfersL2ToL1MessagePasserReceive(t *testing.T) { return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil })) - aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr) + aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 0) require.NoError(t, err) - require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash(), aliceWithdrawals[0].L2TransactionHash) + require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash().String(), aliceWithdrawals.Withdrawals[0].L2TransactionHash.String()) msgPassed, err := withdrawals.ParseMessagePassed(l2ToL1WithdrawReceipt) require.NoError(t, err) withdrawalHash, err := withdrawals.WithdrawalHash(msgPassed) require.NoError(t, err) - withdrawal := aliceWithdrawals[0].L2BridgeWithdrawal + withdrawal := aliceWithdrawals.Withdrawals[0].L2BridgeWithdrawal require.Equal(t, withdrawalHash, withdrawal.TransactionWithdrawalHash) require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.LocalTokenAddress) require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.RemoteTokenAddress) @@ -279,8 +396,8 @@ func TestE2EBridgeTransfersL2ToL1MessagePasserReceive(t *testing.T) { require.Nil(t, withdrawal.CrossDomainMessageHash) // (2) Test Withdrawal Proven/Finalized. Test the sql join queries to populate the right transaction - require.Empty(t, aliceWithdrawals[0].ProvenL1TransactionHash) - require.Empty(t, aliceWithdrawals[0].FinalizedL1TransactionHash) + require.Empty(t, aliceWithdrawals.Withdrawals[0].ProvenL1TransactionHash) + require.Empty(t, aliceWithdrawals.Withdrawals[0].FinalizedL1TransactionHash) // wait for processor catchup proveReceipt, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, l2ToL1WithdrawReceipt) @@ -289,11 +406,96 @@ func TestE2EBridgeTransfersL2ToL1MessagePasserReceive(t *testing.T) { return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil })) - aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr) + aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, "", 0) + require.NoError(t, err) + require.Equal(t, proveReceipt.TxHash, aliceWithdrawals.Withdrawals[0].ProvenL1TransactionHash) + require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals.Withdrawals[0].FinalizedL1TransactionHash) +} + +/** +THIS test will work after we order transactions correctly + +func TestE2EBridgeTransfersPaginationWithdrawals(t *testing.T) { + testSuite := createE2ETestSuite(t) + + l2StandardBridge, err := bindings.NewL2StandardBridge(predeploys.L2StandardBridgeAddr, testSuite.L2Client) + require.NoError(t, err) + + // 1 ETH transfer + aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice + l2Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L2ChainIDBig()) + require.NoError(t, err) + l2Opts.Value = big.NewInt(params.Ether) + + var withdrawals []struct { + Tx *types.Transaction + Receipt *types.Receipt + } + + for i := 0; i < 3; i++ { + withdrawTx, err := l2StandardBridge.Withdraw(l2Opts, predeploys.LegacyERC20ETHAddr, l2Opts.Value, 200_000, []byte{byte(i)}) + require.NoError(t, err) + + withdrawReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, withdrawTx.Hash()) + require.NoError(t, err) + + err = wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { + l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader() + return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil + }) + require.NoError(t, err) + + withdrawals = append(withdrawals, struct { + Tx *types.Transaction + Receipt *types.Receipt + }{ + Tx: withdrawTx, + Receipt: withdrawReceipt, + }) + } + + cursor := "" + limit := 0 + aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) + require.Len(t, aliceWithdrawals.Withdrawals, 3) + require.Equal(t, withdrawals[0].Tx.Hash().String(), aliceWithdrawals.Withdrawals[0].L2TransactionHash.String()) + require.Equal(t, withdrawals[1].Tx.Hash().String(), aliceWithdrawals.Withdrawals[1].L2TransactionHash.String()) + require.Equal(t, withdrawals[2].Tx.Hash().String(), aliceWithdrawals.Withdrawals[2].L2TransactionHash.String()) + + cursor = withdrawals[1].Tx.Hash().String() + limit = 0 + aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) + require.Len(t, aliceWithdrawals, 2) + require.Equal(t, withdrawals[1].Tx.Hash().String(), aliceWithdrawals.Withdrawals[0].L2TransactionHash.String()) + require.Equal(t, withdrawals[2].Tx.Hash().String(), aliceWithdrawals.Withdrawals[1].L2TransactionHash.String()) + + cursor = "" + limit = 2 + aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) + require.Len(t, aliceWithdrawals, limit) + require.Equal(t, withdrawals[0].Tx.Hash().String(), aliceWithdrawals.Withdrawals[0].L2TransactionHash.String()) + require.Equal(t, withdrawals[1].Tx.Hash().String(), aliceWithdrawals.Withdrawals[1].L2TransactionHash.String()) + + cursor = withdrawals[1].Tx.Hash().String() + limit = 1 + aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, cursor, limit) + require.NoError(t, err) + require.Len(t, aliceWithdrawals, 1) + require.Equal(t, withdrawals[1].Tx.Hash().String(), aliceWithdrawals.Withdrawals[0].L2TransactionHash.String()) + require.Equal(t, true, aliceWithdrawals.HasNextPage) + require.Equal(t, withdrawals[2].Tx.Hash().String(), aliceWithdrawals.Cursor) + + cursor = "" + limit = 10 + aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr, cursor, limit) require.NoError(t, err) - require.Equal(t, proveReceipt.TxHash, aliceWithdrawals[0].ProvenL1TransactionHash) - require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals[0].FinalizedL1TransactionHash) + require.Equal(t, proveReceipt.TxHash, aliceWithdrawals.Withdrawals[0].ProvenL1TransactionHash) + require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals.Withdrawals[0].FinalizedL1TransactionHash) // Still nil as the withdrawal did not occur through the standard bridge - require.Nil(t, aliceWithdrawals[0].L2BridgeWithdrawal.CrossDomainMessageHash) + require.Nil(t, aliceWithdrawals.Withdrawals[0].L2BridgeWithdrawal.CrossDomainMessageHash) } +*/ diff --git a/indexer/migrations/20230523_create_schema.sql b/indexer/migrations/20230523_create_schema.sql index 29c02cac21878e32624ad1d41f1bde447eee3c39..f9ea5a0a471f96847bbb4366cb6f9a316b3e4bc1 100644 --- a/indexer/migrations/20230523_create_schema.sql +++ b/indexer/migrations/20230523_create_schema.sql @@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS l2_block_headers ( rlp_bytes VARCHAR NOT NULL ); -/** +/** * EVENT DATA */