From 97ae6b1bf6bb692ac5b8d47b62cc5e3d662eb48a Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Sun, 20 Aug 2023 17:26:24 -0400 Subject: [PATCH] coalesce eth sends and bridge transfers --- indexer/database/bridge_transfers.go | 52 +++++++++++-------- .../e2e_tests/bridge_transfers_e2e_test.go | 2 + indexer/migrations/20230523_create_schema.sql | 10 +--- .../processors/bridge/l1_bridge_processor.go | 15 ------ 4 files changed, 35 insertions(+), 44 deletions(-) diff --git a/indexer/database/bridge_transfers.go b/indexer/database/bridge_transfers.go index d1b1b777ba6e..6e1cb1e19f2c 100644 --- a/indexer/database/bridge_transfers.go +++ b/indexer/database/bridge_transfers.go @@ -106,8 +106,7 @@ func (db *bridgeTransfersDB) L1BridgeDeposit(txSourceHash common.Hash) (*L1Bridg return &deposit, nil } -// L1BridgeDepositByCrossDomainMessengerNonce retrieves tokens deposited, specified by the associated `L1CrossDomainMessenger` nonce. -// All tokens bridged via the StandardBridge flows through the L1CrossDomainMessenger +// L1BridgeDepositWithFilter queries for a bridge deposit with set fields in the `BridgeTransfer` filter func (db *bridgeTransfersDB) L1BridgeDepositWithFilter(filter BridgeTransfer) (*L1BridgeDeposit, error) { var deposit L1BridgeDeposit result := db.gorm.Where(&filter).Take(&deposit) @@ -127,31 +126,43 @@ type L1BridgeDepositsResponse struct { 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. +// 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, 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, -l1_transaction_deposits.l2_transaction_hash`) - // TODO join with l1_tokens and l2_tokens - 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") - - 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) - + ethAddressString := predeploys.LegacyERC20ETHAddr.String() + + // Coalesce l1 transaction deposits that are ETH receives into bridge deposits. + ethTransactionDeposits := db.gorm.Model(&L1TransactionDeposit{}) + ethTransactionDeposits = ethTransactionDeposits.Where(`from_address = ? AND data = '0x' AND amount > 0`, address.String()) + ethTransactionDeposits = ethTransactionDeposits.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = initiated_l1_event_guid") + ethTransactionDeposits = ethTransactionDeposits.Select(` +from_address, to_address, amount, data, source_hash AS transaction_source_hash, +l2_transaction_hash, l1_contract_events.transaction_hash AS l1_transaction_hash, +l1_transaction_deposits.timestamp, NULL AS cross_domain_message_hash, ? AS local_token_address, ? AS remote_token_address`, ethAddressString, ethAddressString) + + depositsQuery := db.gorm.Model(&L1BridgeDeposit{}) + depositsQuery = depositsQuery.Joins("INNER JOIN l1_transaction_deposits ON l1_transaction_deposits.source_hash = transaction_source_hash") + depositsQuery = depositsQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l1_transaction_deposits.initiated_l1_event_guid") + depositsQuery = depositsQuery.Select(` +l1_bridge_deposits.from_address, l1_bridge_deposits.to_address, l1_bridge_deposits.amount, l1_bridge_deposits.data, transaction_source_hash, +l2_transaction_hash, l1_contract_events.transaction_hash as l1_transaction_hash, +l1_bridge_deposits.timestamp, cross_domain_message_hash, local_token_address, remote_token_address`) + + // Since all bridge deposits share have the same primary key corresponding to the transaction + // deposit, we can simply order by the timestamp in the transaction deposits table which will + // order all deposits (bridge & transactions) uniformly + + query := db.gorm.Table("(?) AS deposits", depositsQuery) + query = query.Joins("UNION (?)", ethTransactionDeposits) + query = query.Select("*").Order("timestamp DESC").Limit(limit) deposits := []L1BridgeDepositWithTransactionHashes{} - result := filteredQuery.Scan(&deposits) + result := query.Debug().Find(&deposits) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, nil @@ -201,8 +212,7 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawal(txWithdrawalHash common.Hash) (* return &withdrawal, nil } -// L2BridgeWithdrawalByCrossDomainMessengerNonce retrieves tokens withdrawn, specified by the associated `L2CrossDomainMessenger` nonce. -// All tokens bridged via the StandardBridge flows through the L2CrossDomainMessenger +// L2BridgeWithdrawalWithFilter queries for a bridge withdrawal with set fields in the `BridgeTransfer` filter func (db *bridgeTransfersDB) L2BridgeWithdrawalWithFilter(filter BridgeTransfer) (*L2BridgeWithdrawal, error) { var withdrawal L2BridgeWithdrawal result := db.gorm.Where(filter).Take(&withdrawal) diff --git a/indexer/e2e_tests/bridge_transfers_e2e_test.go b/indexer/e2e_tests/bridge_transfers_e2e_test.go index ddecc42d1817..63c6d9d325bc 100644 --- a/indexer/e2e_tests/bridge_transfers_e2e_test.go +++ b/indexer/e2e_tests/bridge_transfers_e2e_test.go @@ -227,6 +227,8 @@ func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) { aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr, "", 0) require.NoError(t, err) + require.NotNil(t, aliceDeposits) + require.Len(t, aliceDeposits.Deposits, 1) require.Equal(t, portalDepositTx.Hash(), aliceDeposits.Deposits[0].L1TransactionHash) deposit := aliceDeposits.Deposits[0].L1BridgeDeposit diff --git a/indexer/migrations/20230523_create_schema.sql b/indexer/migrations/20230523_create_schema.sql index 2a2af8d836e1..de54f8857497 100644 --- a/indexer/migrations/20230523_create_schema.sql +++ b/indexer/migrations/20230523_create_schema.sql @@ -184,10 +184,7 @@ CREATE TABLE IF NOT EXISTS l2_bridge_messages( -- StandardBridge CREATE TABLE IF NOT EXISTS l1_bridge_deposits ( - transaction_source_hash VARCHAR PRIMARY KEY REFERENCES l1_transaction_deposits(source_hash), - - -- We allow the cross_domain_message_hash to be NULL-able to account - -- for scenarios where ETH is simply sent to the OptimismPortal contract + transaction_source_hash VARCHAR PRIMARY KEY REFERENCES l1_transaction_deposits(source_hash), cross_domain_message_hash VARCHAR UNIQUE REFERENCES l1_bridge_messages(message_hash), -- Deposit information @@ -201,10 +198,7 @@ CREATE TABLE IF NOT EXISTS l1_bridge_deposits ( ); CREATE TABLE IF NOT EXISTS l2_bridge_withdrawals ( transaction_withdrawal_hash VARCHAR PRIMARY KEY REFERENCES l2_transaction_withdrawals(withdrawal_hash), - - -- We allow the cross_domain_message_hash to be NULL-able to account for - -- scenarios where ETH is simply sent to the L2ToL1MessagePasser contract - cross_domain_message_hash VARCHAR UNIQUE REFERENCES l2_bridge_messages(message_hash), + cross_domain_message_hash VARCHAR NOT NULL UNIQUE REFERENCES l2_bridge_messages(message_hash), -- Withdrawal information from_address VARCHAR NOT NULL, diff --git a/indexer/processors/bridge/l1_bridge_processor.go b/indexer/processors/bridge/l1_bridge_processor.go index 3e8a40fdb191..1d7a393952b4 100644 --- a/indexer/processors/bridge/l1_bridge_processor.go +++ b/indexer/processors/bridge/l1_bridge_processor.go @@ -26,7 +26,6 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chainConfig return err } - ethDeposits := []database.L1BridgeDeposit{} portalDeposits := make(map[logKey]*contracts.OptimismPortalTransactionDepositEvent, len(optimismPortalTxDeposits)) transactionDeposits := make([]database.L1TransactionDeposit, len(optimismPortalTxDeposits)) for i := range optimismPortalTxDeposits { @@ -39,14 +38,6 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chainConfig GasLimit: depositTx.GasLimit, Tx: depositTx.Tx, } - - // catch ETH transfers to the portal contract. - if len(depositTx.DepositTx.Data) == 0 && depositTx.DepositTx.Value.BitLen() > 0 { - ethDeposits = append(ethDeposits, database.L1BridgeDeposit{ - TransactionSourceHash: depositTx.DepositTx.SourceHash, - BridgeTransfer: database.BridgeTransfer{Tx: transactionDeposits[i].Tx, TokenPair: database.ETHTokenPair}, - }) - } } if len(transactionDeposits) > 0 { @@ -54,12 +45,6 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chainConfig if err := db.BridgeTransactions.StoreL1TransactionDeposits(transactionDeposits); err != nil { return err } - if len(ethDeposits) > 0 { - log.Info("detected portal ETH transfers", "size", len(ethDeposits)) - if err := db.BridgeTransfers.StoreL1BridgeDeposits(ethDeposits); err != nil { - return err - } - } } // (2) L1CrossDomainMessenger