l1_bridge_processor.go 12 KB
Newer Older
1 2 3 4 5 6
package bridge

import (
	"fmt"
	"math/big"

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

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

18
// L1ProcessInitiatedBridgeEvents will query the database for bridge events that have been initiated between
19 20 21 22
// the specified block range. This covers every part of the multi-layered stack:
//  1. OptimismPortal
//  2. L1CrossDomainMessenger
//  3. L1StandardBridge
Hamdi Allam's avatar
Hamdi Allam committed
23
func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1Metricer, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
24
	// (1) OptimismPortal
25
	optimismPortalTxDeposits, err := contracts.OptimismPortalTransactionDepositEvents(l1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
26 27 28
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
29 30 31
	if len(optimismPortalTxDeposits) > 0 {
		log.Info("detected transaction deposits", "size", len(optimismPortalTxDeposits))
	}
32

33
	mintedGWEI := bigint.Zero
34
	portalDeposits := make(map[logKey]*contracts.OptimismPortalTransactionDepositEvent, len(optimismPortalTxDeposits))
Hamdi Allam's avatar
Hamdi Allam committed
35
	transactionDeposits := make([]database.L1TransactionDeposit, len(optimismPortalTxDeposits))
36 37 38
	for i := range optimismPortalTxDeposits {
		depositTx := optimismPortalTxDeposits[i]
		portalDeposits[logKey{depositTx.Event.BlockHash, depositTx.Event.LogIndex}] = &depositTx
39
		mintedGWEI = new(big.Int).Add(mintedGWEI, depositTx.Tx.Amount)
40

Hamdi Allam's avatar
Hamdi Allam committed
41
		transactionDeposits[i] = database.L1TransactionDeposit{
42 43 44 45 46 47 48
			SourceHash:           depositTx.DepositTx.SourceHash,
			L2TransactionHash:    types.NewTx(depositTx.DepositTx).Hash(),
			InitiatedL1EventGUID: depositTx.Event.GUID,
			GasLimit:             depositTx.GasLimit,
			Tx:                   depositTx.Tx,
		}
	}
49

50 51 52 53
	if len(transactionDeposits) > 0 {
		if err := db.BridgeTransactions.StoreL1TransactionDeposits(transactionDeposits); err != nil {
			return err
		}
54 55

		// Convert to from wei to eth
56 57
		mintedETH, _ := bigint.WeiToETH(mintedGWEI).Float64()
		metrics.RecordL1TransactionDeposits(len(transactionDeposits), mintedETH)
58 59 60
	}

	// (2) L1CrossDomainMessenger
61
	crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
62 63 64
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
65 66
	if len(crossDomainSentMessages) > 0 {
		log.Info("detected sent messages", "size", len(crossDomainSentMessages))
67 68 69
	}

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

		// extract the deposit hash from the previous TransactionDepositedEvent
		portalDeposit, ok := portalDeposits[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex - 1}]
		if !ok {
Hamdi Allam's avatar
Hamdi Allam committed
78
			return fmt.Errorf("expected TransactionDeposit preceding SentMessage event. tx_hash = %s", sentMessage.Event.TransactionHash.String())
79
		} else if portalDeposit.Event.TransactionHash != sentMessage.Event.TransactionHash {
Hamdi Allam's avatar
Hamdi Allam committed
80
			return fmt.Errorf("correlated events tx hash mismatch. deposit_tx_hash = %s, message_tx_hash = %s", portalDeposit.Event.TransactionHash, sentMessage.Event.TransactionHash)
81 82
		}

Hamdi Allam's avatar
Hamdi Allam committed
83 84 85 86
		bridgeMessages[i] = database.L1BridgeMessage{
			TransactionSourceHash: portalDeposit.DepositTx.SourceHash,
			BridgeMessage:         sentMessage.BridgeMessage,
		}
87
	}
Hamdi Allam's avatar
Hamdi Allam committed
88 89
	if len(bridgeMessages) > 0 {
		if err := db.BridgeMessages.StoreL1BridgeMessages(bridgeMessages); err != nil {
90 91
			return err
		}
Hamdi Allam's avatar
Hamdi Allam committed
92
		metrics.RecordL1CrossDomainSentMessages(len(bridgeMessages))
93 94 95
	}

	// (3) L1StandardBridge
