l2_bridge_processor.go 8.58 KB
Newer Older
1 2 3 4 5 6
package bridge

import (
	"fmt"
	"math/big"

7
	"github.com/ethereum-optimism/optimism/indexer/bigint"
8
	"github.com/ethereum-optimism/optimism/indexer/config"
9
	"github.com/ethereum-optimism/optimism/indexer/database"
10
	"github.com/ethereum-optimism/optimism/indexer/processors/contracts"
11

Hamdi Allam's avatar
Hamdi Allam committed
12
	"github.com/ethereum/go-ethereum/common"
13 14 15
	"github.com/ethereum/go-ethereum/log"
)

16
// L2ProcessInitiatedBridgeEvents will query the database for bridge events that have been initiated between
17 18 19 20
// the specified block range. This covers every part of the multi-layered stack:
//  1. OptimismPortal
//  2. L2CrossDomainMessenger
//  3. L2StandardBridge
Hamdi Allam's avatar
Hamdi Allam committed
21
func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L2Metricer, l2Contracts config.L2Contracts, fromHeight, toHeight *big.Int) error {
22
	// (1) L2ToL1MessagePasser
23
	l2ToL1MPMessagesPassed, err := contracts.L2ToL1MessagePasserMessagePassedEvents(l2Contracts.L2ToL1MessagePasser, db, fromHeight, toHeight)
24 25 26
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
27 28 29
	if len(l2ToL1MPMessagesPassed) > 0 {
		log.Info("detected transaction withdrawals", "size", len(l2ToL1MPMessagesPassed))
	}
30

31
	withdrawnWEI := bigint.Zero
32
	messagesPassed := make(map[logKey]*contracts.L2ToL1MessagePasserMessagePassed, len(l2ToL1MPMessagesPassed))
Hamdi Allam's avatar
Hamdi Allam committed
33
	transactionWithdrawals := make([]database.L2TransactionWithdrawal, len(l2ToL1MPMessagesPassed))
34 35 36
	for i := range l2ToL1MPMessagesPassed {
		messagePassed := l2ToL1MPMessagesPassed[i]
		messagesPassed[logKey{messagePassed.Event.BlockHash, messagePassed.Event.LogIndex}] = &messagePassed
37
		withdrawnWEI = new(big.Int).Add(withdrawnWEI, messagePassed.Tx.Amount)
38

Hamdi Allam's avatar
Hamdi Allam committed
39
		transactionWithdrawals[i] = database.L2TransactionWithdrawal{
40 41 42 43 44 45 46 47 48 49 50
			WithdrawalHash:       messagePassed.WithdrawalHash,
			InitiatedL2EventGUID: messagePassed.Event.GUID,
			Nonce:                messagePassed.Nonce,
			GasLimit:             messagePassed.GasLimit,
			Tx:                   messagePassed.Tx,
		}
	}
	if len(messagesPassed) > 0 {
		if err := db.BridgeTransactions.StoreL2TransactionWithdrawals(transactionWithdrawals); err != nil {
			return err
		}
51

52 53 54
		// Convert the withdrawn WEI to ETH
		withdrawnETH, _ := bigint.WeiToETH(withdrawnWEI).Float64()
		metrics.RecordL2TransactionWithdrawals(len(transactionWithdrawals), withdrawnETH)
55 56 57
	}

	// (2) L2CrossDomainMessenger
58
	crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l2", l2Contracts.L2CrossDomainMessenger, db, fromHeight, toHeight)
59 60 61
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
62 63
	if len(crossDomainSentMessages) > 0 {
		log.Info("detected sent messages", "size", len(crossDomainSentMessages))
64 65 66
	}

	sentMessages := make(map[logKey]*contracts.CrossDomainMessengerSentMessageEvent, len(crossDomainSentMessages))
Hamdi Allam's avatar
Hamdi Allam committed
67
	bridgeMessages := make([]database.L2BridgeMessage, len(crossDomainSentMessages))
68 69 70 71 72 73 74
	for i := range crossDomainSentMessages {
		sentMessage := crossDomainSentMessages[i]
		sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = &sentMessage

		// extract the withdrawal hash from the previous MessagePassed event
		messagePassed, ok := messagesPassed[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex - 1}]
		if !ok {
Hamdi Allam's avatar
Hamdi Allam committed
75
			return fmt.Errorf("expected MessagePassedEvent preceding SentMessage. tx_hash = %s", sentMessage.Event.TransactionHash)
76
		} else if messagePassed.Event.TransactionHash != sentMessage.Event.TransactionHash {
Hamdi Allam's avatar
Hamdi Allam committed
77
			return fmt.Errorf("correlated events tx hash mismatch. message_tx_hash = %s, withdraw_tx_hash = %s", sentMessage.Event.TransactionHash, messagePassed.Event.TransactionHash)
78 79
		}

Hamdi Allam's avatar
Hamdi Allam committed
80
		bridgeMessages[i] = database.L2BridgeMessage{TransactionWithdrawalHash: messagePassed.WithdrawalHash, BridgeMessage: sentMessage.BridgeMessage}
81
	}
