Commit cc91c03c authored by Hamdi Allam's avatar Hamdi Allam

bridge transaction/transfer schema & models

parent 42ac06a8
package database
import (
"errors"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
/**
* Types
*/
type Transaction struct {
FromAddress common.Address `gorm:"serializer:json"`
ToAddress common.Address `gorm:"serializer:json"`
Amount U256
Data hexutil.Bytes `gorm:"serializer:json"`
Timestamp uint64
}
type L1TransactionDeposit struct {
SourceHash common.Hash `gorm:"serializer:json;primaryKey"`
L2TransactionHash common.Hash `gorm:"serializer:json"`
InitiatedL1EventGUID uuid.UUID
Version U256
OpaqueData hexutil.Bytes `gorm:"serializer:json"`
Tx Transaction `gorm:"embedded"`
GasLimit U256
}
type L2TransactionWithdrawal struct {
WithdrawalHash common.Hash `gorm:"serializer:json;primaryKey"`
InitiatedL2EventGUID uuid.UUID
ProvenL1EventGUID *uuid.UUID
FinalizedL1EventGUID *uuid.UUID
Nonce U256
Tx Transaction `gorm:"embedded"`
GasLimit U256
}
type BridgeTransactionsView interface {
L1TransactionDeposit(common.Hash) (*L1TransactionDeposit, error)
L2TransactionWithdrawal(common.Hash) (*L2TransactionWithdrawal, error)
}
type BridgeTransactionsDB interface {
BridgeTransactionsView
StoreL1TransactionDeposits([]*L1TransactionDeposit) error
StoreL2TransactionWithdrawals([]*L2TransactionWithdrawal) error
MarkL2TransactionWithdrawalProvenEvent(common.Hash, uuid.UUID) error
MarkL2TransactionWithdrawalFinalizedEvent(common.Hash, uuid.UUID) error
}
/**
* Implementation
*/
type bridgeTransactionsDB struct {
gorm *gorm.DB
}
func newBridgeTransactionsDB(db *gorm.DB) BridgeTransactionsDB {
return &bridgeTransactionsDB{gorm: db}
}
/**
* Transactions deposited from L1
*/
func (db *bridgeTransactionsDB) StoreL1TransactionDeposits(deposits []*L1TransactionDeposit) error {
result := db.gorm.Create(&deposits)
return result.Error
}
func (db *bridgeTransactionsDB) L1TransactionDeposit(sourceHash common.Hash) (*L1TransactionDeposit, error) {
var deposit L1TransactionDeposit
result := db.gorm.Where(&L1TransactionDeposit{SourceHash: sourceHash}).Take(&deposit)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &deposit, nil
}
/**
* Transactions withdrawn from L2
*/
func (db *bridgeTransactionsDB) StoreL2TransactionWithdrawals(withdrawals []*L2TransactionWithdrawal) error {
result := db.gorm.Create(&withdrawals)
return result.Error
}
func (db *bridgeTransactionsDB) L2TransactionWithdrawal(withdrawalHash common.Hash) (*L2TransactionWithdrawal, error) {
var withdrawal L2TransactionWithdrawal
result := db.gorm.Where(&L2TransactionWithdrawal{WithdrawalHash: withdrawalHash}).Take(&withdrawal)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &withdrawal, nil
}
// MarkL2TransactionWithdrawalProvenEvent links a withdrawn transaction with associated Prove action on L1.
func (db *bridgeTransactionsDB) MarkL2TransactionWithdrawalProvenEvent(withdrawalHash common.Hash, provenL1EventGuid uuid.UUID) error {
withdrawal, err := db.L2TransactionWithdrawal(withdrawalHash)
if err != nil {
return err
} else if withdrawal == nil {
return fmt.Errorf("transaction withdrawal hash %s not found", withdrawalHash)
}
withdrawal.ProvenL1EventGUID = &provenL1EventGuid
result := db.gorm.Save(&withdrawal)
return result.Error
}
// MarkL2TransactionWithdrawalProvenEvent links a withdrawn transaction in its finalized state
func (db *bridgeTransactionsDB) MarkL2TransactionWithdrawalFinalizedEvent(withdrawalHash common.Hash, finalizedL1EventGuid uuid.UUID) error {
withdrawal, err := db.L2TransactionWithdrawal(withdrawalHash)
if err != nil {
return err
} else if withdrawal == nil {
return fmt.Errorf("transaction withdrawal hash %s not found", withdrawalHash)
} else if withdrawal.ProvenL1EventGUID == nil {
return fmt.Errorf("cannot mark unproven withdrawal hash %s as finalized", withdrawal.WithdrawalHash)
}
withdrawal.FinalizedL1EventGUID = &finalizedL1EventGuid
result := db.gorm.Save(&withdrawal)
return result.Error
}
...@@ -2,40 +2,26 @@ package database ...@@ -2,40 +2,26 @@ package database
import ( import (
"errors" "errors"
"fmt"
"math/big" "math/big"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/google/uuid"
) )
/** /**
* Types * Types
*/ */
type Transaction struct {
FromAddress common.Address `gorm:"serializer:json"`
ToAddress common.Address `gorm:"serializer:json"`
Amount U256
Data hexutil.Bytes `gorm:"serializer:json"`
Timestamp uint64
}
type TokenPair struct { type TokenPair struct {
L1TokenAddress common.Address `gorm:"serializer:json"` L1TokenAddress common.Address `gorm:"serializer:json"`
L2TokenAddress common.Address `gorm:"serializer:json"` L2TokenAddress common.Address `gorm:"serializer:json"`
} }
type L1BridgeDeposit struct { type L1BridgeDeposit struct {
GUID uuid.UUID `gorm:"primaryKey"` TransactionSourceHash common.Hash `gorm:"primaryKey;serializer:json"`
InitiatedL1EventGUID uuid.UUID
CrossDomainMessengerNonce U256
FinalizedL2EventGUID *uuid.UUID CrossDomainMessengerNonce *U256
Tx Transaction `gorm:"embedded"` Tx Transaction `gorm:"embedded"`
TokenPair TokenPair `gorm:"embedded"` TokenPair TokenPair `gorm:"embedded"`
...@@ -45,18 +31,13 @@ type L1BridgeDepositWithTransactionHashes struct { ...@@ -45,18 +31,13 @@ type L1BridgeDepositWithTransactionHashes struct {
L1BridgeDeposit L1BridgeDeposit `gorm:"embedded"` L1BridgeDeposit L1BridgeDeposit `gorm:"embedded"`
L1TransactionHash common.Hash `gorm:"serializer:json"` L1TransactionHash common.Hash `gorm:"serializer:json"`
FinalizedL2TransactionHash common.Hash `gorm:"serializer:json"` L2TransactionHash common.Hash `gorm:"serializer:json"`
} }
type L2BridgeWithdrawal struct { type L2BridgeWithdrawal struct {
GUID uuid.UUID `gorm:"primaryKey"` TransactionWithdrawalHash common.Hash `gorm:"primaryKey;serializer:json"`
InitiatedL2EventGUID uuid.UUID
CrossDomainMessengerNonce U256
WithdrawalHash common.Hash `gorm:"serializer:json"` CrossDomainMessengerNonce *U256
ProvenL1EventGUID *uuid.UUID
FinalizedL1EventGUID *uuid.UUID
Tx Transaction `gorm:"embedded"` Tx Transaction `gorm:"embedded"`
TokenPair TokenPair `gorm:"embedded"` TokenPair TokenPair `gorm:"embedded"`
...@@ -71,10 +52,11 @@ type L2BridgeWithdrawalWithTransactionHashes struct { ...@@ -71,10 +52,11 @@ type L2BridgeWithdrawalWithTransactionHashes struct {
} }
type BridgeTransfersView interface { type BridgeTransfersView interface {
L1BridgeDeposit(common.Hash) (*L1BridgeDeposit, error)
L1BridgeDepositByCrossDomainMessengerNonce(*big.Int) (*L1BridgeDeposit, error) L1BridgeDepositByCrossDomainMessengerNonce(*big.Int) (*L1BridgeDeposit, error)
L1BridgeDepositsByAddress(common.Address) ([]*L1BridgeDepositWithTransactionHashes, error) L1BridgeDepositsByAddress(common.Address) ([]*L1BridgeDepositWithTransactionHashes, error)
L2BridgeWithdrawalByWithdrawalHash(common.Hash) (*L2BridgeWithdrawal, error) L2BridgeWithdrawal(common.Hash) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalByCrossDomainMessengerNonce(*big.Int) (*L2BridgeWithdrawal, error) L2BridgeWithdrawalByCrossDomainMessengerNonce(*big.Int) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalsByAddress(common.Address) ([]*L2BridgeWithdrawalWithTransactionHashes, error) L2BridgeWithdrawalsByAddress(common.Address) ([]*L2BridgeWithdrawalWithTransactionHashes, error)
} }
...@@ -83,11 +65,7 @@ type BridgeTransfersDB interface { ...@@ -83,11 +65,7 @@ type BridgeTransfersDB interface {
BridgeTransfersView BridgeTransfersView
StoreL1BridgeDeposits([]*L1BridgeDeposit) error StoreL1BridgeDeposits([]*L1BridgeDeposit) error
MarkFinalizedL1BridgeDepositEvent(uuid.UUID, uuid.UUID) error
StoreL2BridgeWithdrawals([]*L2BridgeWithdrawal) error StoreL2BridgeWithdrawals([]*L2BridgeWithdrawal) error
MarkProvenL2BridgeWithdrawalEvent(uuid.UUID, uuid.UUID) error
MarkFinalizedL2BridgeWithdrawalEvent(uuid.UUID, uuid.UUID) error
} }
/** /**
...@@ -111,11 +89,24 @@ func (db *bridgeTransfersDB) StoreL1BridgeDeposits(deposits []*L1BridgeDeposit) ...@@ -111,11 +89,24 @@ func (db *bridgeTransfersDB) StoreL1BridgeDeposits(deposits []*L1BridgeDeposit)
return result.Error return result.Error
} }
// L1BridgeDepositByMessageNonce retrieves tokens deposited, specified by the associated `L1CrossDomainMessenger` nonce. func (db *bridgeTransfersDB) L1BridgeDeposit(txSourceHash common.Hash) (*L1BridgeDeposit, error) {
var deposit L1BridgeDeposit
result := db.gorm.Where(&L1BridgeDeposit{TransactionSourceHash: txSourceHash}).Take(&deposit)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &deposit, nil
}
// L1BridgeDepositByCrossDomainMessengerNonce retrieves tokens deposited, specified by the associated `L1CrossDomainMessenger` nonce.
// All tokens bridged via the StandardBridge flows through the L1CrossDomainMessenger // All tokens bridged via the StandardBridge flows through the L1CrossDomainMessenger
func (db *bridgeTransfersDB) L1BridgeDepositByCrossDomainMessengerNonce(nonce *big.Int) (*L1BridgeDeposit, error) { func (db *bridgeTransfersDB) L1BridgeDepositByCrossDomainMessengerNonce(nonce *big.Int) (*L1BridgeDeposit, error) {
var deposit L1BridgeDeposit var deposit L1BridgeDeposit
result := db.gorm.Where(&L1BridgeDeposit{CrossDomainMessengerNonce: U256{Int: nonce}}).Take(&deposit) result := db.gorm.Where(&L1BridgeDeposit{CrossDomainMessengerNonce: &U256{Int: nonce}}).Take(&deposit)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
...@@ -129,13 +120,16 @@ func (db *bridgeTransfersDB) L1BridgeDepositByCrossDomainMessengerNonce(nonce *b ...@@ -129,13 +120,16 @@ func (db *bridgeTransfersDB) L1BridgeDepositByCrossDomainMessengerNonce(nonce *b
// L1BridgeDepositsByAddress retrieves a list of deposits intiated by the specified address, coupled with the L1/L2 transaction // L1BridgeDepositsByAddress retrieves a list of deposits intiated by the specified address, coupled with the L1/L2 transaction
// hashes that complete the bridge transaction. // hashes that complete the bridge transaction.
func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address) ([]*L1BridgeDepositWithTransactionHashes, error) { func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address) ([]*L1BridgeDepositWithTransactionHashes, error) {
depositsQuery := db.gorm.Table("l1_bridge_deposits").Select("l1_bridge_deposits.*, l1_contract_events.transaction_hash AS l1_transaction_hash, l2_contract_events.transaction_hash AS finalized_l2_transaction_hash") 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`)
initiatedJoinQuery := depositsQuery.Joins("LEFT JOIN l1_contract_events ON l1_bridge_deposits.initiated_l1_event_guid = l1_contract_events.guid") depositsQuery = depositsQuery.Joins("INNER JOIN l1_transaction_deposits ON l1_bridge_deposits.transaction_source_hash = l1_transaction_deposits.source_hash")
finalizedJoinQuery := initiatedJoinQuery.Joins("LEFT JOIN l2_contract_events ON l1_bridge_deposits.finalized_l2_event_guid = l2_contract_events.guid") depositsQuery = depositsQuery.Joins("INNER JOIN l1_contract_events ON l1_transaction_deposits.initiated_l1_event_guid = l1_contract_events.guid")
// add in cursoring options // add in cursoring options
filteredQuery := finalizedJoinQuery.Where(&Transaction{FromAddress: address}).Order("l1_bridge_deposits.timestamp DESC").Limit(100) filteredQuery := depositsQuery.Where(&Transaction{FromAddress: address}).Order("l1_bridge_deposits.timestamp DESC").Limit(100)
deposits := []*L1BridgeDepositWithTransactionHashes{} deposits := []*L1BridgeDepositWithTransactionHashes{}
result := filteredQuery.Scan(&deposits) result := filteredQuery.Scan(&deposits)
...@@ -149,18 +143,6 @@ func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address) ( ...@@ -149,18 +143,6 @@ func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address) (
return deposits, nil return deposits, nil
} }
func (db *bridgeTransfersDB) MarkFinalizedL1BridgeDepositEvent(guid, finalizationEventGUID uuid.UUID) error {
var deposit L1BridgeDeposit
result := db.gorm.Where(&L1BridgeDeposit{GUID: guid}).Take(&deposit)
if result.Error != nil {
return result.Error
}
deposit.FinalizedL2EventGUID = &finalizationEventGUID
result = db.gorm.Save(&deposit)
return result.Error
}
/** /**
* Tokens Bridged (Withdrawn) from L2 * Tokens Bridged (Withdrawn) from L2
*/ */
...@@ -170,9 +152,9 @@ func (db *bridgeTransfersDB) StoreL2BridgeWithdrawals(withdrawals []*L2BridgeWit ...@@ -170,9 +152,9 @@ func (db *bridgeTransfersDB) StoreL2BridgeWithdrawals(withdrawals []*L2BridgeWit
return result.Error return result.Error
} }
func (db *bridgeTransfersDB) L2BridgeWithdrawalByWithdrawalHash(withdrawalHash common.Hash) (*L2BridgeWithdrawal, error) { func (db *bridgeTransfersDB) L2BridgeWithdrawal(txWithdrawalHash common.Hash) (*L2BridgeWithdrawal, error) {
var withdrawal L2BridgeWithdrawal var withdrawal L2BridgeWithdrawal
result := db.gorm.Where(&L2BridgeWithdrawal{WithdrawalHash: withdrawalHash}).Take(&withdrawal) result := db.gorm.Where(&L2BridgeWithdrawal{TransactionWithdrawalHash: txWithdrawalHash}).Take(&withdrawal)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
...@@ -187,7 +169,7 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawalByWithdrawalHash(withdrawalHash c ...@@ -187,7 +169,7 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawalByWithdrawalHash(withdrawalHash c
// All tokens bridged via the StandardBridge flows through the L2CrossDomainMessenger // All tokens bridged via the StandardBridge flows through the L2CrossDomainMessenger
func (db *bridgeTransfersDB) L2BridgeWithdrawalByCrossDomainMessengerNonce(nonce *big.Int) (*L2BridgeWithdrawal, error) { func (db *bridgeTransfersDB) L2BridgeWithdrawalByCrossDomainMessengerNonce(nonce *big.Int) (*L2BridgeWithdrawal, error) {
var withdrawal L2BridgeWithdrawal var withdrawal L2BridgeWithdrawal
result := db.gorm.Where(&L2BridgeWithdrawal{CrossDomainMessengerNonce: U256{Int: nonce}}).Take(&withdrawal) result := db.gorm.Where(&L2BridgeWithdrawal{CrossDomainMessengerNonce: &U256{Int: nonce}}).Take(&withdrawal)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
...@@ -201,14 +183,19 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawalByCrossDomainMessengerNonce(nonce ...@@ -201,14 +183,19 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawalByCrossDomainMessengerNonce(nonce
// L2BridgeDepositsByAddress retrieves a list of deposits intiated by the specified address, coupled with the L1/L2 transaction hashes // 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 // 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) ([]*L2BridgeWithdrawalWithTransactionHashes, error) {
withdrawalsQuery := db.gorm.Table("l2_bridge_withdrawals").Select("l2_bridge_withdrawals.*, l2_contract_events.transaction_hash AS l2_transaction_hash, proven_l1_contract_events.transaction_hash AS proven_l1_transaction_hash, finalized_l1_contract_events.transaction_hash AS finalized_l1_transaction_hash") withdrawalsQuery := db.gorm.Table("l2_bridge_withdrawals").Select(`
l2_bridge_withdrawals.*,
l2_contract_events.transaction_hash AS l2_transaction_hash,
proven_l1_contract_events.transaction_hash AS proven_l1_transaction_hash,
finalized_l1_contract_events.transaction_hash AS finalized_l1_transaction_hash`)
eventsJoinQuery := withdrawalsQuery.Joins("LEFT JOIN l2_contract_events ON l2_bridge_withdrawals.initiated_l2_event_guid = l2_contract_events.guid") withdrawalsQuery = withdrawalsQuery.Joins("INNER JOIN l2_transaction_withdrawals ON l2_bridge_withdrawals.transaction_withdrawal_hash = l2_transaction_withdrawals.withdrawal_hash")
provenJoinQuery := eventsJoinQuery.Joins("LEFT JOIN l1_contract_events AS proven_l1_contract_events ON l2_bridge_withdrawals.proven_l1_event_guid = proven_l1_contract_events.guid") withdrawalsQuery = withdrawalsQuery.Joins("INNER JOIN l2_contract_events ON l2_transaction_withdrawals.initiated_l2_event_guid = l2_contract_events.guid")
finalizedJoinQuery := provenJoinQuery.Joins("LEFT JOIN l1_contract_events AS finalized_l1_contract_events ON l2_bridge_withdrawals.finalized_l1_event_guid = finalized_l1_contract_events.guid") 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 // add in cursoring options
filteredQuery := finalizedJoinQuery.Where(&Transaction{FromAddress: address}).Order("l2_bridge_withdrawals.timestamp DESC").Limit(100) filteredQuery := withdrawalsQuery.Where(&Transaction{FromAddress: address}).Order("l2_bridge_withdrawals.timestamp DESC").Limit(100)
withdrawals := []*L2BridgeWithdrawalWithTransactionHashes{} withdrawals := []*L2BridgeWithdrawalWithTransactionHashes{}
result := filteredQuery.Scan(&withdrawals) result := filteredQuery.Scan(&withdrawals)
...@@ -221,31 +208,3 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawalsByAddress(address common.Address ...@@ -221,31 +208,3 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawalsByAddress(address common.Address
return withdrawals, nil return withdrawals, nil
} }
func (db *bridgeTransfersDB) MarkProvenL2BridgeWithdrawalEvent(guid, provenL1EventGuid uuid.UUID) error {
var withdrawal L2BridgeWithdrawal
result := db.gorm.Where(&L2BridgeWithdrawal{GUID: guid}).Take(&withdrawal)
if result.Error != nil {
return result.Error
}
withdrawal.ProvenL1EventGUID = &provenL1EventGuid
result = db.gorm.Save(&withdrawal)
return result.Error
}
func (db *bridgeTransfersDB) MarkFinalizedL2BridgeWithdrawalEvent(guid, finalizedL1EventGuid uuid.UUID) error {
var withdrawal L2BridgeWithdrawal
result := db.gorm.Where(&L2BridgeWithdrawal{GUID: guid}).Take(&withdrawal)
if result.Error != nil {
return result.Error
}
if withdrawal.ProvenL1EventGUID == nil {
return fmt.Errorf("withdrawal %s marked finalized prior to being proven", guid)
}
withdrawal.FinalizedL1EventGUID = &finalizedL1EventGuid
result = db.gorm.Save(&withdrawal)
return result.Error
}
...@@ -13,6 +13,7 @@ type DB struct { ...@@ -13,6 +13,7 @@ type DB struct {
Blocks BlocksDB Blocks BlocksDB
ContractEvents ContractEventsDB ContractEvents ContractEventsDB
BridgeTransfers BridgeTransfersDB BridgeTransfers BridgeTransfersDB
BridgeTransactions BridgeTransactionsDB
} }
func NewDB(dsn string) (*DB, error) { func NewDB(dsn string) (*DB, error) {
...@@ -34,6 +35,7 @@ func NewDB(dsn string) (*DB, error) { ...@@ -34,6 +35,7 @@ func NewDB(dsn string) (*DB, error) {
gorm: gorm, gorm: gorm,
Blocks: newBlocksDB(gorm), Blocks: newBlocksDB(gorm),
ContractEvents: newContractEventsDB(gorm), ContractEvents: newContractEventsDB(gorm),
BridgeTransactions: newBridgeTransactionsDB(gorm),
BridgeTransfers: newBridgeTransfersDB(gorm), BridgeTransfers: newBridgeTransfersDB(gorm),
} }
...@@ -62,6 +64,7 @@ func dbFromGormTx(tx *gorm.DB) *DB { ...@@ -62,6 +64,7 @@ func dbFromGormTx(tx *gorm.DB) *DB {
gorm: tx, gorm: tx,
Blocks: newBlocksDB(tx), Blocks: newBlocksDB(tx),
ContractEvents: newContractEventsDB(tx), ContractEvents: newContractEventsDB(tx),
BridgeTransactions: newBridgeTransactionsDB(tx),
BridgeTransfers: newBridgeTransfersDB(tx), BridgeTransfers: newBridgeTransfersDB(tx),
} }
} }
package e2e_tests
import (
"context"
"math/big"
"testing"
"time"
e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
func TestE2EBridgeTransactionsOptimismPortalDeposits(t *testing.T) {
testSuite := createE2ETestSuite(t)
optimismPortal, err := bindings.NewOptimismPortal(testSuite.OpCfg.L1Deployments.OptimismPortalProxy, testSuite.L1Client)
require.NoError(t, err)
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
// attach 1 ETH to the deposit and random calldata
calldata := []byte{byte(1), byte(2), byte(3)}
l1Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L1ChainIDBig())
require.NoError(t, err)
l1Opts.Value = big.NewInt(params.Ether)
depositTx, err := optimismPortal.DepositTransaction(l1Opts, aliceAddr, l1Opts.Value, 100_000, false, calldata)
require.NoError(t, err)
depositReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
depositInfo, err := e2etest_utils.ParseDepositInfo(depositReceipt)
require.NoError(t, err)
depositL2TxHash := types.NewTx(depositInfo.DepositTx).Hash()
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
deposit, err := testSuite.DB.BridgeTransactions.L1TransactionDeposit(depositInfo.DepositTx.SourceHash)
require.NoError(t, err)
require.Equal(t, depositL2TxHash, deposit.L2TransactionHash)
require.Equal(t, big.NewInt(100_000), deposit.GasLimit.Int)
require.Equal(t, big.NewInt(params.Ether), deposit.Tx.Amount.Int)
require.Equal(t, aliceAddr, deposit.Tx.FromAddress)
require.Equal(t, aliceAddr, deposit.Tx.ToAddress)
require.ElementsMatch(t, calldata, deposit.Tx.Data)
require.Equal(t, depositInfo.Version.Uint64(), deposit.Version.Int.Uint64())
require.ElementsMatch(t, depositInfo.OpaqueData, deposit.OpaqueData)
event, err := testSuite.DB.ContractEvents.L1ContractEvent(deposit.InitiatedL1EventGUID)
require.NoError(t, err)
require.NotNil(t, event)
require.Equal(t, event.TransactionHash, depositTx.Hash())
// NOTE: The indexer does not track deposit inclusion as it's apart of the block derivation process.
// If this changes, we'd like to test for this here.
}
func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
testSuite := createE2ETestSuite(t)
optimismPortal, err := bindings.NewOptimismPortal(testSuite.OpCfg.L1Deployments.OptimismPortalProxy, testSuite.L1Client)
require.NoError(t, err)
l2ToL1MessagePasser, err := bindings.NewL2ToL1MessagePasser(predeploys.L2ToL1MessagePasserAddr, testSuite.L2Client)
require.NoError(t, err)
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
// attach 1 ETH to the withdrawal and random calldata
calldata := []byte{byte(1), byte(2), byte(3)}
l2Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L2ChainIDBig())
require.NoError(t, err)
l2Opts.Value = big.NewInt(params.Ether)
// Ensure L1 has enough funds for the withdrawal by depositing an equal amount into the OptimismPortal
l1Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L1ChainIDBig())
require.NoError(t, err)
l1Opts.Value = l2Opts.Value
depositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
_, err = utils.WaitReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
withdrawTx, err := l2ToL1MessagePasser.InitiateWithdrawal(l2Opts, aliceAddr, big.NewInt(100_000), calldata)
require.NoError(t, err)
withdrawReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L2Client, withdrawTx.Hash())
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil
}))
msgPassed, err := withdrawals.ParseMessagePassed(withdrawReceipt)
require.NoError(t, err)
withdrawalHash, err := withdrawals.WithdrawalHash(msgPassed)
require.NoError(t, err)
withdraw, err := testSuite.DB.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash)
require.NoError(t, err)
require.Equal(t, msgPassed.Nonce.Uint64(), withdraw.Nonce.Int.Uint64())
require.Equal(t, big.NewInt(100_000), withdraw.GasLimit.Int)
require.Equal(t, big.NewInt(params.Ether), withdraw.Tx.Amount.Int)
require.Equal(t, aliceAddr, withdraw.Tx.FromAddress)
require.Equal(t, aliceAddr, withdraw.Tx.ToAddress)
require.ElementsMatch(t, calldata, withdraw.Tx.Data)
event, err := testSuite.DB.ContractEvents.L2ContractEvent(withdraw.InitiatedL2EventGUID)
require.NoError(t, err)
require.NotNil(t, event)
require.Equal(t, event.TransactionHash, withdrawTx.Hash())
// Test Withdrawal Proven
require.Nil(t, withdraw.ProvenL1EventGUID)
require.Nil(t, withdraw.FinalizedL1EventGUID)
withdrawParams, proveReceipt := op_e2e.ProveWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= proveReceipt.BlockNumber.Uint64(), nil
}))
withdraw, err = testSuite.DB.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash)
require.NoError(t, err)
require.NotNil(t, withdraw.ProvenL1EventGUID)
proveEvent, err := testSuite.DB.ContractEvents.L1ContractEvent(*withdraw.ProvenL1EventGUID)
require.NoError(t, err)
require.NotNil(t, event)
require.Equal(t, proveEvent.TransactionHash, proveReceipt.TxHash)
// Test Withdrawal Finalized
require.Nil(t, withdraw.FinalizedL1EventGUID)
finalizeReceipt := op_e2e.FinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpCfg.Secrets.Alice, proveReceipt, withdrawParams)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
withdraw, err = testSuite.DB.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash)
require.NoError(t, err)
require.NotNil(t, withdraw.FinalizedL1EventGUID)
finalizedEvent, err := testSuite.DB.ContractEvents.L1ContractEvent(*withdraw.FinalizedL1EventGUID)
require.NoError(t, err)
require.NotNil(t, event)
require.Equal(t, finalizedEvent.TransactionHash, finalizeReceipt.TxHash)
}
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"time" "time"
e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils" e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils"
"github.com/ethereum-optimism/optimism/indexer/processor"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-node/withdrawals" "github.com/ethereum-optimism/optimism/op-node/withdrawals"
...@@ -33,10 +34,6 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) { ...@@ -33,10 +34,6 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
l1Opts.Value = big.NewInt(params.Ether) l1Opts.Value = big.NewInt(params.Ether)
// Pause the L2Processor so that we can test for finalization separately. A pause is
// required since deposit inclusion is apart of the L2 block derivation process
testSuite.Indexer.L2Processor.PauseForTest()
// (1) Test Deposit Initiation // (1) Test Deposit Initiation
depositTx, err := l1StandardBridge.DepositETH(l1Opts, 200_000, []byte{byte(1)}) depositTx, err := l1StandardBridge.DepositETH(l1Opts, 200_000, []byte{byte(1)})
require.NoError(t, err) require.NoError(t, err)
...@@ -56,9 +53,10 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) { ...@@ -56,9 +53,10 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, aliceDeposits, 1) require.Len(t, aliceDeposits, 1)
require.Equal(t, depositTx.Hash(), aliceDeposits[0].L1TransactionHash) require.Equal(t, depositTx.Hash(), aliceDeposits[0].L1TransactionHash)
require.Empty(t, aliceDeposits[0].FinalizedL2TransactionHash) require.Equal(t, types.NewTx(depositInfo.DepositTx).Hash(), aliceDeposits[0].L2TransactionHash)
deposit := aliceDeposits[0].L1BridgeDeposit deposit := aliceDeposits[0].L1BridgeDeposit
require.Equal(t, depositInfo.DepositTx.SourceHash, deposit.TransactionSourceHash)
require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.L1TokenAddress) require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.L1TokenAddress)
require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.L2TokenAddress) require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.L2TokenAddress)
require.Equal(t, big.NewInt(params.Ether), deposit.Tx.Amount.Int) require.Equal(t, big.NewInt(params.Ether), deposit.Tx.Amount.Int)
...@@ -66,22 +64,61 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) { ...@@ -66,22 +64,61 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
require.Equal(t, aliceAddr, deposit.Tx.ToAddress) require.Equal(t, aliceAddr, deposit.Tx.ToAddress)
require.Equal(t, byte(1), deposit.Tx.Data[0]) require.Equal(t, byte(1), deposit.Tx.Data[0])
// StandardBridge flows through the messenger. We remove the first two significant
// bytes of the nonce dedicated to the version. nonce == 0 (first message)
require.NotNil(t, deposit.CrossDomainMessengerNonce)
_, nonce := processor.DecodeVersionedNonce(deposit.CrossDomainMessengerNonce.Int)
require.Zero(t, nonce.Uint64())
// (2) Test Deposit Finalization // (2) Test Deposit Finalization
require.Nil(t, deposit.FinalizedL2EventGUID) // Nothing to do as we rely on the derivation process to include the deposit
testSuite.Indexer.L2Processor.ResumeForTest() }
func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) {
testSuite := createE2ETestSuite(t)
optimismPortal, err := bindings.NewOptimismPortal(testSuite.OpCfg.L1Deployments.OptimismPortalProxy, testSuite.L1Client)
require.NoError(t, err)
// 1 ETH transfer
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
l1Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L1ChainIDBig())
require.NoError(t, err)
l1Opts.Value = big.NewInt(params.Ether)
// (1) Test Deposit Initiation
portalDepositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
portalDepositReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L1Client, portalDepositTx.Hash())
require.NoError(t, err)
// wait for the l2 processor to catch this deposit in the derivation process depositInfo, err := e2etest_utils.ParseDepositInfo(portalDepositReceipt)
depositReceipt, err = utils.WaitReceiptOK(context.Background(), testSuite.L2Client, types.NewTx(depositInfo.DepositTx).Hash())
require.NoError(t, err) require.NoError(t, err)
depositL2TxHash := types.NewTx(depositInfo.DepositTx).Hash()
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader() l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil return l1Header != nil && l1Header.Number.Uint64() >= portalDepositReceipt.BlockNumber.Uint64(), nil
})) }))
aliceDeposits, err = testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr) aliceDeposits, err := testSuite.DB.BridgeTransfers.L1BridgeDepositsByAddress(aliceAddr)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, aliceDeposits[0].L1BridgeDeposit.FinalizedL2EventGUID) require.Equal(t, portalDepositTx.Hash(), aliceDeposits[0].L1TransactionHash)
require.Equal(t, types.NewTx(depositInfo.DepositTx).Hash(), aliceDeposits[0].FinalizedL2TransactionHash) require.Equal(t, depositL2TxHash, aliceDeposits[0].L2TransactionHash)
deposit := aliceDeposits[0].L1BridgeDeposit
require.Equal(t, depositInfo.DepositTx.SourceHash, deposit.TransactionSourceHash)
require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.L1TokenAddress)
require.Equal(t, predeploys.LegacyERC20ETHAddr, deposit.TokenPair.L2TokenAddress)
require.Equal(t, big.NewInt(params.Ether), deposit.Tx.Amount.Int)
require.Equal(t, aliceAddr, deposit.Tx.FromAddress)
require.Equal(t, aliceAddr, deposit.Tx.ToAddress)
require.Len(t, deposit.Tx.Data, 0)
// deposit was not sent through the cross domain messenger
require.Nil(t, deposit.CrossDomainMessengerNonce)
} }
func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) { func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
...@@ -130,7 +167,7 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) { ...@@ -130,7 +167,7 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
withdrawal := aliceWithdrawals[0].L2BridgeWithdrawal withdrawal := aliceWithdrawals[0].L2BridgeWithdrawal
require.Equal(t, withdrawalHash, withdrawal.WithdrawalHash) require.Equal(t, withdrawalHash, withdrawal.TransactionWithdrawalHash)
require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.L1TokenAddress) require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.L1TokenAddress)
require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.L2TokenAddress) require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.L2TokenAddress)
require.Equal(t, big.NewInt(params.Ether), withdrawal.Tx.Amount.Int) require.Equal(t, big.NewInt(params.Ether), withdrawal.Tx.Amount.Int)
...@@ -138,9 +175,13 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) { ...@@ -138,9 +175,13 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
require.Equal(t, aliceAddr, withdrawal.Tx.ToAddress) require.Equal(t, aliceAddr, withdrawal.Tx.ToAddress)
require.Equal(t, byte(1), withdrawal.Tx.Data[0]) require.Equal(t, byte(1), withdrawal.Tx.Data[0])
// StandardBridge flows through the messenger. We remove the first two
// bytes of the nonce dedicated to the version. nonce == 0 (first message)
require.NotNil(t, withdrawal.CrossDomainMessengerNonce)
_, nonce := processor.DecodeVersionedNonce(withdrawal.CrossDomainMessengerNonce.Int)
require.Zero(t, nonce.Uint64())
// (2) Test Withdrawal Proven/Finalized. Test the sql join queries to populate the right transaction // (2) Test Withdrawal Proven/Finalized. Test the sql join queries to populate the right transaction
require.Nil(t, withdrawal.ProvenL1EventGUID)
require.Nil(t, withdrawal.FinalizedL1EventGUID)
require.Empty(t, aliceWithdrawals[0].ProvenL1TransactionHash) require.Empty(t, aliceWithdrawals[0].ProvenL1TransactionHash)
require.Empty(t, aliceWithdrawals[0].FinalizedL1TransactionHash) require.Empty(t, aliceWithdrawals[0].FinalizedL1TransactionHash)
...@@ -153,8 +194,79 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) { ...@@ -153,8 +194,79 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr) aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, aliceWithdrawals[0].L2BridgeWithdrawal.ProvenL1EventGUID) require.Equal(t, proveReceipt.TxHash, aliceWithdrawals[0].ProvenL1TransactionHash)
require.NotNil(t, aliceWithdrawals[0].L2BridgeWithdrawal.FinalizedL1EventGUID) require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals[0].FinalizedL1TransactionHash)
}
func TestE2EBridgeTransfersL2ToL1MessagePasserReceive(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)
// 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)
// Ensure L1 has enough funds for the withdrawal by depositing an equal amount into the OptimismPortal
l1Opts, err := bind.NewKeyedTransactorWithChainID(testSuite.OpCfg.Secrets.Alice, testSuite.OpCfg.L1ChainIDBig())
require.NoError(t, err)
l1Opts.Value = l2Opts.Value
depositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
_, err = utils.WaitReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
// (1) Test Withdrawal Initiation
l2ToL1MessagePasserWithdrawTx, err := l2ToL1MessagePasser.Receive(l2Opts)
require.NoError(t, err)
l2ToL1WithdrawReceipt, err := utils.WaitReceiptOK(context.Background(), testSuite.L2Client, l2ToL1MessagePasserWithdrawTx.Hash())
require.NoError(t, err)
// wait for processor catchup
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader()
return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err := testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr)
require.NoError(t, err)
require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash(), aliceWithdrawals[0].L2TransactionHash)
msgPassed, err := withdrawals.ParseMessagePassed(l2ToL1WithdrawReceipt)
require.NoError(t, err)
withdrawalHash, err := withdrawals.WithdrawalHash(msgPassed)
require.NoError(t, err)
withdrawal := aliceWithdrawals[0].L2BridgeWithdrawal
require.Equal(t, withdrawalHash, withdrawal.TransactionWithdrawalHash)
require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.L1TokenAddress)
require.Equal(t, predeploys.LegacyERC20ETHAddr, withdrawal.TokenPair.L2TokenAddress)
require.Equal(t, big.NewInt(params.Ether), withdrawal.Tx.Amount.Int)
require.Equal(t, aliceAddr, withdrawal.Tx.FromAddress)
require.Equal(t, aliceAddr, withdrawal.Tx.ToAddress)
require.Len(t, withdrawal.Tx.Data, 0)
// withdrawal was not sent through the cross domain messenger
require.Nil(t, withdrawal.CrossDomainMessengerNonce)
// (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)
// wait for processor catchup
proveReceipt, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, l2ToL1WithdrawReceipt)
require.NoError(t, utils.WaitFor(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader()
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
aliceWithdrawals, err = testSuite.DB.BridgeTransfers.L2BridgeWithdrawalsByAddress(aliceAddr)
require.NoError(t, err)
require.Equal(t, proveReceipt.TxHash, aliceWithdrawals[0].ProvenL1TransactionHash) require.Equal(t, proveReceipt.TxHash, aliceWithdrawals[0].ProvenL1TransactionHash)
require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals[0].FinalizedL1TransactionHash) require.Equal(t, finalizeReceipt.TxHash, aliceWithdrawals[0].FinalizedL1TransactionHash)
} }
CREATE DOMAIN UINT256 AS NUMERIC NOT NULL CREATE DOMAIN UINT256 AS NUMERIC
CHECK (VALUE >= 0 AND VALUE < 2^256 and SCALE(VALUE) = 0); CHECK (VALUE >= 0 AND VALUE < 2^256 and SCALE(VALUE) = 0);
/** /**
...@@ -9,7 +9,7 @@ CREATE DOMAIN UINT256 AS NUMERIC NOT NULL ...@@ -9,7 +9,7 @@ CREATE DOMAIN UINT256 AS NUMERIC NOT NULL
CREATE TABLE IF NOT EXISTS l1_block_headers ( CREATE TABLE IF NOT EXISTS l1_block_headers (
hash VARCHAR NOT NULL PRIMARY KEY, hash VARCHAR NOT NULL PRIMARY KEY,
parent_hash VARCHAR NOT NULL, parent_hash VARCHAR NOT NULL,
number UINT256, number UINT256 NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0) timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
...@@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS l2_block_headers ( ...@@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS l2_block_headers (
-- Block header -- Block header
hash VARCHAR NOT NULL PRIMARY KEY, hash VARCHAR NOT NULL PRIMARY KEY,
parent_hash VARCHAR NOT NULL, parent_hash VARCHAR NOT NULL,
number UINT256, number UINT256 NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0) timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
...@@ -57,8 +57,8 @@ CREATE TABLE IF NOT EXISTS legacy_state_batches ( ...@@ -57,8 +57,8 @@ CREATE TABLE IF NOT EXISTS legacy_state_batches (
CREATE TABLE IF NOT EXISTS output_proposals ( CREATE TABLE IF NOT EXISTS output_proposals (
output_root VARCHAR NOT NULL PRIMARY KEY, output_root VARCHAR NOT NULL PRIMARY KEY,
l2_output_index UINT256, l2_output_index UINT256 NOT NULL,
l2_block_number UINT256, l2_block_number UINT256 NOT NULL,
l1_contract_event_guid VARCHAR REFERENCES l1_contract_events(guid) l1_contract_event_guid VARCHAR REFERENCES l1_contract_events(guid)
); );
...@@ -67,47 +67,76 @@ CREATE TABLE IF NOT EXISTS output_proposals ( ...@@ -67,47 +67,76 @@ CREATE TABLE IF NOT EXISTS output_proposals (
* BRIDGING DATA * BRIDGING DATA
*/ */
CREATE TABLE IF NOT EXISTS l1_bridge_deposits ( -- OptimismPortal/L2ToL1MessagePasser
guid VARCHAR PRIMARY KEY NOT NULL, CREATE TABLE IF NOT EXISTS l1_transaction_deposits (
source_hash VARCHAR NOT NULL PRIMARY KEY,
l2_transaction_hash VARCHAR NOT NULL,
-- Event causing the deposit
initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid), initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid),
cross_domain_messenger_nonce UINT256 UNIQUE,
-- Finalization marker for the deposit -- OptimismPortal specific
finalized_l2_event_guid VARCHAR REFERENCES l2_contract_events(guid), version UINT256 NOT NULL,
opaque_data VARCHAR NOT NULL,
-- Deposit information (do we need indexes on from/to?) -- transaction data
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL, to_address VARCHAR NOT NULL,
l1_token_address VARCHAR NOT NULL, amount UINT256 NOT NULL,
l2_token_address VARCHAR NOT NULL, gas_limit UINT256 NOT NULL,
amount UINT256,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0) timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
CREATE TABLE IF NOT EXISTS l2_transaction_withdrawals (
withdrawal_hash VARCHAR NOT NULL PRIMARY KEY,
CREATE TABLE IF NOT EXISTS l2_bridge_withdrawals (
guid VARCHAR PRIMARY KEY NOT NULL,
-- Event causing this withdrawal
initiated_l2_event_guid VARCHAR NOT NULL REFERENCES l2_contract_events(guid), initiated_l2_event_guid VARCHAR NOT NULL REFERENCES l2_contract_events(guid),
cross_domain_messenger_nonce UINT256 UNIQUE,
-- Multistep (bedrock) process of a withdrawal -- Multistep (bedrock) process of a withdrawal
withdrawal_hash VARCHAR NOT NULL,
proven_l1_event_guid VARCHAR REFERENCES l1_contract_events(guid), proven_l1_event_guid VARCHAR REFERENCES l1_contract_events(guid),
-- Finalization marker (legacy & bedrock)
finalized_l1_event_guid VARCHAR REFERENCES l1_contract_events(guid), finalized_l1_event_guid VARCHAR REFERENCES l1_contract_events(guid),
-- Withdrawal information (do we need indexes on from/to?) -- L2ToL1MessagePasser specific
nonce UINT256 UNIQUE,
-- transaction data
from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL,
amount UINT256 NOT NULL,
gas_limit UINT256 NOT NULL,
data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0)
);
-- 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_messenger_nonce to be NULL-able to account
-- for scenarios where ETH is simply sent to the OptimismPortal contract
cross_domain_messenger_nonce UINT256 UNIQUE,
-- Deposit information
from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL,
l1_token_address VARCHAR NOT NULL,
l2_token_address VARCHAR NOT NULL,
amount UINT256 NOT NULL,
data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0)
);
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_messenger_nonce to be NULL-able to account for
-- scenarios where ETH is simply sent to the L2ToL1MessagePasser contract
cross_domain_messenger_nonce UINT256 UNIQUE,
-- Withdrawal information
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL, to_address VARCHAR NOT NULL,
l1_token_address VARCHAR NOT NULL, l1_token_address VARCHAR NOT NULL,
l2_token_address VARCHAR NOT NULL, l2_token_address VARCHAR NOT NULL,
amount UINT256, amount UINT256 NOT NULL,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0) timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
package processor
import (
"encoding/binary"
"math/big"
)
// DecodeVersionNonce is an re-implementation of Encoding.sol#decodeVersionedNonce.
// If the nonce is greater than 32 bytes (solidity uint256), bytes [32:] are ignored
func DecodeVersionedNonce(nonce *big.Int) (uint16, *big.Int) {
nonceBytes := nonce.Bytes()
nonceByteLen := len(nonceBytes)
if nonceByteLen < 30 {
// version is 0x0000
return 0, nonce
} else if nonceByteLen == 31 {
// version is 0x00[01..ff]
return uint16(nonceBytes[0]), new(big.Int).SetBytes(nonceBytes[1:])
} else {
// fully specified
version := binary.BigEndian.Uint16(nonceBytes[:2])
return version, new(big.Int).SetBytes(nonceBytes[2:])
}
}
package processor package processor
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"math/big"
"reflect" "reflect"
"github.com/google/uuid"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
legacy_bindings "github.com/ethereum-optimism/optimism/op-bindings/legacy-bindings" legacy_bindings "github.com/ethereum-optimism/optimism/op-bindings/legacy-bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
...@@ -232,8 +234,14 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1 ...@@ -232,8 +234,14 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1
} }
} }
// forward along contract events to the bridge processor // forward along contract events to bridge txs processor
err = l1BridgeProcessContractEvents(processLog, db, ethClient, processedContractEvents, l1Contracts) err = l1ProcessContractEventsBridgeTransactions(processLog, db, l1Contracts, processedContractEvents)
if err != nil {
return err
}
// forward along contract events to standard bridge processor
err = l1ProcessContractEventsStandardBridge(processLog, db, ethClient, processedContractEvents)
if err != nil { if err != nil {
return err return err
} }
...@@ -246,120 +254,200 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1 ...@@ -246,120 +254,200 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1
} }
} }
func l1BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethClient node.EthClient, events *ProcessedContractEvents, l1Contracts L1Contracts) error { func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *database.DB, l1Contracts L1Contracts, events *ProcessedContractEvents) error {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient()) // (1) Process New Deposits
portalDeposits, err := OptimismPortalTransactionDepositEvents(events)
// Process New Deposits
initiatedDepositEvents, err := StandardBridgeInitiatedEvents(events)
if err != nil { if err != nil {
return err return err
} }
deposits := make([]*database.L1BridgeDeposit, len(initiatedDepositEvents)) ethDeposits := []*database.L1BridgeDeposit{}
for i, initiatedBridgeEvent := range initiatedDepositEvents { transactionDeposits := make([]*database.L1TransactionDeposit, len(portalDeposits))
deposits[i] = &database.L1BridgeDeposit{ for i, depositEvent := range portalDeposits {
GUID: uuid.New(), depositTx := depositEvent.DepositTx
InitiatedL1EventGUID: initiatedBridgeEvent.RawEvent.GUID, transactionDeposits[i] = &database.L1TransactionDeposit{
CrossDomainMessengerNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce}, SourceHash: depositTx.SourceHash,
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken}, L2TransactionHash: types.NewTx(depositTx).Hash(),
InitiatedL1EventGUID: depositEvent.RawEvent.GUID,
Version: database.U256{Int: depositEvent.Version},
OpaqueData: depositEvent.OpaqueData,
GasLimit: database.U256{Int: new(big.Int).SetUint64(depositTx.Gas)},
Tx: database.Transaction{ Tx: database.Transaction{
FromAddress: initiatedBridgeEvent.From, FromAddress: depositTx.From,
ToAddress: initiatedBridgeEvent.To, ToAddress: depositTx.From,
Amount: database.U256{Int: initiatedBridgeEvent.Amount}, Amount: database.U256{Int: depositTx.Value},
Data: initiatedBridgeEvent.ExtraData, Data: depositTx.Data,
Timestamp: initiatedBridgeEvent.RawEvent.Timestamp, Timestamp: depositEvent.RawEvent.Timestamp,
}, },
} }
// catch ETH transfers to the portal contract.
if len(depositTx.Data) == 0 && depositTx.Value.BitLen() > 0 {
ethDeposits = append(ethDeposits, &database.L1BridgeDeposit{
TransactionSourceHash: depositTx.SourceHash,
Tx: transactionDeposits[i].Tx,
TokenPair: database.TokenPair{
L1TokenAddress: predeploys.LegacyERC20ETHAddr,
L2TokenAddress: predeploys.LegacyERC20ETHAddr,
},
})
}
} }
if len(deposits) > 0 { if len(transactionDeposits) > 0 {
processLog.Info("detected L1StandardBridge deposits", "num", len(deposits)) processLog.Info("detected transaction deposits", "size", len(transactionDeposits))
err := db.BridgeTransfers.StoreL1BridgeDeposits(deposits) err := db.BridgeTransactions.StoreL1TransactionDeposits(transactionDeposits)
if err != nil {
return err
}
if len(ethDeposits) > 0 {
processLog.Info("detected portal ETH transfers", "size", len(ethDeposits))
err := db.BridgeTransfers.StoreL1BridgeDeposits(ethDeposits)
if err != nil { if err != nil {
return err return err
} }
} }
}
// Prove L2 Withdrawals // (2) Process Proven Withdrawals
provenWithdrawalEvents, err := OptimismPortalWithdrawalProvenEvents(events) provenWithdrawals, err := OptimismPortalWithdrawalProvenEvents(events)
if err != nil { if err != nil {
return err return err
} }
latestL2Header, err := db.Blocks.LatestL2BlockHeader() latestL2Header, err := db.Blocks.LatestL2BlockHeader()
if err != nil { if err != nil {
return err return nil
} else if len(provenWithdrawalEvents) > 0 && latestL2Header == nil { } else if len(provenWithdrawals) > 0 && latestL2Header == nil {
return errors.New("no indexed L2 state to process any proven L1 transactions") return errors.New("no indexed L2 headers to prove withdrawals. waiting for L2Processor to catch up")
} }
numProvenWithdrawals := 0 for _, provenWithdrawal := range provenWithdrawals {
for _, provenWithdrawalEvent := range provenWithdrawalEvents { withdrawalHash := provenWithdrawal.WithdrawalHash
withdrawalHash := provenWithdrawalEvent.WithdrawalHash withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash)
withdrawal, err := db.BridgeTransfers.L2BridgeWithdrawalByWithdrawalHash(withdrawalHash)
if err != nil { if err != nil {
return err return err
} else if withdrawal == nil { } else if withdrawal == nil {
// NOTE: This needs to be updated to identify if this CrossDomainMessenger message is a StandardBridge message. This // We need to ensure we are in a caught up state before claiming a missing event. Since L2 timestamps
// will be easier to do once we index cross domain messages and track its lifecyle separately // are derived from L1, we can simply compare the timestamp of this event with the latest L2 header.
if provenWithdrawalEvent.From != common.HexToAddress("0x4200000000000000000000000000000000000007") || provenWithdrawalEvent.To != l1Contracts.L1CrossDomainMessenger { if provenWithdrawal.RawEvent.Timestamp > latestL2Header.Timestamp {
// non-bridge withdrawal processLog.Warn("behind on indexed L2 withdrawals")
continue
}
// Check if the L2Processor is behind or really has missed an event. Since L2 timestamps
// are derived from L1, we can simply compare timestamps
if provenWithdrawalEvent.RawEvent.Timestamp > latestL2Header.Timestamp {
processLog.Warn("behind on indexed L2StandardBridge withdrawals")
return errors.New("waiting for L2Processor to catch up") return errors.New("waiting for L2Processor to catch up")
} else { } else {
processLog.Crit("missing indexed L2StandardBridge withdrawal for this proven event") processLog.Crit("withdrawal missing!", "hash", withdrawalHash)
return errors.New("missing withdrawal message") return errors.New("withdrawal missing!")
} }
} }
err = db.BridgeTransfers.MarkProvenL2BridgeWithdrawalEvent(withdrawal.GUID, provenWithdrawalEvent.RawEvent.GUID) err = db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(withdrawalHash, provenWithdrawal.RawEvent.GUID)
if err != nil { if err != nil {
return err return err
} }
}
numProvenWithdrawals++ if len(provenWithdrawals) > 0 {
processLog.Info("proven transaction withdrawals", "size", len(provenWithdrawals))
} }
if numProvenWithdrawals > 0 { // (2) Process Withdrawal Finalization
processLog.Info("proven L2StandardBridge withdrawals", "size", numProvenWithdrawals) finalizedWithdrawals, err := OptimismPortalWithdrawalFinalizedEvents(events)
if err != nil {
return err
} }
// Finalize Pending Withdrawals for _, finalizedWithdrawal := range finalizedWithdrawals {
finalizedWithdrawalEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events) withdrawalHash := finalizedWithdrawal.WithdrawalHash
withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash)
if err != nil { if err != nil {
return err return err
} else if withdrawal == nil {
// since withdrawals must be proven first, we don't have to check on the L2Processor
processLog.Crit("withdrawal missing!", "hash", withdrawalHash)
return errors.New("withdrawal missing!")
} }
for _, finalizedBridgeEvent := range finalizedWithdrawalEvents { err = db.BridgeTransactions.MarkL2TransactionWithdrawalFinalizedEvent(withdrawalHash, finalizedWithdrawal.RawEvent.GUID)
nonce := finalizedBridgeEvent.CrossDomainMessengerNonce
withdrawal, err := db.BridgeTransfers.L2BridgeWithdrawalByCrossDomainMessengerNonce(nonce)
if err != nil { if err != nil {
processLog.Error("error querying associated withdrawal messsage using nonce", "cross_domain_messenger_nonce", nonce)
return err return err
} }
}
// Since we have to prove the event on-chain first, we don't need to check if the processor is behind if len(finalizedWithdrawals) > 0 {
// We're definitely in an error state if we cannot find the withdrawal when parsing this event processLog.Info("finalized transaction withdrawals", "size", len(finalizedWithdrawals))
if withdrawal == nil {
processLog.Crit("missing indexed withdrawal for this finalization event")
return errors.New("missing withdrawal message")
} }
err = db.BridgeTransfers.MarkFinalizedL2BridgeWithdrawalEvent(withdrawal.GUID, finalizedBridgeEvent.RawEvent.GUID) // a-ok
return nil
}
func l1ProcessContractEventsStandardBridge(processLog log.Logger, db *database.DB, ethClient node.EthClient, events *ProcessedContractEvents) error {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())
// (1) Process New Deposits
initiatedDepositEvents, err := StandardBridgeInitiatedEvents(events)
if err != nil {
return err
}
deposits := make([]*database.L1BridgeDeposit, len(initiatedDepositEvents))
for i, initiatedBridgeEvent := range initiatedDepositEvents {
log := events.eventLog[initiatedBridgeEvent.RawEvent.GUID]
// extract the deposit hash from the following TransactionDeposited event
transactionDepositedLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].GUID]
depositTx, err := derive.UnmarshalDepositLogEvent(transactionDepositedLog)
if err != nil {
return err
}
deposits[i] = &database.L1BridgeDeposit{
TransactionSourceHash: depositTx.SourceHash,
CrossDomainMessengerNonce: &database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
Tx: database.Transaction{
FromAddress: initiatedBridgeEvent.From,
ToAddress: initiatedBridgeEvent.To,
Amount: database.U256{Int: initiatedBridgeEvent.Amount},
Data: initiatedBridgeEvent.ExtraData,
Timestamp: initiatedBridgeEvent.RawEvent.Timestamp,
},
}
}
if len(deposits) > 0 {
processLog.Info("detected L1StandardBridge deposits", "size", len(deposits))
err := db.BridgeTransfers.StoreL1BridgeDeposits(deposits)
if err != nil {
return err
}
}
// (2) Process Finalized Withdrawals
// - We dont need do anything actionable on the database here as this is layered on top of the
// bridge transaction & messages that have a tracked lifecyle. We simply walk through and ensure
// that the corresponding initiated withdrawals exist and match as an integrity check
finalizedWithdrawalEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events)
if err != nil { if err != nil {
processLog.Error("error finalizing withdrawal", "err", err)
return err return err
} }
for _, finalizedWithdrawalEvent := range finalizedWithdrawalEvents {
withdrawal, err := db.BridgeTransfers.L2BridgeWithdrawalByCrossDomainMessengerNonce(finalizedWithdrawalEvent.CrossDomainMessengerNonce)
if err != nil {
return err
} else if withdrawal == nil {
processLog.Error("missing indexed L2StandardBridge withdrawal for finalization", "cross_domain_messenger_nonce", finalizedWithdrawalEvent.CrossDomainMessengerNonce)
return errors.New("missing indexed L2StandardBridge withdrawal for finalization event")
} }
if len(finalizedWithdrawalEvents) > 0 { // sanity check on the bridge fields
processLog.Info("finalized L2StandardBridge withdrawals", "num", len(finalizedWithdrawalEvents)) if finalizedWithdrawalEvent.From != withdrawal.Tx.FromAddress || finalizedWithdrawalEvent.To != withdrawal.Tx.ToAddress ||
finalizedWithdrawalEvent.Amount.Cmp(withdrawal.Tx.Amount.Int) != 0 || !bytes.Equal(finalizedWithdrawalEvent.ExtraData, withdrawal.Tx.Data) ||
finalizedWithdrawalEvent.LocalToken != withdrawal.TokenPair.L1TokenAddress || finalizedWithdrawalEvent.RemoteToken != withdrawal.TokenPair.L2TokenAddress {
processLog.Crit("bridge finalization fields mismatch with initiated fields!", "tx_withdrawal_hash", withdrawal.TransactionWithdrawalHash, "cross_domain_messenger_nonce", withdrawal.CrossDomainMessengerNonce.Int)
return errors.New("bridge tx mismatch!")
}
} }
// a-ok! // a-ok!
......
package processor package processor
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"reflect" "reflect"
...@@ -8,7 +9,7 @@ import ( ...@@ -8,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/google/uuid" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -155,8 +156,14 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -155,8 +156,14 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
return err return err
} }
// forward along contract events to the bridge processor // forward along contract events to bridge txs processor
err = l2BridgeProcessContractEvents(processLog, db, ethClient, processedContractEvents) err = l2ProcessContractEventsBridgeTransactions(processLog, db, processedContractEvents)
if err != nil {
return err
}
// forward along contract events to standard bridge processor
err = l2ProcessContractEventsStandardBridge(processLog, db, ethClient, processedContractEvents)
if err != nil { if err != nil {
return err return err
} }
...@@ -167,17 +174,73 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -167,17 +174,73 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
} }
} }
func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethClient node.EthClient, events *ProcessedContractEvents) error { func l2ProcessContractEventsBridgeTransactions(processLog log.Logger, db *database.DB, events *ProcessedContractEvents) error {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient()) // (1) Process New Withdrawals
messagesPassed, err := L2ToL1MessagePasserMessagesPassed(events)
if err != nil {
return err
}
ethWithdrawals := []*database.L2BridgeWithdrawal{}
transactionWithdrawals := make([]*database.L2TransactionWithdrawal, len(messagesPassed))
for i, withdrawalEvent := range messagesPassed {
transactionWithdrawals[i] = &database.L2TransactionWithdrawal{
WithdrawalHash: withdrawalEvent.WithdrawalHash,
InitiatedL2EventGUID: withdrawalEvent.RawEvent.GUID,
Nonce: database.U256{Int: withdrawalEvent.Nonce},
GasLimit: database.U256{Int: withdrawalEvent.GasLimit},
Tx: database.Transaction{
FromAddress: withdrawalEvent.Sender,
ToAddress: withdrawalEvent.Target,
Amount: database.U256{Int: withdrawalEvent.Value},
Data: withdrawalEvent.Data,
Timestamp: withdrawalEvent.RawEvent.Timestamp,
},
}
if len(withdrawalEvent.Data) == 0 && withdrawalEvent.Value.BitLen() > 0 {
ethWithdrawals = append(ethWithdrawals, &database.L2BridgeWithdrawal{
TransactionWithdrawalHash: withdrawalEvent.WithdrawalHash,
Tx: transactionWithdrawals[i].Tx,
TokenPair: database.TokenPair{
L1TokenAddress: predeploys.LegacyERC20ETHAddr,
L2TokenAddress: predeploys.LegacyERC20ETHAddr,
},
})
}
}
if len(transactionWithdrawals) > 0 {
processLog.Info("detected transaction withdrawals", "size", len(transactionWithdrawals))
db.BridgeTransactions.StoreL2TransactionWithdrawals(transactionWithdrawals)
l2ToL1MessagePasserABI, err := bindings.L2ToL1MessagePasserMetaData.GetAbi() if len(ethWithdrawals) > 0 {
processLog.Info("detected L2ToL1MessagePasser ETH transfers", "size", len(ethWithdrawals))
err := db.BridgeTransfers.StoreL2BridgeWithdrawals(ethWithdrawals)
if err != nil { if err != nil {
return err return err
} }
}
}
// (2) Process Deposit Finalization
// - Since L2 deposits are apart of the block derivation processes, we dont track finalization as it's too tricky
// to do so purely from the L2-side since there is not a way to easily identify deposit transations on L2 without walking
// the transaction list of every L2 epoch.
messagePassedEventAbi := l2ToL1MessagePasserABI.Events["MessagePassed"] // a-ok!
return nil
}
func l2ProcessContractEventsStandardBridge(processLog log.Logger, db *database.DB, ethClient node.EthClient, events *ProcessedContractEvents) error {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())
// Process New Withdrawals l2ToL1MessagePasserABI, err := bindings.NewL2ToL1MessagePasser(common.Address{}, nil)
if err != nil {
return err
}
// (1) Process New Withdrawals
initiatedWithdrawalEvents, err := StandardBridgeInitiatedEvents(events) initiatedWithdrawalEvents, err := StandardBridgeInitiatedEvents(events)
if err != nil { if err != nil {
return err return err
...@@ -187,19 +250,16 @@ func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl ...@@ -187,19 +250,16 @@ func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
for i, initiatedBridgeEvent := range initiatedWithdrawalEvents { for i, initiatedBridgeEvent := range initiatedWithdrawalEvents {
log := events.eventLog[initiatedBridgeEvent.RawEvent.GUID] log := events.eventLog[initiatedBridgeEvent.RawEvent.GUID]
// extract the withdrawal hash from the MessagePassed event // extract the withdrawal hash from the following MessagePassed event
var msgPassedData bindings.L2ToL1MessagePasserMessagePassed
msgPassedLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].GUID] msgPassedLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].GUID]
err := UnpackLog(&msgPassedData, msgPassedLog, messagePassedEventAbi.Name, l2ToL1MessagePasserABI) msgPassedEvent, err := l2ToL1MessagePasserABI.ParseMessagePassed(*msgPassedLog)
if err != nil { if err != nil {
return err return err
} }
withdrawals[i] = &database.L2BridgeWithdrawal{ withdrawals[i] = &database.L2BridgeWithdrawal{
GUID: uuid.New(), TransactionWithdrawalHash: msgPassedEvent.WithdrawalHash,
InitiatedL2EventGUID: initiatedBridgeEvent.RawEvent.GUID, CrossDomainMessengerNonce: &database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
CrossDomainMessengerNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
WithdrawalHash: msgPassedData.WithdrawalHash,
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken}, TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
Tx: database.Transaction{ Tx: database.Transaction{
FromAddress: initiatedBridgeEvent.From, FromAddress: initiatedBridgeEvent.From,
...@@ -219,49 +279,35 @@ func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl ...@@ -219,49 +279,35 @@ func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethCl
} }
} }
// Finalize Deposits // (2) Process Finalized Deposits
finalizationBridgeEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events) // - We dont need do anything actionable on the database here as this is layered on top of the
if err != nil { // bridge transaction & messages that have a tracked lifecyle. We simply walk through and ensure
return err // that the corresponding initiated deposits exist as an integrity check
}
latestL1Header, err := db.Blocks.LatestL1BlockHeader() finalizedDepositEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events)
if err != nil { if err != nil {
return err return err
} else if len(finalizationBridgeEvents) > 0 && latestL1Header == nil {
return errors.New("no indexed L1 state to process any L2 bridge finalizations")
} }
for _, finalizedBridgeEvent := range finalizationBridgeEvents { for _, finalizedDepositEvent := range finalizedDepositEvents {
nonce := finalizedBridgeEvent.CrossDomainMessengerNonce deposit, err := db.BridgeTransfers.L1BridgeDepositByCrossDomainMessengerNonce(finalizedDepositEvent.CrossDomainMessengerNonce)
deposit, err := db.BridgeTransfers.L1BridgeDepositByCrossDomainMessengerNonce(nonce)
if err != nil { if err != nil {
processLog.Error("error querying associated deposit messsage using nonce", "cross_domain_messenger_nonce", nonce)
return err return err
} else if deposit == nil { } else if deposit == nil {
// Check if the L1Processor is behind or really has missed an event. Since L2 timestamps // NOTE: We'll be indexing CrossDomainMessenger messages that'll ensure we're in a caught up state here
// are derived from L1, we can simply compare timestamps processLog.Error("missing indexed L1StandardBridge deposit on finalization", "cross_domain_messenger_nonce", finalizedDepositEvent.CrossDomainMessengerNonce)
if finalizedBridgeEvent.RawEvent.Timestamp > latestL1Header.Timestamp { return errors.New("missing indexed L1StandardBridge deposit on finalization")
processLog.Warn("behind on indexed L1StandardBridge deposits")
return errors.New("waiting for L1Processor to catch up")
} else {
processLog.Crit("missing indexed L1StandardBridge deposit for this finalization event")
return errors.New("missing deposit message")
}
} }
err = db.BridgeTransfers.MarkFinalizedL1BridgeDepositEvent(deposit.GUID, finalizedBridgeEvent.RawEvent.GUID) // sanity check on the bridge fields
if err != nil { if finalizedDepositEvent.From != deposit.Tx.FromAddress || finalizedDepositEvent.To != deposit.Tx.ToAddress ||
processLog.Error("error finalizing deposit", "err", err) finalizedDepositEvent.Amount.Cmp(deposit.Tx.Amount.Int) != 0 || !bytes.Equal(finalizedDepositEvent.ExtraData, deposit.Tx.Data) ||
return err finalizedDepositEvent.LocalToken != deposit.TokenPair.L1TokenAddress || finalizedDepositEvent.RemoteToken != deposit.TokenPair.L2TokenAddress {
processLog.Error("bridge finalization fields mismatch with initiated fields!", "tx_source_hash", deposit.TransactionSourceHash, "cross_domain_messenger_nonce", deposit.CrossDomainMessengerNonce.Int)
return errors.New("bridge tx mismatch")
} }
} }
if len(finalizationBridgeEvents) > 0 { // a-ok!
processLog.Info("finalized L1StandardBridge deposits", "size", len(finalizationBridgeEvents))
}
// a-ok
return nil return nil
} }
package processor
import (
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
)
type L2ToL1MessagePasserMessagePassed struct {
*bindings.L2ToL1MessagePasserMessagePassed
RawEvent *database.ContractEvent
}
func L2ToL1MessagePasserMessagesPassed(events *ProcessedContractEvents) ([]L2ToL1MessagePasserMessagePassed, error) {
l2ToL1MessagePasserAbi, err := bindings.L2ToL1MessagePasserMetaData.GetAbi()
if err != nil {
return nil, err
}
eventName := "MessagePassed"
processedMessagePassedEvents := events.eventsBySignature[l2ToL1MessagePasserAbi.Events[eventName].ID]
messagesPassed := make([]L2ToL1MessagePasserMessagePassed, len(processedMessagePassedEvents))
for i, messagePassedEvent := range processedMessagePassedEvents {
log := events.eventLog[messagePassedEvent.GUID]
var messagePassed bindings.L2ToL1MessagePasserMessagePassed
err := UnpackLog(&messagePassed, log, eventName, l2ToL1MessagePasserAbi)
if err != nil {
return nil, err
}
messagesPassed[i] = L2ToL1MessagePasserMessagePassed{&messagePassed, messagePassedEvent}
}
return messagesPassed, nil
}
...@@ -2,19 +2,32 @@ package processor ...@@ -2,19 +2,32 @@ package processor
import ( import (
"context" "context"
"errors"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
) )
type OptimismPortalTransactionDepositEvent struct {
*bindings.OptimismPortalTransactionDeposited
DepositTx *types.DepositTx
RawEvent *database.ContractEvent
}
type OptimismPortalWithdrawalProvenEvent struct { type OptimismPortalWithdrawalProvenEvent struct {
*bindings.OptimismPortalWithdrawalProven *bindings.OptimismPortalWithdrawalProven
RawEvent *database.ContractEvent
}
type OptimismPortalWithdrawalFinalizedEvent struct {
*bindings.OptimismPortalWithdrawalFinalized
RawEvent *database.ContractEvent RawEvent *database.ContractEvent
} }
...@@ -24,6 +37,39 @@ type OptimismPortalProvenWithdrawal struct { ...@@ -24,6 +37,39 @@ type OptimismPortalProvenWithdrawal struct {
L2OutputIndex *big.Int L2OutputIndex *big.Int
} }
func OptimismPortalTransactionDepositEvents(events *ProcessedContractEvents) ([]OptimismPortalTransactionDepositEvent, error) {
optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return nil, err
}
eventName := "TransactionDeposited"
if optimismPortalAbi.Events[eventName].ID != derive.DepositEventABIHash {
return nil, errors.New("op-node deposit event abi hash & optimism portal tx deposit mismatch")
}
processedTxDepositedEvents := events.eventsBySignature[derive.DepositEventABIHash]
txDeposits := make([]OptimismPortalTransactionDepositEvent, len(processedTxDepositedEvents))
for i, txDepositEvent := range processedTxDepositedEvents {
log := events.eventLog[txDepositEvent.GUID]
depositTx, err := derive.UnmarshalDepositLogEvent(log)
if err != nil {
return nil, err
}
var txDeposit bindings.OptimismPortalTransactionDeposited
err = UnpackLog(&txDeposit, log, eventName, optimismPortalAbi)
if err != nil {
return nil, err
}
txDeposits[i] = OptimismPortalTransactionDepositEvent{&txDeposit, depositTx, txDepositEvent}
}
return txDeposits, nil
}
func OptimismPortalWithdrawalProvenEvents(events *ProcessedContractEvents) ([]OptimismPortalWithdrawalProvenEvent, error) { func OptimismPortalWithdrawalProvenEvents(events *ProcessedContractEvents) ([]OptimismPortalWithdrawalProvenEvent, error) {
optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi() optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil { if err != nil {
...@@ -31,7 +77,6 @@ func OptimismPortalWithdrawalProvenEvents(events *ProcessedContractEvents) ([]Op ...@@ -31,7 +77,6 @@ func OptimismPortalWithdrawalProvenEvents(events *ProcessedContractEvents) ([]Op
} }
eventName := "WithdrawalProven" eventName := "WithdrawalProven"
processedWithdrawalProvenEvents := events.eventsBySignature[optimismPortalAbi.Events[eventName].ID] processedWithdrawalProvenEvents := events.eventsBySignature[optimismPortalAbi.Events[eventName].ID]
provenEvents := make([]OptimismPortalWithdrawalProvenEvent, len(processedWithdrawalProvenEvents)) provenEvents := make([]OptimismPortalWithdrawalProvenEvent, len(processedWithdrawalProvenEvents))
for i, provenEvent := range processedWithdrawalProvenEvents { for i, provenEvent := range processedWithdrawalProvenEvents {
...@@ -49,6 +94,30 @@ func OptimismPortalWithdrawalProvenEvents(events *ProcessedContractEvents) ([]Op ...@@ -49,6 +94,30 @@ func OptimismPortalWithdrawalProvenEvents(events *ProcessedContractEvents) ([]Op
return provenEvents, nil return provenEvents, nil
} }
func OptimismPortalWithdrawalFinalizedEvents(events *ProcessedContractEvents) ([]OptimismPortalWithdrawalFinalizedEvent, error) {
optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return nil, err
}
eventName := "WithdrawalFinalized"
processedWithdrawalFinalizedEvents := events.eventsBySignature[optimismPortalAbi.Events[eventName].ID]
finalizedEvents := make([]OptimismPortalWithdrawalFinalizedEvent, len(processedWithdrawalFinalizedEvents))
for i, finalizedEvent := range processedWithdrawalFinalizedEvents {
log := events.eventLog[finalizedEvent.GUID]
var withdrawalFinalized bindings.OptimismPortalWithdrawalFinalized
err := UnpackLog(&withdrawalFinalized, log, eventName, optimismPortalAbi)
if err != nil {
return nil, err
}
finalizedEvents[i] = OptimismPortalWithdrawalFinalizedEvent{&withdrawalFinalized, finalizedEvent}
}
return finalizedEvents, nil
}
func OptimismPortalQueryProvenWithdrawal(ethClient *ethclient.Client, portalAddress common.Address, withdrawalHash common.Hash) (OptimismPortalProvenWithdrawal, error) { func OptimismPortalQueryProvenWithdrawal(ethClient *ethclient.Client, portalAddress common.Address, withdrawalHash common.Hash) (OptimismPortalProvenWithdrawal, error) {
var provenWithdrawal OptimismPortalProvenWithdrawal var provenWithdrawal OptimismPortalProvenWithdrawal
......
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