l1_processor.go 19.7 KB
Newer Older
1 2 3
package processor

import (
4
	"bytes"
5 6
	"context"
	"errors"
7
	"fmt"
8
	"math/big"
9 10
	"reflect"

11 12
	"github.com/ethereum-optimism/optimism/indexer/database"
	"github.com/ethereum-optimism/optimism/indexer/node"
13 14
	"github.com/ethereum-optimism/optimism/op-bindings/bindings"
	legacy_bindings "github.com/ethereum-optimism/optimism/op-bindings/legacy-bindings"
15 16
	"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
	"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
17

18
	"github.com/ethereum/go-ethereum"
19
	"github.com/ethereum/go-ethereum/accounts/abi"
20
	"github.com/ethereum/go-ethereum/common"
21
	"github.com/ethereum/go-ethereum/core/types"
22
	"github.com/ethereum/go-ethereum/ethclient"
23 24 25
	"github.com/ethereum/go-ethereum/log"
)

26 27 28 29 30 31 32 33
type L1Contracts struct {
	OptimismPortal         common.Address
	L2OutputOracle         common.Address
	L1CrossDomainMessenger common.Address
	L1StandardBridge       common.Address
	L1ERC721Bridge         common.Address

	// Some more contracts -- ProxyAdmin, SystemConfig, etcc
34
	// Ignore the auxiliary contracts?
35 36 37 38 39

	// Legacy contracts? We'll add this in to index the legacy chain.
	// Remove afterwards?
}

40
func (c L1Contracts) ToSlice() []common.Address {
41 42 43 44 45 46 47 48 49 50 51
	fields := reflect.VisibleFields(reflect.TypeOf(c))
	v := reflect.ValueOf(c)

	contracts := make([]common.Address, len(fields))
	for i, field := range fields {
		contracts[i] = (v.FieldByName(field.Name).Interface()).(common.Address)
	}

	return contracts
}

52 53 54 55 56
type checkpointAbi struct {
	l2OutputOracle             *abi.ABI
	legacyStateCommitmentChain *abi.ABI
}

57 58 59 60
type L1Processor struct {
	processor
}

61 62
func NewL1Processor(logger log.Logger, ethClient node.EthClient, db *database.DB, l1Contracts L1Contracts) (*L1Processor, error) {
	l1ProcessLog := logger.New("processor", "l1")
63
	l1ProcessLog.Info("initializing processor")
64

65 66 67 68 69 70 71 72 73 74 75 76 77
	l2OutputOracleABI, err := bindings.L2OutputOracleMetaData.GetAbi()
	if err != nil {
		l1ProcessLog.Error("unable to generate L2OutputOracle ABI", "err", err)
		return nil, err
	}
	legacyStateCommitmentChainABI, err := legacy_bindings.StateCommitmentChainMetaData.GetAbi()
	if err != nil {
		l1ProcessLog.Error("unable to generate legacy StateCommitmentChain ABI", "err", err)
		return nil, err
	}
	checkpointAbi := checkpointAbi{l2OutputOracle: l2OutputOracleABI, legacyStateCommitmentChain: legacyStateCommitmentChainABI}

	latestHeader, err := db.Blocks.LatestL1BlockHeader()
78 79 80 81 82 83
	if err != nil {
		return nil, err
	}

	var fromL1Header *types.Header
	if latestHeader != nil {
84
		l1ProcessLog.Info("detected last indexed block", "height", latestHeader.Number.Int, "hash", latestHeader.Hash)
85 86
		l1Header, err := ethClient.BlockHeaderByHash(latestHeader.Hash)
		if err != nil {
87
			l1ProcessLog.Error("unable to fetch header for last indexed block", "hash", latestHeader.Hash, "err", err)
88 89 90 91 92
			return nil, err
		}

		fromL1Header = l1Header
	} else {
93
		// we shouldn't start from genesis with l1. Need a "genesis" L1 height provided for the rollup
94 95 96 97 98 99
		l1ProcessLog.Info("no indexed state, starting from genesis")
		fromL1Header = nil
	}

	l1Processor := &L1Processor{
		processor: processor{
100
			headerTraversal: node.NewHeaderTraversal(ethClient, fromL1Header),
101 102 103
			db:              db,
			processFn:       l1ProcessFn(l1ProcessLog, ethClient, l1Contracts, checkpointAbi),
			processLog:      l1ProcessLog,
104 105 106 107 108 109
		},
	}

	return l1Processor, nil
}