Hamdi Allam's avatar
Hamdi Allam committed
82 83
	if len(bridgeMessages) > 0 {
		if err := db.BridgeMessages.StoreL2BridgeMessages(bridgeMessages); err != nil {
84 85
			return err
		}
Hamdi Allam's avatar
Hamdi Allam committed
86
		metrics.RecordL2CrossDomainSentMessages(len(bridgeMessages))
87 88 89
	}

	// (3) L2StandardBridge
90
	initiatedBridges, err := contracts.StandardBridgeInitiatedEvents("l2", l2Contracts.L2StandardBridge, db, fromHeight, toHeight)
91 92 93
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
94 95
	if len(initiatedBridges) > 0 {
		log.Info("detected bridge withdrawals", "size", len(initiatedBridges))
96 97
	}

Hamdi Allam's avatar
Hamdi Allam committed
98 99
	bridgedTokens := make(map[common.Address]int)
	bridgeWithdrawals := make([]database.L2BridgeWithdrawal, len(initiatedBridges))
100 101 102
	for i := range initiatedBridges {
		initiatedBridge := initiatedBridges[i]

103
		// extract the cross domain message hash & withdraw hash from the following events
104 105
		messagePassed, ok := messagesPassed[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex + 1}]
		if !ok {
Hamdi Allam's avatar
Hamdi Allam committed
106
			return fmt.Errorf("expected MessagePassed following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
107
		} else if messagePassed.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
Hamdi Allam's avatar
Hamdi Allam committed
108
			return fmt.Errorf("correlated events tx hash mismatch. bridge_tx_hash = %s, withdraw_tx_hash = %s", initiatedBridge.Event.TransactionHash, messagePassed.Event.TransactionHash)
109
		}
110

111 112
		sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex + 2}]
		if !ok {
Hamdi Allam's avatar
Hamdi Allam committed
113
			return fmt.Errorf("expected SentMessage following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
114
		} else if sentMessage.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
Hamdi Allam's avatar
Hamdi Allam committed
115
			return fmt.Errorf("correlated events tx hash mismatch. bridge_tx_hash = %s, message_tx_hash = %s", initiatedBridge.Event.TransactionHash, sentMessage.Event.TransactionHash)
116 117
		}

Hamdi Allam's avatar
Hamdi Allam committed
118
		bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
119 120

		initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
Hamdi Allam's avatar
Hamdi Allam committed
121 122 123 124
		bridgeWithdrawals[i] = database.L2BridgeWithdrawal{
			TransactionWithdrawalHash: messagePassed.WithdrawalHash,
			BridgeTransfer:            initiatedBridge.BridgeTransfer,
		}
125
	}