96
	initiatedBridges, err := contracts.StandardBridgeInitiatedEvents("l1", l1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
97 98 99
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
100 101
	if len(initiatedBridges) > 0 {
		log.Info("detected bridge deposits", "size", len(initiatedBridges))
102 103
	}

Hamdi Allam's avatar
Hamdi Allam committed
104 105
	bridgedTokens := make(map[common.Address]int)
	bridgeDeposits := make([]database.L1BridgeDeposit, len(initiatedBridges))
106 107 108 109 110 111
	for i := range initiatedBridges {
		initiatedBridge := initiatedBridges[i]

		// extract the cross domain message hash & deposit source hash from the following events
		portalDeposit, ok := portalDeposits[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex + 1}]
		if !ok {
Hamdi Allam's avatar
Hamdi Allam committed
112
			return fmt.Errorf("expected TransactionDeposit following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
113
		} else if portalDeposit.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
Hamdi Allam's avatar
Hamdi Allam committed
114
			return fmt.Errorf("correlated events tx hash mismatch, bridge_tx_hash = %s, deposit_tx_hash = %s", initiatedBridge.Event.TransactionHash, portalDeposit.Event.TransactionHash)
115
		}
116

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

Hamdi Allam's avatar
Hamdi Allam committed
124
		bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
125

126
		initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
Hamdi Allam's avatar
Hamdi Allam committed
127
		bridgeDeposits[i] = database.L1BridgeDeposit{
128 129 130 131
			TransactionSourceHash: portalDeposit.DepositTx.SourceHash,
			BridgeTransfer:        initiatedBridge.BridgeTransfer,
		}
	}
Hamdi Allam's avatar
Hamdi Allam committed
132 133
	if len(bridgeDeposits) > 0 {
		if err := db.BridgeTransfers.StoreL1BridgeDeposits(bridgeDeposits); err != nil {
134 135
			return err
		}
Hamdi Allam's avatar
Hamdi Allam committed
136 137 138
		for tokenAddr, size := range bridgedTokens {
			metrics.RecordL1InitiatedBridgeTransfers(tokenAddr, size)
		}
139 140 141 142 143 144 145 146 147
	}

	return nil
}

// L1ProcessFinalizedBridgeEvent will query the database for all the finalization markers for all initiated
// bridge events. This covers every part of the multi-layered stack:
//  1. OptimismPortal (Bedrock prove & finalize steps)
//  2. L1CrossDomainMessenger (relayMessage marker)
Hamdi Allam's avatar
Hamdi Allam committed
148
//  3. L1StandardBridge (no-op, since this is simply a wrapper over the L1CrossDomainMessenger)
Hamdi Allam's avatar
Hamdi Allam committed
149
func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1Metricer, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
150
	// (1) OptimismPortal (proven withdrawals)
