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

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

10
	"github.com/ethereum-optimism/optimism/indexer/config"
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
	"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
16

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

25 26 27 28 29
type checkpointAbi struct {
	l2OutputOracle             *abi.ABI
	legacyStateCommitmentChain *abi.ABI
}

30 31 32 33
type L1Processor struct {
	processor
}

34
func NewL1Processor(logger log.Logger, ethClient node.EthClient, db *database.DB, l1Contracts config.L1Contracts) (*L1Processor, error) {
35
	l1ProcessLog := logger.New("processor", "l1")
36
	l1ProcessLog.Info("initializing processor")
37

38 39 40 41 42 43 44 45 46 47 48 49 50
	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()
51 52 53 54 55 56
	if err != nil {
		return nil, err
	}

	var fromL1Header *types.Header
	if latestHeader != nil {
57
		l1ProcessLog.Info("detected last indexed block", "height", latestHeader.Number.Int, "hash", latestHeader.Hash)
58 59
		l1Header, err := ethClient.BlockHeaderByHash(latestHeader.Hash)
		if err != nil {
60
			l1ProcessLog.Error("unable to fetch header for last indexed block", "hash", latestHeader.Hash, "err", err)
61 62 63 64 65
			return nil, err
		}

		fromL1Header = l1Header
	} else {
66
		// we shouldn't start from genesis with l1. Need a "genesis" L1 height provided for the rollup
67 68 69 70 71 72
		l1ProcessLog.Info("no indexed state, starting from genesis")
		fromL1Header = nil
	}

	l1Processor := &L1Processor{
		processor: processor{
73
			headerTraversal: node.NewHeaderTraversal(ethClient, fromL1Header),
74 75 76
			db:              db,
			processFn:       l1ProcessFn(l1ProcessLog, ethClient, l1Contracts, checkpointAbi),
			processLog:      l1ProcessLog,
77 78 79 80 81 82
		},
	}

	return l1Processor, nil
}

83
func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts config.L1Contracts, checkpointAbi checkpointAbi) ProcessFn {
84 85
	rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())

86
	contractAddrs := l1Contracts.ToSlice()
87 88
	processLog.Info("processor configured with contracts", "contracts", l1Contracts)

89 90 91 92 93
	outputProposedEventName := "OutputProposed"
	outputProposedEventSig := checkpointAbi.l2OutputOracle.Events[outputProposedEventName].ID

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

