l1_bridge_processor.go 11.5 KB
Newer Older
1 2 3 4 5 6 7 8
package bridge

import (
	"fmt"
	"math/big"

	"github.com/ethereum-optimism/optimism/indexer/config"
	"github.com/ethereum-optimism/optimism/indexer/database"
9
	"github.com/ethereum-optimism/optimism/indexer/processors/contracts"
10

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

16
// L1ProcessInitiatedBridgeEvents 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. L1CrossDomainMessenger
//  3. L1StandardBridge
Hamdi Allam's avatar
Hamdi Allam committed
21
func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1Metricer, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
22
	// (1) OptimismPortal
23
	optimismPortalTxDeposits, err := contracts.OptimismPortalTransactionDepositEvents(l1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
24 25 26
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
27 28 29
	if len(optimismPortalTxDeposits) > 0 {
		log.Info("detected transaction deposits", "size", len(optimismPortalTxDeposits))
	}
30 31

	portalDeposits := make(map[logKey]*contracts.OptimismPortalTransactionDepositEvent, len(optimismPortalTxDeposits))
Hamdi Allam's avatar
Hamdi Allam committed
32
	transactionDeposits := make([]database.L1TransactionDeposit, len(optimismPortalTxDeposits))
33 34 35
	for i := range optimismPortalTxDeposits {
		depositTx := optimismPortalTxDeposits[i]
		portalDeposits[logKey{depositTx.Event.BlockHash, depositTx.Event.LogIndex}] = &depositTx
Hamdi Allam's avatar
Hamdi Allam committed
36
		transactionDeposits[i] = database.L1TransactionDeposit{
37 38 39 40 41 42 43 44 45 46 47
			SourceHash:           depositTx.DepositTx.SourceHash,
			L2TransactionHash:    types.NewTx(depositTx.DepositTx).Hash(),
			InitiatedL1EventGUID: depositTx.Event.GUID,
			GasLimit:             depositTx.GasLimit,
			Tx:                   depositTx.Tx,
		}
	}
	if len(transactionDeposits) > 0 {
		if err := db.BridgeTransactions.StoreL1TransactionDeposits(transactionDeposits); err != nil {
			return err
		}
Hamdi Allam's avatar
Hamdi Allam committed
48
		metrics.RecordL1TransactionDeposits(len(transactionDeposits))
49 50 51
	}

	// (2) L1CrossDomainMessenger
52
	crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
53 54 55
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
56 57
	if len(crossDomainSentMessages) > 0 {
		log.Info("detected sent messages", "size", len(crossDomainSentMessages))
58 59 60
	}

	sentMessages := make(map[logKey]*contracts.CrossDomainMessengerSentMessageEvent, len(crossDomainSentMessages))
Hamdi Allam's avatar
Hamdi Allam committed
61
	bridgeMessages := make([]database.L1BridgeMessage, len(crossDomainSentMessages))
62 63 64 65 66 67 68
	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
69 70
			log.Error("expected TransactionDeposit preceding SentMessage event", "tx_hash", sentMessage.Event.TransactionHash.String())
			return fmt.Errorf("expected TransactionDeposit preceding SentMessage event. tx_hash = %s", sentMessage.Event.TransactionHash.String())
71 72
		}

Hamdi Allam's avatar
Hamdi Allam committed
73
		bridgeMessages[i] = database.L1BridgeMessage{TransactionSourceHash: portalDeposit.DepositTx.SourceHash, BridgeMessage: sentMessage.BridgeMessage}
74
	}
Hamdi Allam's avatar
Hamdi Allam committed
75 76
	if len(bridgeMessages) > 0 {
		if err := db.BridgeMessages.StoreL1BridgeMessages(bridgeMessages); err != nil {
77 78
			return err
		}
Hamdi Allam's avatar
Hamdi Allam committed
79
		metrics.RecordL1CrossDomainSentMessages(len(bridgeMessages))
80 81 82
	}

	// (3) L1StandardBridge