110
func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1Contracts, checkpointAbi checkpointAbi) ProcessFn {
111 112
	rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())

113
	contractAddrs := l1Contracts.ToSlice()
114 115
	processLog.Info("processor configured with contracts", "contracts", l1Contracts)

116 117 118 119 120
	outputProposedEventName := "OutputProposed"
	outputProposedEventSig := checkpointAbi.l2OutputOracle.Events[outputProposedEventName].ID

	legacyStateBatchAppendedEventName := "StateBatchAppended"
	legacyStateBatchAppendedEventSig := checkpointAbi.legacyStateCommitmentChain.Events[legacyStateBatchAppendedEventName].ID
121

122
	return func(db *database.DB, headers []*types.Header) error {
123
		headerMap := make(map[common.Hash]*types.Header)
124
		for _, header := range headers {
125
			headerMap[header.Hash()] = header
126 127
		}

Hamdi Allam's avatar
Hamdi Allam committed
128
		/** Watch for all Optimism Contract Events **/
129

130
		logFilter := ethereum.FilterQuery{FromBlock: headers[0].Number, ToBlock: headers[len(headers)-1].Number, Addresses: contractAddrs}
Hamdi Allam's avatar
Hamdi Allam committed
131
		logs, err := rawEthClient.FilterLogs(context.Background(), logFilter) // []types.Log
132
		if err != nil {
133
			return err
134 135
		}

Hamdi Allam's avatar
Hamdi Allam committed
136
		// L2 checkpoints posted on L1
137 138 139
		outputProposals := []*database.OutputProposal{}
		legacyStateBatches := []*database.LegacyStateBatch{}

140
		l1HeadersOfInterest := make(map[common.Hash]bool)
Hamdi Allam's avatar
Hamdi Allam committed
141 142 143
		l1ContractEvents := make([]*database.L1ContractEvent, len(logs))

		processedContractEvents := NewProcessedContractEvents()
144 145
		for i := range logs {
			log := &logs[i]
146
			header, ok := headerMap[log.BlockHash]
147
			if !ok {
148
				processLog.Error("contract event found with associated header not in the batch", "header", log.BlockHash, "log_index", log.Index)
149
				return errors.New("parsed log with a block hash not in this batch")
150 151
			}

152
			contractEvent := processedContractEvents.AddLog(log, header.Time)
153
			l1HeadersOfInterest[log.BlockHash] = true
Hamdi Allam's avatar
Hamdi Allam committed
154
			l1ContractEvents[i] = &database.L1ContractEvent{ContractEvent: *contractEvent}
155 156 157 158

			// Track Checkpoint Events for L2
			switch contractEvent.EventSignature {
			case outputProposedEventSig:
159 160 161 162
				var outputProposed bindings.L2OutputOracleOutputProposed
				err := UnpackLog(&outputProposed, log, outputProposedEventName, checkpointAbi.l2OutputOracle)
				if err != nil {
					return err
163 164 165
				}

				outputProposals = append(outputProposals, &database.OutputProposal{
166 167 168
					OutputRoot:          outputProposed.OutputRoot,
					L2OutputIndex:       database.U256{Int: outputProposed.L2OutputIndex},
					L2BlockNumber:       database.U256{Int: outputProposed.L2BlockNumber},
169 170 171 172 173
					L1ContractEventGUID: contractEvent.GUID,
				})

			case legacyStateBatchAppendedEventSig:
				var stateBatchAppended legacy_bindings.StateCommitmentChainStateBatchAppended
174 175
				err := UnpackLog(&stateBatchAppended, log, legacyStateBatchAppendedEventName, checkpointAbi.legacyStateCommitmentChain)
				if err != nil {
176
					return err
177 178 179
				}

				legacyStateBatches = append(legacyStateBatches, &database.LegacyStateBatch{
180
					Index:               stateBatchAppended.BatchIndex.Uint64(),
181 182 183 184 185 186
					Root:                stateBatchAppended.BatchRoot,
					Size:                stateBatchAppended.BatchSize.Uint64(),
					PrevTotal:           stateBatchAppended.PrevTotalElements.Uint64(),
					L1ContractEventGUID: contractEvent.GUID,
				})
			}
187 188
		}

189
		/** Aggregate applicable L1 Blocks **/
190

191 192
		// we iterate on the original array to maintain ordering. probably can find a more efficient
		// way to iterate over the `l1HeadersOfInterest` map while maintaining ordering
Hamdi Allam's avatar
Hamdi Allam committed
193
		indexedL1Headers := []*database.L1BlockHeader{}
194
		for _, header := range headers {
Hamdi Allam's avatar
Hamdi Allam committed
195 196
			_, hasLogs := l1HeadersOfInterest[header.Hash()]
			if !hasLogs {
197 198 199
				continue
			}

200
			indexedL1Headers = append(indexedL1Headers, &database.L1BlockHeader{BlockHeader: database.BlockHeaderFromGethHeader(header)})
201 202
		}

203 204
		/** Update Database **/

Hamdi Allam's avatar
Hamdi Allam committed
205 206
		numIndexedL1Headers := len(indexedL1Headers)
		if numIndexedL1Headers > 0 {
207
			processLog.Info("saving l1 blocks with optimism logs", "size", numIndexedL1Headers, "batch_size", len(headers))
Hamdi Allam's avatar
Hamdi Allam committed
208 209 210 211
			err = db.Blocks.StoreL1BlockHeaders(indexedL1Headers)
			if err != nil {
				return err
			}
212

Hamdi Allam's avatar
Hamdi Allam committed
213
			// Since the headers to index are derived from the existence of logs, we know in this branch `numLogs > 0`
Hamdi Allam's avatar
Hamdi Allam committed
214
			processLog.Info("detected contract logs", "size", len(l1ContractEvents))
Hamdi Allam's avatar
Hamdi Allam committed
215 216 217 218
			err = db.ContractEvents.StoreL1ContractEvents(l1ContractEvents)
			if err != nil {
				return err
			}
219

Hamdi Allam's avatar
Hamdi Allam committed
220 221 222 223 224 225 226
			// Mark L2 checkpoints that have been recorded on L1 (L2OutputProposal & StateBatchAppended events)
			numLegacyStateBatches := len(legacyStateBatches)
			if numLegacyStateBatches > 0 {
				latestBatch := legacyStateBatches[numLegacyStateBatches-1]
				latestL2Height := latestBatch.PrevTotal + latestBatch.Size - 1
				processLog.Info("detected legacy state batches", "size", numLegacyStateBatches, "latest_l2_block_number", latestL2Height)
			}
227

Hamdi Allam's avatar
Hamdi Allam committed
228 229 230 231 232 233 234 235 236
			numOutputProposals := len(outputProposals)
			if numOutputProposals > 0 {
				latestL2Height := outputProposals[numOutputProposals-1].L2BlockNumber.Int
				processLog.Info("detected output proposals", "size", numOutputProposals, "latest_l2_block_number", latestL2Height)
				err := db.Blocks.StoreOutputProposals(outputProposals)
				if err != nil {
					return err
				}
			}
237

238 239 240 241 242 243
			// forward along contract events to bridge txs processor
			err = l1ProcessContractEventsBridgeTransactions(processLog, db, l1Contracts, processedContractEvents)
			if err != nil {
				return err
			}

244 245 246 247 248 249
			// forward along contract events to bridge messages processor
			err = l1ProcessContractEventsBridgeCrossDomainMessages(processLog, db, processedContractEvents)
			if err != nil {
				return err
			}

250 251
			// forward along contract events to standard bridge processor
			err = l1ProcessContractEventsStandardBridge(processLog, db, ethClient, processedContractEvents)
252
			if err != nil {
253
				return err
254
			}
Hamdi Allam's avatar
Hamdi Allam committed
255 256
		} else {
			processLog.Info("no l1 blocks of interest within batch")
257 258
		}

259
		// a-ok!
260
		return nil
261 262
	}
}
Hamdi Allam's avatar
Hamdi Allam committed
263