Hamdi Allam's avatar
Hamdi Allam committed
126 127
	if len(bridgeWithdrawals) > 0 {
		if err := db.BridgeTransfers.StoreL2BridgeWithdrawals(bridgeWithdrawals); err != nil {
128 129
			return err
		}
Hamdi Allam's avatar
Hamdi Allam committed
130 131 132
		for tokenAddr, size := range bridgedTokens {
			metrics.RecordL2InitiatedBridgeTransfers(tokenAddr, size)
		}
133 134 135 136 137 138
	}

	// a-ok!
	return nil
}

139
// L2ProcessFinalizedBridgeEvents will query the database for all the finalization markers for all initiated
140 141 142 143 144
// bridge events. This covers every part of the multi-layered stack:
//  1. L2CrossDomainMessenger (relayMessage marker)
//  2. L2StandardBridge (no-op, since this is simply a wrapper over the L2CrossDomainMEssenger)
//
// NOTE: Unlike L1, there's no L2ToL1MessagePasser stage since transaction deposits are apart of the block derivation process.
Hamdi Allam's avatar
Hamdi Allam committed
145
func L2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L2Metricer, l2Contracts config.L2Contracts, fromHeight, toHeight *big.Int) error {
Hamdi Allam's avatar
Hamdi Allam committed
146
	// (1) L2CrossDomainMessenger
147
	crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l2", l2Contracts.L2CrossDomainMessenger, db, fromHeight, toHeight)
148 149 150
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
151 152 153
	if len(crossDomainRelayedMessages) > 0 {
		log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
	}
154 155 156 157 158 159 160

	for i := range crossDomainRelayedMessages {
		relayed := crossDomainRelayedMessages[i]
		message, err := db.BridgeMessages.L1BridgeMessage(relayed.MessageHash)
		if err != nil {
			return err
		} else if message == nil {
Hamdi Allam's avatar
Hamdi Allam committed
161
			return fmt.Errorf("missing indexed L1CrossDomainMessager message! tx_hash = %s", relayed.Event.TransactionHash)
162 163 164
		}

		if err := db.BridgeMessages.MarkRelayedL1BridgeMessage(relayed.MessageHash, relayed.Event.GUID); err != nil {
Hamdi Allam's avatar
Hamdi Allam committed
165
			return fmt.Errorf("failed to relay cross domain message. tx_hash = %s: %w", relayed.Event.TransactionHash, err)
166 167
		}
	}
168 169
	if len(crossDomainRelayedMessages) > 0 {
		metrics.RecordL2CrossDomainRelayedMessages(len(crossDomainRelayedMessages))
Hamdi Allam's avatar
Hamdi Allam committed
170
	}
171

Hamdi Allam's avatar
Hamdi Allam committed
172
	// (2) L2StandardBridge
173 174 175
	// - Nothing actionable on the database. Since the StandardBridge is layered ontop of the
	// CrossDomainMessenger, there's no need for any sanity or invariant checks as the previous step
	// ensures a relayed message (finalized bridge) can be linked with a sent message (initiated bridge).
176
	finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l2", l2Contracts.L2StandardBridge, db, fromHeight, toHeight)
177 178 179 180
	if err != nil {
		return err
	}

Hamdi Allam's avatar
Hamdi Allam committed
181
	finalizedTokens := make(map[common.Address]int)
182 183
	for i := range finalizedBridges {
		finalizedBridge := finalizedBridges[i]
Hamdi Allam's avatar
Hamdi Allam committed
184 185 186
		finalizedTokens[finalizedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
	}
	if len(finalizedBridges) > 0 {
187
		log.Info("detected finalized bridge deposits", "size", len(finalizedBridges))
Hamdi Allam's avatar
Hamdi Allam committed
188 189 190
		for tokenAddr, size := range finalizedTokens {
			metrics.RecordL2FinalizedBridgeTransfers(tokenAddr, size)
		}
191 192 193 194 195
	}

	// a-ok!
	return nil
}