95
	return func(db *database.DB, headers []*types.Header) error {
96
		headerMap := make(map[common.Hash]*types.Header)
97
		for _, header := range headers {
98
			headerMap[header.Hash()] = header
99 100
		}

Hamdi Allam's avatar
Hamdi Allam committed
101
		/** Watch for all Optimism Contract Events **/
102

103
		logFilter := ethereum.FilterQuery{FromBlock: headers[0].Number, ToBlock: headers[len(headers)-1].Number, Addresses: contractAddrs}
Hamdi Allam's avatar
Hamdi Allam committed
104
		logs, err := rawEthClient.FilterLogs(context.Background(), logFilter) // []types.Log
105
		if err != nil {
106
			return err
107 108
		}

Hamdi Allam's avatar
Hamdi Allam committed
109
		// L2 checkpoints posted on L1
110 111 112
		outputProposals := []*database.OutputProposal{}
		legacyStateBatches := []*database.LegacyStateBatch{}

113
		l1HeadersOfInterest := make(map[common.Hash]bool)
Hamdi Allam's avatar
Hamdi Allam committed
114 115 116
		l1ContractEvents := make([]*database.L1ContractEvent, len(logs))

		processedContractEvents := NewProcessedContractEvents()
117 118
		for i := range logs {
			log := &logs[i]
119
			header, ok := headerMap[log.BlockHash]
120
			if !ok {
121
				processLog.Error("contract event found with associated header not in the batch", "header", log.BlockHash, "log_index", log.Index)
122
				return errors.New("parsed log with a block hash not in this batch")
123 124
			}

125
			contractEvent := processedContractEvents.AddLog(log, header.Time)
126
			l1HeadersOfInterest[log.BlockHash] = true
Hamdi Allam's avatar
Hamdi Allam committed
127
			l1ContractEvents[i] = &database.L1ContractEvent{ContractEvent: *contractEvent}
128 129 130 131

			// Track Checkpoint Events for L2
			switch contractEvent.EventSignature {
			case outputProposedEventSig:
132 133 134 135
				var outputProposed bindings.L2OutputOracleOutputProposed
				err := UnpackLog(&outputProposed, log, outputProposedEventName, checkpointAbi.l2OutputOracle)
				if err != nil {
					return err
136 137 138
				}

				outputProposals = append(outputProposals, &database.OutputProposal{
139 140 141
					OutputRoot:          outputProposed.OutputRoot,
					L2OutputIndex:       database.U256{Int: outputProposed.L2OutputIndex},
					L2BlockNumber:       database.U256{Int: outputProposed.L2BlockNumber},
142 143 144 145 146
					L1ContractEventGUID: contractEvent.GUID,
				})

			case legacyStateBatchAppendedEventSig:
				var stateBatchAppended legacy_bindings.StateCommitmentChainStateBatchAppended
147 148
				err := UnpackLog(&stateBatchAppended, log, legacyStateBatchAppendedEventName, checkpointAbi.legacyStateCommitmentChain)
				if err != nil {
149
					return err
150 151 152
				}

				legacyStateBatches = append(legacyStateBatches, &database.LegacyStateBatch{
153
					Index:               stateBatchAppended.BatchIndex.Uint64(),
154 155 156 157 158 159
					Root:                stateBatchAppended.BatchRoot,
					Size:                stateBatchAppended.BatchSize.Uint64(),
					PrevTotal:           stateBatchAppended.PrevTotalElements.Uint64(),
					L1ContractEventGUID: contractEvent.GUID,
				})
			}
160 161
		}

162
		/** Aggregate applicable L1 Blocks **/
163

164 165
		// 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
166
		indexedL1Headers := []*database.L1BlockHeader{}
167
		for _, header := range headers {
Hamdi Allam's avatar
Hamdi Allam committed
168 169
			_, hasLogs := l1HeadersOfInterest[header.Hash()]
			if !hasLogs {
170 171 172
				continue
			}

173
			indexedL1Headers = append(indexedL1Headers, &database.L1BlockHeader{BlockHeader: database.BlockHeaderFromHeader(header)})
174 175
		}

176 177
		/** Update Database **/

Hamdi Allam's avatar
Hamdi Allam committed
178 179
		numIndexedL1Headers := len(indexedL1Headers)
		if numIndexedL1Headers > 0 {
180
			processLog.Info("saving l1 blocks with optimism logs", "size", numIndexedL1Headers, "batch_size", len(headers))
Hamdi Allam's avatar
Hamdi Allam committed
181 182 183 184
			err = db.Blocks.StoreL1BlockHeaders(indexedL1Headers)
			if err != nil {
				return err
			}
185

Hamdi Allam's avatar
Hamdi Allam committed
186
			// 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
187
			processLog.Info("detected contract logs", "size", len(l1ContractEvents))
Hamdi Allam's avatar
Hamdi Allam committed
188 189 190 191
			err = db.ContractEvents.StoreL1ContractEvents(l1ContractEvents)
			if err != nil {
				return err
			}
192

Hamdi Allam's avatar
Hamdi Allam committed
193 194 195 196 197 198 199
			// 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)
			}
200

Hamdi Allam's avatar
Hamdi Allam committed
201 202 203 204 205 206 207 208 209
			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
				}
			}
210

211 212 213 214 215 216
			// forward along contract events to bridge txs processor
			err = l1ProcessContractEventsBridgeTransactions(processLog, db, l1Contracts, processedContractEvents)
			if err != nil {
				return err
			}

217 218 219 220 221 222
			// forward along contract events to bridge messages processor
			err = l1ProcessContractEventsBridgeCrossDomainMessages(processLog, db, processedContractEvents)
			if err != nil {
				return err
			}

223
			// forward along contract events to standard bridge processor
224
			err = l1ProcessContractEventsStandardBridge(processLog, db, processedContractEvents)
225
			if err != nil {
226
				return err
227
			}
Hamdi Allam's avatar
Hamdi Allam committed
228 229
		} else {
			processLog.Info("no l1 blocks of interest within batch")
230 231
		}

232
		// a-ok!
233
		return nil
234 235
	}
}
Hamdi Allam's avatar
Hamdi Allam committed
236

