legacy_bridge_processor.go 15.3 KB
Newer Older
Hamdi Allam's avatar
Hamdi Allam committed
1 2 3 4 5 6
package bridge

import (
	"fmt"
	"math/big"

7
	"github.com/ethereum/go-ethereum/crypto"
Hamdi Allam's avatar
Hamdi Allam committed
8 9 10 11 12 13 14 15 16 17
	"github.com/ethereum/go-ethereum/log"

	"github.com/ethereum-optimism/optimism/indexer/config"
	"github.com/ethereum-optimism/optimism/indexer/database"
	"github.com/ethereum-optimism/optimism/indexer/node"
	"github.com/ethereum-optimism/optimism/indexer/processors/contracts"
)

// Legacy Bridge Initiation

18 19 20 21 22
// LegacyL1ProcessInitiatedEvents will query the data for bridge events within the specified block range
// according the pre-bedrock protocol. This follows:
//  1. CanonicalTransactionChain
//  2. L1CrossDomainMessenger
//  3. L1StandardBridge
23
func LegacyL1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
Hamdi Allam's avatar
Hamdi Allam committed
24
	// (1) CanonicalTransactionChain
25
	ctcTxDepositEvents, err := contracts.LegacyCTCDepositEvents(l1Contracts.LegacyCanonicalTransactionChain, db, fromHeight, toHeight)
Hamdi Allam's avatar
Hamdi Allam committed
26 27 28
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
29 30 31
	if len(ctcTxDepositEvents) > 0 {
		log.Info("detected legacy transaction deposits", "size", len(ctcTxDepositEvents))
	}
Hamdi Allam's avatar
Hamdi Allam committed
32 33 34 35 36 37 38

	ctcTxDeposits := make(map[logKey]*contracts.LegacyCTCDepositEvent, len(ctcTxDepositEvents))
	transactionDeposits := make([]database.L1TransactionDeposit, len(ctcTxDepositEvents))
	for i := range ctcTxDepositEvents {
		deposit := ctcTxDepositEvents[i]
		ctcTxDeposits[logKey{deposit.Event.BlockHash, deposit.Event.LogIndex}] = &deposit
		transactionDeposits[i] = database.L1TransactionDeposit{
39 40 41 42 43
			// We re-use the L2 Transaction hash as the source hash
			// to remain consistent in the schema.
			SourceHash:        deposit.TxHash,
			L2TransactionHash: deposit.TxHash,

Hamdi Allam's avatar
Hamdi Allam committed
44 45 46 47 48 49 50 51 52 53 54 55
			InitiatedL1EventGUID: deposit.Event.GUID,
			GasLimit:             deposit.GasLimit,
			Tx:                   deposit.Tx,
		}
	}
	if len(ctcTxDepositEvents) > 0 {
		if err := db.BridgeTransactions.StoreL1TransactionDeposits(transactionDeposits); err != nil {
			return err
		}
	}

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

Hamdi Allam's avatar
Hamdi Allam committed
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
	sentMessages := make(map[logKey]*contracts.CrossDomainMessengerSentMessageEvent, len(crossDomainSentMessages))
	l1BridgeMessages := make([]database.L1BridgeMessage, len(crossDomainSentMessages))
	for i := range crossDomainSentMessages {
		sentMessage := crossDomainSentMessages[i]
		sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = &sentMessage

		// extract the deposit hash from the previous TransactionDepositedEvent
		ctcTxDeposit, ok := ctcTxDeposits[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex - 1}]
		if !ok {
			log.Error("missing transaction deposit for cross domain message", "tx_hash", sentMessage.Event.TransactionHash.String())
			return fmt.Errorf("missing preceding TransactionEnqueued for SentMessage event. tx_hash = %s", sentMessage.Event.TransactionHash.String())
		}

		l1BridgeMessages[i] = database.L1BridgeMessage{TransactionSourceHash: ctcTxDeposit.TxHash, BridgeMessage: sentMessage.BridgeMessage}
	}
	if len(l1BridgeMessages) > 0 {
		if err := db.BridgeMessages.StoreL1BridgeMessages(l1BridgeMessages); err != nil {
			return err
		}
	}

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