151
	provenWithdrawals, err := contracts.OptimismPortalWithdrawalProvenEvents(l1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
152 153 154
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
155 156 157
	if len(provenWithdrawals) > 0 {
		log.Info("detected proven withdrawals", "size", len(provenWithdrawals))
	}
158

159
	skippedOVM1ProvenWithdrawals := 0
160
	for i := range provenWithdrawals {
161 162
		provenWithdrawal := provenWithdrawals[i]
		withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(provenWithdrawal.WithdrawalHash)
163 164 165
		if err != nil {
			return err
		} else if withdrawal == nil {
166 167 168 169 170
			if _, ok := ovm1.PortalWithdrawalTransactions[provenWithdrawal.WithdrawalHash]; ok {
				skippedOVM1ProvenWithdrawals++
				continue
			}
			return fmt.Errorf("missing indexed withdrawal! tx_hash = %s", provenWithdrawal.Event.TransactionHash)
171 172
		}

173 174
		if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(provenWithdrawal.WithdrawalHash, provenWithdrawals[i].Event.GUID); err != nil {
			return fmt.Errorf("failed to mark withdrawal as proven. tx_hash = %s: %w", provenWithdrawal.Event.TransactionHash, err)
175 176
		}
	}
Hamdi Allam's avatar
Hamdi Allam committed
177 178
	if len(provenWithdrawals) > 0 {
		metrics.RecordL1ProvenWithdrawals(len(provenWithdrawals))
179 180 181 182
		if skippedOVM1ProvenWithdrawals > 0 {
			metrics.RecordL1SkippedOVM1ProvenWithdrawals(skippedOVM1ProvenWithdrawals)
			log.Info("skipped OVM 1.0 proven withdrawals", "size", skippedOVM1ProvenWithdrawals)
		}
Hamdi Allam's avatar
Hamdi Allam committed
183
	}
184 185

	// (2) OptimismPortal (finalized withdrawals)
186
	finalizedWithdrawals, err := contracts.OptimismPortalWithdrawalFinalizedEvents(l1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
187 188 189
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
190 191 192
	if len(finalizedWithdrawals) > 0 {
		log.Info("detected finalized withdrawals", "size", len(finalizedWithdrawals))
	}
193

194
	skippedOVM1FinalizedWithdrawals := 0
195
	for i := range finalizedWithdrawals {
196 197
		finalizedWithdrawal := finalizedWithdrawals[i]
		withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(finalizedWithdrawal.WithdrawalHash)
198 199 200
		if err != nil {
			return err
		} else if withdrawal == nil {
201 202 203 204
			if _, ok := ovm1.PortalWithdrawalTransactions[finalizedWithdrawal.WithdrawalHash]; ok {
				skippedOVM1FinalizedWithdrawals++
				continue
			}
Hamdi Allam's avatar
Hamdi Allam committed
205
			return fmt.Errorf("missing indexed withdrawal on finalization! tx_hash = %s", finalizedWithdrawal.Event.TransactionHash.String())
206 207 208
		}

		if err = db.BridgeTransactions.MarkL2TransactionWithdrawalFinalizedEvent(finalizedWithdrawal.WithdrawalHash, finalizedWithdrawal.Event.GUID, finalizedWithdrawal.Success); err != nil {
Hamdi Allam's avatar
Hamdi Allam committed
209
			return fmt.Errorf("failed to mark withdrawal as finalized. tx_hash = %s: %w", finalizedWithdrawal.Event.TransactionHash, err)
210 211
		}
	}
Hamdi Allam's avatar
Hamdi Allam committed
212 213
	if len(finalizedWithdrawals) > 0 {
		metrics.RecordL1FinalizedWithdrawals(len(finalizedWithdrawals))
Hamdi Allam's avatar
Hamdi Allam committed
214
		if skippedOVM1ProvenWithdrawals > 0 {
215 216 217
			metrics.RecordL1SkippedOVM1FinalizedWithdrawals(skippedOVM1FinalizedWithdrawals)
			log.Info("skipped OVM 1.0 finalized withdrawals", "size", skippedOVM1FinalizedWithdrawals)
		}
Hamdi Allam's avatar
Hamdi Allam committed
218
	}
219 220

	// (3) L1CrossDomainMessenger
221
	crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
222 223 224
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
225 226 227
	if len(crossDomainRelayedMessages) > 0 {
		log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
	}
228

229
	skippedOVM1Messages := 0
230
	for i := range crossDomainRelayedMessages {
231 232
		relayedMessage := crossDomainRelayedMessages[i]
		message, err := db.BridgeMessages.L2BridgeMessage(relayedMessage.MessageHash)
233 234 235
		if err != nil {
			return err
		} else if message == nil {
236 237 238 239 240
			if _, ok := ovm1.L1RelayedMessages[relayedMessage.MessageHash]; ok {
				skippedOVM1Messages++
				continue
			}
			return fmt.Errorf("missing indexed L2CrossDomainMessager message! tx_hash = %s", relayedMessage.Event.TransactionHash.String())
241 242
		}

243 244
		if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
			return fmt.Errorf("failed to relay cross domain message. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
245 246
		}
	}
Hamdi Allam's avatar
Hamdi Allam committed
247 248
	if len(crossDomainRelayedMessages) > 0 {
		metrics.RecordL1CrossDomainRelayedMessages(len(crossDomainRelayedMessages))
249 250 251 252
		if skippedOVM1Messages > 0 { // Logged as a warning just for visibility
			metrics.RecordL1SkippedOVM1CrossDomainRelayedMessages(skippedOVM1Messages)
			log.Info("skipped OVM 1.0 relayed L2CrossDomainMessenger withdrawals", "size", skippedOVM1Messages)
		}
Hamdi Allam's avatar
Hamdi Allam committed
253
	}
254 255

	// (4) L1StandardBridge
256 257 258
	// - 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).
259
	finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l1", l1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
260 261 262 263
	if err != nil {
		return err
	}

Hamdi Allam's avatar
Hamdi Allam committed
264
	finalizedTokens := make(map[common.Address]int)
265 266
	for i := range finalizedBridges {
		finalizedBridge := finalizedBridges[i]
Hamdi Allam's avatar
Hamdi Allam committed
267 268 269
		finalizedTokens[finalizedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
	}
	if len(finalizedBridges) > 0 {
270
		log.Info("detected finalized bridge withdrawals", "size", len(finalizedBridges))
Hamdi Allam's avatar
Hamdi Allam committed
271 272 273
		for tokenAddr, size := range finalizedTokens {
			metrics.RecordL1FinalizedBridgeTransfers(tokenAddr, size)
		}
274 275 276 277 278
	}

	// a-ok!
	return nil
}