83
	initiatedBridges, err := contracts.StandardBridgeInitiatedEvents("l1", l1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
84 85 86
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
87 88
	if len(initiatedBridges) > 0 {
		log.Info("detected bridge deposits", "size", len(initiatedBridges))
89 90
	}

Hamdi Allam's avatar
Hamdi Allam committed
91 92
	bridgedTokens := make(map[common.Address]int)
	bridgeDeposits := make([]database.L1BridgeDeposit, len(initiatedBridges))
93 94 95 96 97 98
	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
99 100
			log.Error("expected TransactionDeposit following BridgeInitiated event", "tx_hash", initiatedBridge.Event.TransactionHash.String())
			return fmt.Errorf("expected TransactionDeposit following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
101 102 103
		}
		sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex + 2}]
		if !ok {
Hamdi Allam's avatar
Hamdi Allam committed
104 105
			log.Error("expected SentMessage following TransactionDeposit event", "tx_hash", initiatedBridge.Event.TransactionHash.String())
			return fmt.Errorf("expected SentMessage following TransactionDeposit event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
106 107 108
		}

		initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
Hamdi Allam's avatar
Hamdi Allam committed
109 110
		bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
		bridgeDeposits[i] = database.L1BridgeDeposit{
111 112 113 114
			TransactionSourceHash: portalDeposit.DepositTx.SourceHash,
			BridgeTransfer:        initiatedBridge.BridgeTransfer,
		}
	}
Hamdi Allam's avatar
Hamdi Allam committed
115 116
	if len(bridgeDeposits) > 0 {
		if err := db.BridgeTransfers.StoreL1BridgeDeposits(bridgeDeposits); err != nil {
117 118
			return err
		}
Hamdi Allam's avatar
Hamdi Allam committed
119 120 121
		for tokenAddr, size := range bridgedTokens {
			metrics.RecordL1InitiatedBridgeTransfers(tokenAddr, size)
		}
122 123 124 125 126 127 128 129 130
	}

	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
131
//  3. L1StandardBridge (no-op, since this is simply a wrapper over the L1CrossDomainMessenger)
Hamdi Allam's avatar
Hamdi Allam committed
132
func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1Metricer, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
133
	// (1) OptimismPortal (proven withdrawals)
134
	provenWithdrawals, err := contracts.OptimismPortalWithdrawalProvenEvents(l1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
135 136 137
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
138 139 140
	if len(provenWithdrawals) > 0 {
		log.Info("detected proven withdrawals", "size", len(provenWithdrawals))
	}
141 142 143 144 145 146 147

	for i := range provenWithdrawals {
		proven := provenWithdrawals[i]
		withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(proven.WithdrawalHash)
		if err != nil {
			return err
		} else if withdrawal == nil {
Hamdi Allam's avatar
Hamdi Allam committed
148 149
			log.Error("missing indexed withdrawal on proven event!", "tx_hash", proven.Event.TransactionHash.String())
			return fmt.Errorf("missing indexed withdrawal! tx_hash = %s", proven.Event.TransactionHash.String())
150 151 152
		}

		if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(proven.WithdrawalHash, provenWithdrawals[i].Event.GUID); err != nil {
Hamdi Allam's avatar
Hamdi Allam committed
153
			log.Error("failed to mark withdrawal as proven", "err", err, "tx_hash", proven.Event.TransactionHash.String())
154 155 156
			return err
		}
	}
Hamdi Allam's avatar
Hamdi Allam committed
157 158 159
	if len(provenWithdrawals) > 0 {
		metrics.RecordL1ProvenWithdrawals(len(provenWithdrawals))
	}
160 161

	// (2) OptimismPortal (finalized withdrawals)
162
	finalizedWithdrawals, err := contracts.OptimismPortalWithdrawalFinalizedEvents(l1Contracts.OptimismPortalProxy, db, fromHeight, toHeight)
163 164 165
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
166 167 168
	if len(finalizedWithdrawals) > 0 {
		log.Info("detected finalized withdrawals", "size", len(finalizedWithdrawals))
	}
169 170 171 172 173 174 175

	for i := range finalizedWithdrawals {
		finalized := finalizedWithdrawals[i]
		withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(finalized.WithdrawalHash)
		if err != nil {
			return err
		} else if withdrawal == nil {
Hamdi Allam's avatar
Hamdi Allam committed
176 177
			log.Error("missing indexed withdrawal on finalization event!", "tx_hash", finalized.Event.TransactionHash.String())
			return fmt.Errorf("missing indexed withdrawal on finalization! tx_hash: %s", finalized.Event.TransactionHash.String())
178 179 180
		}

		if err = db.BridgeTransactions.MarkL2TransactionWithdrawalFinalizedEvent(finalized.WithdrawalHash, finalized.Event.GUID, finalized.Success); err != nil {
Hamdi Allam's avatar
Hamdi Allam committed
181
			log.Error("failed to mark withdrawal as finalized", "err", err, "tx_hash", finalized.Event.TransactionHash.String())
182 183 184
			return err
		}
	}
Hamdi Allam's avatar
Hamdi Allam committed
185 186 187
	if len(finalizedWithdrawals) > 0 {
		metrics.RecordL1FinalizedWithdrawals(len(finalizedWithdrawals))
	}
188 189

	// (3) L1CrossDomainMessenger
190
	crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
191 192 193
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
194 195 196
	if len(crossDomainRelayedMessages) > 0 {
		log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
	}
197 198 199 200 201 202 203 204 205

	relayedMessages := make(map[logKey]*contracts.CrossDomainMessengerRelayedMessageEvent, len(crossDomainRelayedMessages))
	for i := range crossDomainRelayedMessages {
		relayed := crossDomainRelayedMessages[i]
		relayedMessages[logKey{BlockHash: relayed.Event.BlockHash, LogIndex: relayed.Event.LogIndex}] = &relayed
		message, err := db.BridgeMessages.L2BridgeMessage(relayed.MessageHash)
		if err != nil {
			return err
		} else if message == nil {
Hamdi Allam's avatar
Hamdi Allam committed
206 207
			log.Error("missing indexed L2CrossDomainMessenger message", "tx_hash", relayed.Event.TransactionHash.String())
			return fmt.Errorf("missing indexed L2CrossDomainMessager message. tx_hash %s", relayed.Event.TransactionHash.String())
208 209 210
		}

		if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayed.MessageHash, relayed.Event.GUID); err != nil {
Hamdi Allam's avatar
Hamdi Allam committed
211
			log.Error("failed to relay cross domain message", "err", err, "tx_hash", relayed.Event.TransactionHash.String())
212 213 214
			return err
		}
	}