264 265 266
func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *database.DB, l1Contracts L1Contracts, events *ProcessedContractEvents) error {
	// (1) Process New Deposits
	portalDeposits, err := OptimismPortalTransactionDepositEvents(events)
Hamdi Allam's avatar
Hamdi Allam committed
267 268 269 270
	if err != nil {
		return err
	}

271 272 273 274 275 276 277 278 279 280 281
	ethDeposits := []*database.L1BridgeDeposit{}
	transactionDeposits := make([]*database.L1TransactionDeposit, len(portalDeposits))
	for i, depositEvent := range portalDeposits {
		depositTx := depositEvent.DepositTx
		transactionDeposits[i] = &database.L1TransactionDeposit{
			SourceHash:           depositTx.SourceHash,
			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)},
Hamdi Allam's avatar
Hamdi Allam committed
282
			Tx: database.Transaction{
283 284 285 286 287
				FromAddress: depositTx.From,
				ToAddress:   depositTx.From,
				Amount:      database.U256{Int: depositTx.Value},
				Data:        depositTx.Data,
				Timestamp:   depositEvent.RawEvent.Timestamp,
Hamdi Allam's avatar
Hamdi Allam committed
288
			},
Hamdi Allam's avatar
Hamdi Allam committed
289
		}
290 291 292 293 294 295 296 297 298 299 300 301

		// 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,
				},
			})
		}