237
func l1ProcessContractEventsBridgeTransactions(processLog log.Logger, db *database.DB, l1Contracts config.L1Contracts, events *ProcessedContractEvents) error {
238 239
	// (1) Process New Deposits
	portalDeposits, err := OptimismPortalTransactionDepositEvents(events)
Hamdi Allam's avatar
Hamdi Allam committed
240 241 242 243
	if err != nil {
		return err
	}

244 245 246 247 248 249 250
	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(),
251
			InitiatedL1EventGUID: depositEvent.Event.GUID,
252
			GasLimit:             database.U256{Int: new(big.Int).SetUint64(depositTx.Gas)},
Hamdi Allam's avatar
Hamdi Allam committed
253
			Tx: database.Transaction{
254 255 256 257
				FromAddress: depositTx.From,
				ToAddress:   depositTx.From,
				Amount:      database.U256{Int: depositTx.Value},
				Data:        depositTx.Data,
258
				Timestamp:   depositEvent.Event.Timestamp,
Hamdi Allam's avatar
Hamdi Allam committed
259
			},
Hamdi Allam's avatar
Hamdi Allam committed
260
		}
261 262 263 264 265

		// catch ETH transfers to the portal contract.
		if len(depositTx.Data) == 0 && depositTx.Value.BitLen() > 0 {
			ethDeposits = append(ethDeposits, &database.L1BridgeDeposit{
				TransactionSourceHash: depositTx.SourceHash,
266 267
				BridgeTransfer: database.BridgeTransfer{
					Tx: transactionDeposits[i].Tx,
268
					// TODO index eth token if it doesn't exist
269
					TokenPair: database.ETHTokenPair,
270 271 272
				},
			})
		}
Hamdi Allam's avatar
Hamdi Allam committed
273 274
	}

275 276 277
	if len(transactionDeposits) > 0 {
		processLog.Info("detected transaction deposits", "size", len(transactionDeposits))
		err := db.BridgeTransactions.StoreL1TransactionDeposits(transactionDeposits)
Hamdi Allam's avatar
Hamdi Allam committed
278 279 280
		if err != nil {
			return err
		}
281 282 283 284 285 286 287 288

		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
289 290
	}

291 292
	// (2) Process Proven Withdrawals
	provenWithdrawals, err := OptimismPortalWithdrawalProvenEvents(events)
293 294 295 296
	if err != nil {
		return err
	}

297 298
	latestL2Header, err := db.Blocks.LatestL2BlockHeader()
	if err != nil {
299 300 301
		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")
302 303
	}

304 305 306
	for _, provenWithdrawal := range provenWithdrawals {
		withdrawalHash := provenWithdrawal.WithdrawalHash
		withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash)
307 308
		if err != nil {
			return err
309 310 311
		}

		if withdrawal == nil {
312 313
			// 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.
314
			if provenWithdrawal.Event.Timestamp > latestL2Header.Timestamp {
315
				processLog.Warn("behind on indexed L2 withdrawals")
316 317
				return errors.New("waiting for L2Processor to catch up")
			} else {
318
				processLog.Crit("L2 withdrawal missing!", "withdrawal_hash", withdrawalHash)
319
				return errors.New("withdrawal missing!")
320 321 322
			}
		}

323
		err = db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(withdrawalHash, provenWithdrawal.Event.GUID)
324 325 326
		if err != nil {
			return err
		}
327
	}
328

329 330
	if len(provenWithdrawals) > 0 {
		processLog.Info("proven transaction withdrawals", "size", len(provenWithdrawals))
331 332
	}

333 334 335 336
	// (2) Process Withdrawal Finalization
	finalizedWithdrawals, err := OptimismPortalWithdrawalFinalizedEvents(events)
	if err != nil {
		return err
337 338
	}

339 340 341 342 343 344 345 346 347 348 349
	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!")
		}

350
		err = db.BridgeTransactions.MarkL2TransactionWithdrawalFinalizedEvent(withdrawalHash, finalizedWithdrawal.Event.GUID, finalizedWithdrawal.Success)
351 352 353 354 355 356 357 358 359 360 361 362 363
		if err != nil {
			return err
		}
	}

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

	// a-ok
	return nil
}

364 365 366 367 368 369 370 371 372
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 {
373
		log := sentMessageEvent.Event.RLPLog
374 375

		// extract the deposit hash from the previous TransactionDepositedEvent
376
		transactionDepositedLog := events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index - 1}].RLPLog
377 378 379 380 381 382 383 384 385
		depositTx, err := derive.UnmarshalDepositLogEvent(transactionDepositedLog)
		if err != nil {
			return err
		}

		sentMessages[i] = &database.L1BridgeMessage{
			TransactionSourceHash: depositTx.SourceHash,
			BridgeMessage: database.BridgeMessage{
				MessageHash:          sentMessageEvent.MessageHash,
386
				Nonce:                database.U256{Int: sentMessageEvent.MessageNonce},
387
				SentMessageEventGUID: sentMessageEvent.Event.GUID,
388 389 390 391 392 393
				GasLimit:             database.U256{Int: sentMessageEvent.GasLimit},
				Tx: database.Transaction{
					FromAddress: sentMessageEvent.Sender,
					ToAddress:   sentMessageEvent.Target,
					Amount:      database.U256{Int: sentMessageEvent.Value},
					Data:        sentMessageEvent.Message,
394
					Timestamp:   sentMessageEvent.Event.Timestamp,
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
				},
			},
		}
	}

	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 {
418
		message, err := db.BridgeMessages.L2BridgeMessage(relayedMessage.MsgHash)
419 420 421 422 423 424 425 426 427
		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)
		}