Hamdi Allam's avatar
Hamdi Allam committed
215 216 217
	if len(crossDomainRelayedMessages) > 0 {
		metrics.RecordL1CrossDomainRelayedMessages(len(crossDomainRelayedMessages))
	}
218 219

	// (4) L1StandardBridge
220
	finalizedBridges, err := contracts.StandardBridgeFinalizedEvents("l1", l1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
221 222 223
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
224 225
	if len(finalizedBridges) > 0 {
		log.Info("detected finalized bridge withdrawals", "size", len(finalizedBridges))
226 227
	}

Hamdi Allam's avatar
Hamdi Allam committed
228
	finalizedTokens := make(map[common.Address]int)
229 230 231 232 233 234
	for i := range finalizedBridges {
		// Nothing actionable on the database. However, we can treat the relayed message
		// as an invariant by ensuring we can query for a deposit by the same hash
		finalizedBridge := finalizedBridges[i]
		relayedMessage, ok := relayedMessages[logKey{finalizedBridge.Event.BlockHash, finalizedBridge.Event.LogIndex + 1}]
		if !ok {
Hamdi Allam's avatar
Hamdi Allam committed
235 236
			log.Error("expected RelayedMessage following BridgeFinalized event", "tx_hash", finalizedBridge.Event.TransactionHash.String())
			return fmt.Errorf("expected RelayedMessage following BridgeFinalized event. tx_hash = %s", finalizedBridge.Event.TransactionHash.String())
237 238
		}

Hamdi Allam's avatar
Hamdi Allam committed
239
		// Since the message hash is computed from the relayed message, this ensures the deposit fields must match
240 241 242 243
		withdrawal, err := db.BridgeTransfers.L2BridgeWithdrawalWithFilter(database.BridgeTransfer{CrossDomainMessageHash: &relayedMessage.MessageHash})
		if err != nil {
			return err
		} else if withdrawal == nil {
Hamdi Allam's avatar
Hamdi Allam committed
244 245
			log.Error("missing L2StandardBridge withdrawal on L1 finalization", "tx_hash", finalizedBridge.Event.TransactionHash.String())
			return fmt.Errorf("missing L2StandardBridge withdrawal on L1 finalization. tx_hash: %s", finalizedBridge.Event.TransactionHash.String())
246
		}
Hamdi Allam's avatar
Hamdi Allam committed
247 248 249 250 251 252 253

		finalizedTokens[finalizedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
	}
	if len(finalizedBridges) > 0 {
		for tokenAddr, size := range finalizedTokens {
			metrics.RecordL1FinalizedBridgeTransfers(tokenAddr, size)
		}
254 255 256 257 258
	}

	// a-ok!
	return nil
}