Hamdi Allam's avatar
Hamdi Allam committed
302 303
	}

304 305 306
	if len(transactionDeposits) > 0 {
		processLog.Info("detected transaction deposits", "size", len(transactionDeposits))
		err := db.BridgeTransactions.StoreL1TransactionDeposits(transactionDeposits)
Hamdi Allam's avatar
Hamdi Allam committed
307 308 309
		if err != nil {
			return err
		}
310 311 312 313 314 315 316 317

		if len(ethDeposits) > 0 {
			processLog.Info("detected portal ETH transfers", "size", len(ethDeposits))
			err := db.BridgeTransfers.StoreL1BridgeDeposits(ethDeposits)
			if err != nil {
				return err
			}
		}
Hamdi Allam's avatar
Hamdi Allam committed
318 319
	}

320 321
	// (2) Process Proven Withdrawals
	provenWithdrawals, err := OptimismPortalWithdrawalProvenEvents(events)
322 323 324 325
	if err != nil {
		return err
	}

326 327
	latestL2Header, err := db.Blocks.LatestL2BlockHeader()
	if err != nil {
328 329 330
		return nil
	} else if len(provenWithdrawals) > 0 && latestL2Header == nil {
		return errors.New("no indexed L2 headers to prove withdrawals. waiting for L2Processor to catch up")
331 332
	}

333 334 335
	for _, provenWithdrawal := range provenWithdrawals {
		withdrawalHash := provenWithdrawal.WithdrawalHash
		withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash)
336 337
		if err != nil {
			return err
338 339 340
		}

		if withdrawal == nil {
341 342 343 344
			// We need to ensure we are in a caught up state before claiming a missing event. Since L2 timestamps
			// are derived from L1, we can simply compare the timestamp of this event with the latest L2 header.
			if provenWithdrawal.RawEvent.Timestamp > latestL2Header.Timestamp {
				processLog.Warn("behind on indexed L2 withdrawals")
345 346
				return errors.New("waiting for L2Processor to catch up")
			} else {
347
				processLog.Crit("L2 withdrawal missing!", "withdrawal_hash", withdrawalHash)
348
				return errors.New("withdrawal missing!")
349 350 351
			}
		}

352
		err = db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(withdrawalHash, provenWithdrawal.RawEvent.GUID)
353 354 355
		if err != nil {
			return err
		}
356
	}
357

358 359
	if len(provenWithdrawals) > 0 {
		processLog.Info("proven transaction withdrawals", "size", len(provenWithdrawals))
360 361
	}

362 363 364 365
	// (2) Process Withdrawal Finalization
	finalizedWithdrawals, err := OptimismPortalWithdrawalFinalizedEvents(events)
	if err != nil {
		return err
366 367
	}

368 369 370 371 372 373 374 375 376 377 378
	for _, finalizedWithdrawal := range finalizedWithdrawals {
		withdrawalHash := finalizedWithdrawal.WithdrawalHash
		withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash)
		if err != nil {
			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!")
		}

379
		err = db.BridgeTransactions.MarkL2TransactionWithdrawalFinalizedEvent(withdrawalHash, finalizedWithdrawal.RawEvent.GUID, finalizedWithdrawal.Success)
380 381 382 383 384 385 386 387 388 389 390 391 392
		if err != nil {
			return err
		}
	}

	if len(finalizedWithdrawals) > 0 {
		processLog.Info("finalized transaction withdrawals", "size", len(finalizedWithdrawals))
	}

	// a-ok
	return nil
}