428
		err = db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MsgHash, relayedMessage.Event.GUID)
429 430 431 432 433 434 435 436 437 438 439 440 441
		if err != nil {
			return err
		}
	}

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

	// a-ok!
	return nil
}

442
func l1ProcessContractEventsStandardBridge(processLog log.Logger, db *database.DB, events *ProcessedContractEvents) error {
443 444
	// (1) Process New Deposits
	initiatedDepositEvents, err := StandardBridgeInitiatedEvents(events)
445 446 447 448
	if err != nil {
		return err
	}

449 450
	deposits := make([]*database.L1BridgeDeposit, len(initiatedDepositEvents))
	for i, initiatedBridgeEvent := range initiatedDepositEvents {
451
		log := initiatedBridgeEvent.Event.RLPLog
452

453 454 455 456
		// extract the deposit hash from the following TransactionDeposited event. The `BlockHash` and `LogIndex`
		// fields are filled in for `RLPLog` which is required for `DepositTx#SourceHash` to be computed correctly
		transactionDepositedRLPLog := events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].RLPLog
		depositTx, err := derive.UnmarshalDepositLogEvent(transactionDepositedRLPLog)
457 458 459 460
		if err != nil {
			return err
		}

461
		deposits[i] = &database.L1BridgeDeposit{
462 463 464 465
			TransactionSourceHash: depositTx.SourceHash,
			BridgeTransfer: database.BridgeTransfer{
				CrossDomainMessageHash: &initiatedBridgeEvent.CrossDomainMessageHash,
				// TODO index the tokens pairs if they don't exist
Hamdi Allam's avatar
Hamdi Allam committed
466
				TokenPair: database.TokenPair{LocalTokenAddress: initiatedBridgeEvent.LocalToken, RemoteTokenAddress: initiatedBridgeEvent.RemoteToken},
467 468 469 470 471 472 473
				Tx: database.Transaction{
					FromAddress: initiatedBridgeEvent.From,
					ToAddress:   initiatedBridgeEvent.To,
					Amount:      database.U256{Int: initiatedBridgeEvent.Amount},
					Data:        initiatedBridgeEvent.ExtraData,
					Timestamp:   initiatedBridgeEvent.Event.Timestamp,
				},
474
			},
475
		}
476
	}
477

478 479 480
	if len(deposits) > 0 {
		processLog.Info("detected L1StandardBridge deposits", "size", len(deposits))
		err := db.BridgeTransfers.StoreL1BridgeDeposits(deposits)
481 482 483 484 485
		if err != nil {
			return err
		}
	}

486 487 488 489
	// (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
490
	finalizedWithdrawalEvents, err := StandardBridgeFinalizedEvents(events)
491 492 493 494 495
	if err != nil {
		return err
	}

	for _, finalizedWithdrawalEvent := range finalizedWithdrawalEvents {
496
		withdrawal, err := db.BridgeTransfers.L2BridgeWithdrawalWithFilter(database.BridgeTransfer{CrossDomainMessageHash: &finalizedWithdrawalEvent.CrossDomainMessageHash})
497 498 499
		if err != nil {
			return err
		} else if withdrawal == nil {
500
			processLog.Error("missing indexed L2StandardBridge withdrawal for finalization", "cross_domain_message_hash", finalizedWithdrawalEvent.CrossDomainMessageHash)
501 502 503 504 505 506
			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) ||
Hamdi Allam's avatar
Hamdi Allam committed
507
			finalizedWithdrawalEvent.LocalToken != withdrawal.TokenPair.LocalTokenAddress || finalizedWithdrawalEvent.RemoteToken != withdrawal.TokenPair.RemoteTokenAddress {
508
			processLog.Crit("bridge finalization fields mismatch with initiated fields!", "tx_withdrawal_hash", withdrawal.TransactionWithdrawalHash, "cross_domain_message_hash", withdrawal.CrossDomainMessageHash)
509
		}
510 511 512
	}

	// a-ok!
Hamdi Allam's avatar
Hamdi Allam committed
513 514
	return nil
}