Hamdi Allam's avatar
Hamdi Allam committed
94 95 96 97 98 99 100 101 102 103
	l1BridgeDeposits := make([]database.L1BridgeDeposit, len(initiatedBridges))
	for i := range initiatedBridges {
		initiatedBridge := initiatedBridges[i]

		// extract the cross domain message hash & deposit source hash from the following events
		// Unlike bedrock, the bridge events are emitted AFTER sending the cross domain message
		// 	- Event Flow: TransactionEnqueued -> SentMessage -> DepositInitiated
		sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex - 1}]
		if !ok {
			log.Error("missing cross domain message for bridge transfer", "tx_hash", initiatedBridge.Event.TransactionHash.String())
104
			return fmt.Errorf("expected SentMessage preceding DepositInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
Hamdi Allam's avatar
Hamdi Allam committed
105 106 107 108
		}
		ctcTxDeposit, ok := ctcTxDeposits[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex - 2}]
		if !ok {
			log.Error("missing transaction deposit for bridge transfer", "tx_hash", initiatedBridge.Event.TransactionHash.String())
109
			return fmt.Errorf("expected TransactionEnqueued preceding DepostInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
Hamdi Allam's avatar
Hamdi Allam committed
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
		}

		initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
		l1BridgeDeposits[i] = database.L1BridgeDeposit{
			TransactionSourceHash: ctcTxDeposit.TxHash,
			BridgeTransfer:        initiatedBridge.BridgeTransfer,
		}
	}
	if len(l1BridgeDeposits) > 0 {
		if err := db.BridgeTransfers.StoreL1BridgeDeposits(l1BridgeDeposits); err != nil {
			return err
		}
	}

	// a-ok!
	return nil
}

128 129
// LegacyL2ProcessInitiatedEvents will query the data for bridge events within the specified block range
// according the pre-bedrock protocol. This follows:
130
//  1. L2CrossDomainMessenger - The LegacyMessagePasser contract cannot be used as entrypoint to bridge transactions from L2. The protocol
131 132
//     only allows the L2CrossDomainMessenger as the sole sender when relaying a bridged message.
//  2. L2StandardBridge
133
func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, l2Contracts config.L2Contracts, fromHeight, toHeight *big.Int) error {
134
	// (1) L2CrossDomainMessenger
135
	crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l2", l2Contracts.L2CrossDomainMessenger, db, fromHeight, toHeight)
Hamdi Allam's avatar
Hamdi Allam committed
136 137 138
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
139 140 141 142 143

	if len(crossDomainSentMessages) > 0 {
		log.Info("detected legacy transaction withdrawals (via L2CrossDomainMessenger)", "size", len(crossDomainSentMessages))
	}

Hamdi Allam's avatar
Hamdi Allam committed
144 145 146 147 148 149 150
	sentMessages := make(map[logKey]*contracts.CrossDomainMessengerSentMessageEvent, len(crossDomainSentMessages))
	l2BridgeMessages := make([]database.L2BridgeMessage, len(crossDomainSentMessages))
	l2TransactionWithdrawals := make([]database.L2TransactionWithdrawal, len(crossDomainSentMessages))
	for i := range crossDomainSentMessages {
		sentMessage := crossDomainSentMessages[i]
		sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = &sentMessage

151 152
		// To ensure consistency in the schema, we duplicate this as the "root" transaction withdrawal. The storage key in the message
		// passer contract is sha3(calldata + sender). The sender always being the L2CrossDomainMessenger pre-bedrock.
153
		withdrawalHash := crypto.Keccak256Hash(append(sentMessage.MessageCalldata, l2Contracts.L2CrossDomainMessenger[:]...))
Hamdi Allam's avatar
Hamdi Allam committed
154
		l2TransactionWithdrawals[i] = database.L2TransactionWithdrawal{
155
			WithdrawalHash:       withdrawalHash,
Hamdi Allam's avatar
Hamdi Allam committed
156 157 158 159 160 161 162 163 164 165 166
			InitiatedL2EventGUID: sentMessage.Event.GUID,
			Nonce:                sentMessage.BridgeMessage.Nonce,
			GasLimit:             sentMessage.BridgeMessage.GasLimit,
			Tx: database.Transaction{
				FromAddress: sentMessage.BridgeMessage.Tx.FromAddress,
				ToAddress:   sentMessage.BridgeMessage.Tx.ToAddress,
				Amount:      big.NewInt(0),
				Data:        sentMessage.BridgeMessage.Tx.Data,
				Timestamp:   sentMessage.Event.Timestamp,
			},
		}
167 168

		l2BridgeMessages[i] = database.L2BridgeMessage{
169
			TransactionWithdrawalHash: withdrawalHash,
170 171
			BridgeMessage:             sentMessage.BridgeMessage,
		}
Hamdi Allam's avatar
Hamdi Allam committed
172 173 174 175 176 177 178 179 180 181 182 183
	}

	if len(l2BridgeMessages) > 0 {
		if err := db.BridgeTransactions.StoreL2TransactionWithdrawals(l2TransactionWithdrawals); err != nil {
			return err
		}
		if err := db.BridgeMessages.StoreL2BridgeMessages(l2BridgeMessages); err != nil {
			return err
		}
	}

	// (2) L2StandardBridge
184
	initiatedBridges, err := contracts.L2StandardBridgeLegacyWithdrawalInitiatedEvents(l2Contracts.L2StandardBridge, db, fromHeight, toHeight)
Hamdi Allam's avatar
Hamdi Allam committed
185 186 187
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
188 189 190 191
	if len(initiatedBridges) > 0 {
		log.Info("detected legacy bridge withdrawals", "size", len(initiatedBridges))
	}

Hamdi Allam's avatar
Hamdi Allam committed
192 193 194 195 196 197 198 199 200
	l2BridgeWithdrawals := make([]database.L2BridgeWithdrawal, len(initiatedBridges))
	for i := range initiatedBridges {
		initiatedBridge := initiatedBridges[i]

		// extract the cross domain message hash & deposit source hash from the following events
		// Unlike bedrock, the bridge events are emitted AFTER sending the cross domain message
		// 	- Event Flow: TransactionEnqueued -> SentMessage -> DepositInitiated
		sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex - 1}]
		if !ok {
Hamdi Allam's avatar
Hamdi Allam committed
201
			log.Error("expected SentMessage preceding DepositInitiated event", "tx_hash", initiatedBridge.Event.TransactionHash.String())
Hamdi Allam's avatar
Hamdi Allam committed
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
			return fmt.Errorf("expected SentMessage preceding DepositInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
		}

		initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
		l2BridgeWithdrawals[i] = database.L2BridgeWithdrawal{
			TransactionWithdrawalHash: sentMessage.BridgeMessage.MessageHash,
			BridgeTransfer:            initiatedBridge.BridgeTransfer,
		}
	}
	if len(l2BridgeWithdrawals) > 0 {
		if err := db.BridgeTransfers.StoreL2BridgeWithdrawals(l2BridgeWithdrawals); err != nil {
			return err
		}
	}

	// a-ok
	return nil
}