393 394 395 396 397 398 399 400 401
func l1ProcessContractEventsBridgeCrossDomainMessages(processLog log.Logger, db *database.DB, events *ProcessedContractEvents) error {
	// (1) Process New Messages
	sentMessageEvents, err := CrossDomainMessengerSentMessageEvents(events)
	if err != nil {
		return err
	}

	sentMessages := make([]*database.L1BridgeMessage, len(sentMessageEvents))
	for i, sentMessageEvent := range sentMessageEvents {
402
		log := sentMessageEvent.RawEvent.GethLog
403 404

		// extract the deposit hash from the previous TransactionDepositedEvent
405
		transactionDepositedLog := events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index - 1}].GethLog
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
		depositTx, err := derive.UnmarshalDepositLogEvent(transactionDepositedLog)
		if err != nil {
			return err
		}

		sentMessages[i] = &database.L1BridgeMessage{
			TransactionSourceHash: depositTx.SourceHash,
			BridgeMessage: database.BridgeMessage{
				Nonce:                database.U256{Int: sentMessageEvent.MessageNonce},
				MessageHash:          sentMessageEvent.MessageHash,
				SentMessageEventGUID: sentMessageEvent.RawEvent.GUID,
				GasLimit:             database.U256{Int: sentMessageEvent.GasLimit},
				Tx: database.Transaction{
					FromAddress: sentMessageEvent.Sender,
					ToAddress:   sentMessageEvent.Target,
					Amount:      database.U256{Int: sentMessageEvent.Value},
					Data:        sentMessageEvent.Message,
					Timestamp:   sentMessageEvent.RawEvent.Timestamp,
				},
			},
		}
	}

	if len(sentMessages) > 0 {
		processLog.Info("detected L1CrossDomainMessenger messages", "size", len(sentMessages))
		err := db.BridgeMessages.StoreL1BridgeMessages(sentMessages)
		if err != nil {
			return err
		}
	}

	// (2) Process Relayed Messages.
	//
	// NOTE: Should we care about failed messages? A failed message can be
	// inferred via a finalized withdrawal that has not been marked as relayed.
	relayedMessageEvents, err := CrossDomainMessengerRelayedMessageEvents(events)
	if err != nil {
		return err
	}

	for _, relayedMessage := range relayedMessageEvents {
		message, err := db.BridgeMessages.L2BridgeMessageByHash(relayedMessage.MsgHash)
		if err != nil {
			return err
		} else if message == nil {
			// Since L2 withdrawals must be proven before being relayed, the transaction processor
			// ensures that we are in indexed state on L2 if we've seen this finalization event
			processLog.Crit("missing indexed L2CrossDomainMessenger sent message", "message_hash", relayedMessage.MsgHash)
			return fmt.Errorf("missing indexed L2CrossDomainMessager mesesage: 0x%x", relayedMessage.MsgHash)
		}

		err = db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MsgHash, relayedMessage.RawEvent.GUID)
		if err != nil {
			return err
		}
	}

	if len(relayedMessageEvents) > 0 {
		processLog.Info("relayed L2CrossDomainMessenger messages", "size", len(relayedMessageEvents))
	}

	// a-ok!
	return nil
}

471 472 473 474 475
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)
476 477 478 479
	if err != nil {
		return err
	}

480 481
	deposits := make([]*database.L1BridgeDeposit, len(initiatedDepositEvents))
	for i, initiatedBridgeEvent := range initiatedDepositEvents {
482
		log := initiatedBridgeEvent.RawEvent.GethLog
483 484

		// extract the deposit hash from the following TransactionDeposited event
485
		transactionDepositedLog := events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].GethLog
486
		depositTx, err := derive.UnmarshalDepositLogEvent(transactionDepositedLog)
487 488 489 490
		if err != nil {
			return err
		}

491 492 493 494 495 496 497 498 499 500 501
		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,
			},
502
		}
503
	}
504

505 506 507
	if len(deposits) > 0 {
		processLog.Info("detected L1StandardBridge deposits", "size", len(deposits))
		err := db.BridgeTransfers.StoreL1BridgeDeposits(deposits)
508 509 510 511 512
		if err != nil {
			return err
		}
	}

513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
	// (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 {
		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")
		}

		// sanity check on the bridge fields
		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!")
		}
538 539 540
	}

	// a-ok!
Hamdi Allam's avatar
Hamdi Allam committed
541 542
	return nil
}