// Legacy Bridge Finalization

223
// LegacyL1ProcessFinalizedBridgeEvents will query for bridge events within the specified block range
Hamdi Allam's avatar
Hamdi Allam committed
224
// according to the pre-bedrock protocol. This follows:
225 226
//  1. L1CrossDomainMessenger
//  2. L1StandardBridge
227
func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, l1Client node.EthClient, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
Hamdi Allam's avatar
Hamdi Allam committed
228 229
	// (1) L1CrossDomainMessenger -> This is the root-most contract from which bridge events are finalized since withdrawals must be initiated from the
	// L2CrossDomainMessenger. Since there's no two-step withdrawal process, we mark the transaction as proven/finalized in the same step
230
	crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
Hamdi Allam's avatar
Hamdi Allam committed
231 232 233
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
234 235 236
	if len(crossDomainRelayedMessages) > 0 {
		log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
	}
Hamdi Allam's avatar
Hamdi Allam committed
237 238 239 240 241 242 243 244 245 246 247 248 249 250

	skippedPreRegenesisMessages := 0
	for i := range crossDomainRelayedMessages {
		relayedMessage := crossDomainRelayedMessages[i]
		message, err := db.BridgeMessages.L2BridgeMessage(relayedMessage.MessageHash)
		if err != nil {
			return err
		} else if message == nil {
			// Before surfacing an error about a missing withdrawal, we need to handle an edge case
			// for OP-Mainnet pre-regensis withdrawals that no longer exist on L2.
			tx, err := l1Client.TxByHash(relayedMessage.Event.TransactionHash)
			if err != nil {
				return err
			} else if tx == nil {
Hamdi Allam's avatar
Hamdi Allam committed
251 252
				log.Error("missing tx for relayed message", "tx_hash", relayedMessage.Event.TransactionHash.String())
				return fmt.Errorf("missing tx for relayed message. tx_hash = %s", relayedMessage.Event.TransactionHash.String())
Hamdi Allam's avatar
Hamdi Allam committed
253 254 255 256 257
			}

			relayMessageData := tx.Data()[4:]
			inputs, err := contracts.CrossDomainMessengerLegacyRelayMessageEncoding.Inputs.Unpack(relayMessageData)
			if err != nil || inputs == nil {
Hamdi Allam's avatar
Hamdi Allam committed
258 259
				log.Error("failed to extract XDomainCallData from relayMessage transaction", "err", err, "tx_hash", relayedMessage.Event.TransactionHash.String())
				return fmt.Errorf("unable to extract XDomainCallData from relayMessage transaction. err = %w. tx_hash = %s", err, relayedMessage.Event.TransactionHash.String())
Hamdi Allam's avatar
Hamdi Allam committed
260 261 262 263 264 265 266 267 268 269
			}

			// NOTE: Since OP-Mainnet is the only network to go through a regensis we can simply harcode the
			// the starting message nonce at genesis (100k). Any relayed withdrawal on L1 with a lesser nonce
			// is a clear indicator of a pre-regenesis withdrawal.
			if inputs[3].(*big.Int).Int64() < 100_000 {
				// skip pre-regenesis withdrawals
				skippedPreRegenesisMessages++
				continue
			} else {
Hamdi Allam's avatar
Hamdi Allam committed
270 271
				log.Error("missing indexed legacy L2CrossDomainMessenger message", "tx_hash", relayedMessage.Event.TransactionHash.String())
				return fmt.Errorf("missing indexed L2CrossDomainMessager message. tx_hash %s", relayedMessage.Event.TransactionHash.String())
Hamdi Allam's avatar
Hamdi Allam committed
272 273 274 275 276
			}
		}

		// Mark the associated tx withdrawal as proven/finalized with the same event
		if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
Hamdi Allam's avatar
Hamdi Allam committed
277
			log.Error("failed to mark withdrawal as proven", "err", err)
Hamdi Allam's avatar
Hamdi Allam committed
278 279 280
			return err
		}
		if err := db.BridgeTransactions.MarkL2TransactionWithdrawalFinalizedEvent(relayedMessage.MessageHash, relayedMessage.Event.GUID, true); err != nil {
Hamdi Allam's avatar
Hamdi Allam committed
281
			log.Error("failed to mark withdrawal as finalzed", "err", err)
Hamdi Allam's avatar
Hamdi Allam committed
282 283 284
			return err
		}
		if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
Hamdi Allam's avatar
Hamdi Allam committed
285
			log.Error("failed to relay cross domain message", "err", err)
Hamdi Allam's avatar
Hamdi Allam committed
286 287 288 289
			return err
		}
	}

Hamdi Allam's avatar
Hamdi Allam committed
290 291 292
	if skippedPreRegenesisMessages > 0 {
		// Logged as a warning just for visibility
		log.Warn("skipped pre-regensis relayed L2CrossDomainMessenger withdrawals", "size", skippedPreRegenesisMessages)
Hamdi Allam's avatar
Hamdi Allam committed
293 294
	}

295 296 297
	// (2) L2StandardBridge -- no-op for now as there's nothing actionable to do here besides
	// santiy checks which is not important for legacy code. The message status is already tracked
	// via the relayed bridge messed through the cross domain messenger.
Hamdi Allam's avatar
Hamdi Allam committed
298 299 300 301 302

	// a-ok!
	return nil
}

303
// LegacyL2ProcessFinalizedBridgeEvents will query for bridge events within the specified block range
Hamdi Allam's avatar
Hamdi Allam committed
304
// according to the pre-bedrock protocol. This follows:
305 306
//  1. L2CrossDomainMessenger
//  2. L2StandardBridge
307
func LegacyL2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, l2Contracts config.L2Contracts, fromHeight, toHeight *big.Int) error {
Hamdi Allam's avatar
Hamdi Allam committed
308
	// (1) L2CrossDomainMessenger
309
	crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l2", l2Contracts.L2CrossDomainMessenger, db, fromHeight, toHeight)
Hamdi Allam's avatar
Hamdi Allam committed
310 311 312
	if err != nil {
		return err
	}
Hamdi Allam's avatar
Hamdi Allam committed
313 314 315
	if len(crossDomainRelayedMessages) > 0 {
		log.Info("detected relayed legacy messages", "size", len(crossDomainRelayedMessages))
	}
Hamdi Allam's avatar
Hamdi Allam committed
316 317 318 319 320 321 322

	for i := range crossDomainRelayedMessages {
		relayedMessage := crossDomainRelayedMessages[i]
		message, err := db.BridgeMessages.L1BridgeMessage(relayedMessage.MessageHash)
		if err != nil {
			return err
		} else if message == nil {
Hamdi Allam's avatar
Hamdi Allam committed
323 324
			log.Error("missing indexed legacy L1CrossDomainMessenger message", "tx_hash", relayedMessage.Event.TransactionHash.String())
			return fmt.Errorf("missing indexed L1CrossDomainMessager message. tx_hash = %s", relayedMessage.Event.TransactionHash.String())
Hamdi Allam's avatar
Hamdi Allam committed
325 326 327
		}

		if err := db.BridgeMessages.MarkRelayedL1BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
Hamdi Allam's avatar
Hamdi Allam committed
328
			log.Error("failed to relay cross domain message", "err", err)
Hamdi Allam's avatar
Hamdi Allam committed
329 330 331 332
			return err
		}
	}

333 334 335
	// (2) L2StandardBridge -- no-op for now as there's nothing actionable to do here besides
	// santiy checks which is not important for legacy code. The message status is already tracked
	// via the relayed bridge messed through the cross domain messenger.
Hamdi Allam's avatar
Hamdi Allam committed
336 337 338 339

	// a-ok!
	return nil
}