l2_processor.go 14.7 KB
Newer Older
1 2 3
package processor

import (
4
	"bytes"
5 6
	"context"
	"errors"
7
	"fmt"
8 9
	"reflect"

10 11
	"github.com/ethereum-optimism/optimism/indexer/database"
	"github.com/ethereum-optimism/optimism/indexer/node"
12
	"github.com/ethereum-optimism/optimism/op-bindings/bindings"
13
	"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
14

15 16
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/common"
17
	"github.com/ethereum/go-ethereum/core/types"
18
	"github.com/ethereum/go-ethereum/ethclient"
19 20 21
	"github.com/ethereum/go-ethereum/log"
)

22 23 24 25 26 27 28
type L2Contracts struct {
	L2CrossDomainMessenger common.Address
	L2StandardBridge       common.Address
	L2ERC721Bridge         common.Address
	L2ToL1MessagePasser    common.Address

	// Some more contracts -- ProxyAdmin, SystemConfig, etcc
29
	// Ignore the auxiliary contracts?
30 31 32 33 34 35 36 37 38 39 40 41 42 43

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

func L2ContractPredeploys() L2Contracts {
	return L2Contracts{
		L2CrossDomainMessenger: common.HexToAddress("0x4200000000000000000000000000000000000007"),
		L2StandardBridge:       common.HexToAddress("0x4200000000000000000000000000000000000010"),
		L2ERC721Bridge:         common.HexToAddress("0x4200000000000000000000000000000000000014"),
		L2ToL1MessagePasser:    common.HexToAddress("0x4200000000000000000000000000000000000016"),
	}
}

44
func (c L2Contracts) ToSlice() []common.Address {
45 46 47 48 49 50 51 52 53 54 55
	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
}

56 57 58 59
type L2Processor struct {
	processor
}

60 61
func NewL2Processor(logger log.Logger, ethClient node.EthClient, db *database.DB, l2Contracts L2Contracts) (*L2Processor, error) {
	l2ProcessLog := logger.New("processor", "l2")
62
	l2ProcessLog.Info("initializing processor")
63

64
	latestHeader, err := db.Blocks.LatestL2BlockHeader()
65 66 67 68 69 70
	if err != nil {
		return nil, err
	}

	var fromL2Header *types.Header
	if latestHeader != nil {
71
		l2ProcessLog.Info("detected last indexed block", "height", latestHeader.Number.Int, "hash", latestHeader.Hash)
72 73
		l2Header, err := ethClient.BlockHeaderByHash(latestHeader.Hash)
		if err != nil {
74
			l2ProcessLog.Error("unable to fetch header for last indexed block", "hash", latestHeader.Hash, "err", err)
75 76 77 78 79 80 81 82 83 84 85
			return nil, err
		}

		fromL2Header = l2Header
	} else {
		l2ProcessLog.Info("no indexed state, starting from genesis")
		fromL2Header = nil
	}

	l2Processor := &L2Processor{
		processor: processor{
86
			headerTraversal: node.NewHeaderTraversal(ethClient, fromL2Header),
87 88 89
			db:              db,
			processFn:       l2ProcessFn(l2ProcessLog, ethClient, l2Contracts),
			processLog:      l2ProcessLog,
90 91 92 93 94 95
		},
	}

	return l2Processor, nil
}

96
func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2Contracts) ProcessFn {
97 98
	rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())

99
	contractAddrs := l2Contracts.ToSlice()
100
	processLog.Info("processor configured with contracts", "contracts", l2Contracts)
101
	return func(db *database.DB, headers []*types.Header) error {
102 103
		numHeaders := len(headers)

104
		/** Index all L2 blocks **/
105 106

		l2Headers := make([]*database.L2BlockHeader, len(headers))
107
		l2HeaderMap := make(map[common.Hash]*types.Header)
108
		for i, header := range headers {
109
			blockHash := header.Hash()
110 111
			l2Headers[i] = &database.L2BlockHeader{
				BlockHeader: database.BlockHeader{
112
					Hash:       blockHash,
113 114 115 116 117
					ParentHash: header.ParentHash,
					Number:     database.U256{Int: header.Number},
					Timestamp:  header.Time,
				},
			}
118 119 120 121

			l2HeaderMap[blockHash] = header
		}

122
		/** Watch for Contract Events **/
123 124 125 126

		logFilter := ethereum.FilterQuery{FromBlock: headers[0].Number, ToBlock: headers[numHeaders-1].Number, Addresses: contractAddrs}
		logs, err := rawEthClient.FilterLogs(context.Background(), logFilter)
		if err != nil {
127
			return err
128 129
		}

Hamdi Allam's avatar
Hamdi Allam committed
130 131
		l2ContractEvents := make([]*database.L2ContractEvent, len(logs))
		processedContractEvents := NewProcessedContractEvents()
132 133
		for i := range logs {
			log := &logs[i]
134 135
			header, ok := l2HeaderMap[log.BlockHash]
			if !ok {
136
				processLog.Error("contract event found with associated header not in the batch", "header", header, "log_index", log.Index)
137
				return errors.New("parsed log with a block hash not in this batch")
138 139
			}

140
			contractEvent := processedContractEvents.AddLog(log, header.Time)
Hamdi Allam's avatar
Hamdi Allam committed
141
			l2ContractEvents[i] = &database.L2ContractEvent{ContractEvent: *contractEvent}
142 143 144 145
		}

		/** Update Database **/

146
		processLog.Info("saving l2 blocks", "size", numHeaders)
147 148
		err = db.Blocks.StoreL2BlockHeaders(l2Headers)
		if err != nil {
149
			return err
150 151
		}

Hamdi Allam's avatar
Hamdi Allam committed
152
		numLogs := len(l2ContractEvents)
153
		if numLogs > 0 {
154
			processLog.Info("detected contract logs", "size", numLogs)
155 156
			err = db.ContractEvents.StoreL2ContractEvents(l2ContractEvents)
			if err != nil {
157
				return err
158
			}
Hamdi Allam's avatar
Hamdi Allam committed
159

160 161 162 163 164 165
			// forward along contract events to bridge txs processor
			err = l2ProcessContractEventsBridgeTransactions(processLog, db, processedContractEvents)
			if err != nil {
				return err
			}

166 167 168 169 170
			err = l2ProcessContractEventsBridgeCrossDomainMessages(processLog, db, processedContractEvents)
			if err != nil {
				return err
			}

171 172
			// forward along contract events to standard bridge processor
			err = l2ProcessContractEventsStandardBridge(processLog, db, ethClient, processedContractEvents)
Hamdi Allam's avatar
Hamdi Allam committed
173 174 175
			if err != nil {
				return err
			}
176 177
		}

178
		// a-ok!
179
		return nil
180 181
	}
}
Hamdi Allam's avatar
Hamdi Allam committed
182

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
func l2ProcessContractEventsBridgeTransactions(processLog log.Logger, db *database.DB, events *ProcessedContractEvents) error {
	// (1) Process New Withdrawals
	messagesPassed, err := L2ToL1MessagePasserMessagesPassed(events)
	if err != nil {
		return err
	}

	ethWithdrawals := []*database.L2BridgeWithdrawal{}
	transactionWithdrawals := make([]*database.L2TransactionWithdrawal, len(messagesPassed))
	for i, withdrawalEvent := range messagesPassed {
		transactionWithdrawals[i] = &database.L2TransactionWithdrawal{
			WithdrawalHash:       withdrawalEvent.WithdrawalHash,
			InitiatedL2EventGUID: withdrawalEvent.RawEvent.GUID,
			Nonce:                database.U256{Int: withdrawalEvent.Nonce},
			GasLimit:             database.U256{Int: withdrawalEvent.GasLimit},
			Tx: database.Transaction{
				FromAddress: withdrawalEvent.Sender,
				ToAddress:   withdrawalEvent.Target,
				Amount:      database.U256{Int: withdrawalEvent.Value},
				Data:        withdrawalEvent.Data,
				Timestamp:   withdrawalEvent.RawEvent.Timestamp,
			},
		}

		if len(withdrawalEvent.Data) == 0 && withdrawalEvent.Value.BitLen() > 0 {
			ethWithdrawals = append(ethWithdrawals, &database.L2BridgeWithdrawal{
				TransactionWithdrawalHash: withdrawalEvent.WithdrawalHash,
				Tx:                        transactionWithdrawals[i].Tx,
				TokenPair: database.TokenPair{
					L1TokenAddress: predeploys.LegacyERC20ETHAddr,
					L2TokenAddress: predeploys.LegacyERC20ETHAddr,
				},
			})
		}
	}

	if len(transactionWithdrawals) > 0 {
		processLog.Info("detected transaction withdrawals", "size", len(transactionWithdrawals))
Hamdi Allam's avatar
Hamdi Allam committed
221 222 223 224
		err := db.BridgeTransactions.StoreL2TransactionWithdrawals(transactionWithdrawals)
		if err != nil {
			return err
		}
225 226 227 228 229 230 231 232 233 234 235 236

		if len(ethWithdrawals) > 0 {
			processLog.Info("detected L2ToL1MessagePasser ETH transfers", "size", len(ethWithdrawals))
			err := db.BridgeTransfers.StoreL2BridgeWithdrawals(ethWithdrawals)
			if err != nil {
				return err
			}
		}
	}

	// (2) Process Deposit Finalization
	//  - Since L2 deposits are apart of the block derivation processes, we dont track finalization as it's too tricky
Hamdi Allam's avatar
Hamdi Allam committed
237
	// to do so purely from the L2-side since there is not a way to easily identify deposit transactions on L2 without walking
238 239 240 241 242 243
	// the transaction list of every L2 epoch.

	// a-ok!
	return nil
}

244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
func l2ProcessContractEventsBridgeCrossDomainMessages(processLog log.Logger, db *database.DB, events *ProcessedContractEvents) error {
	l2ToL1MessagePasserABI, err := bindings.NewL2ToL1MessagePasser(common.Address{}, nil)
	if err != nil {
		return err
	}

	// (2) Process New Messages
	sentMessageEvents, err := CrossDomainMessengerSentMessageEvents(events)
	if err != nil {
		return err
	}

	sentMessages := make([]*database.L2BridgeMessage, len(sentMessageEvents))
	for i, sentMessageEvent := range sentMessageEvents {
		log := events.eventLog[sentMessageEvent.RawEvent.GUID]

		// extract the withdrawal hash from the previous MessagePassed event
		msgPassedLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index - 1}].GUID]
		msgPassedEvent, err := l2ToL1MessagePasserABI.ParseMessagePassed(*msgPassedLog)
		if err != nil {
			return err
		}

		sentMessages[i] = &database.L2BridgeMessage{
			TransactionWithdrawalHash: msgPassedEvent.WithdrawalHash,
			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 L2CrossDomainMessenger messages", "size", len(sentMessages))
		err := db.BridgeMessages.StoreL2BridgeMessages(sentMessages)
		if err != nil {
			return err
		}
	}

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

	latestL1Header, err := db.Blocks.LatestL1BlockHeader()
	if err != nil {
		return err
	} else if len(relayedMessageEvents) > 0 && latestL1Header == nil {
		return errors.New("no indexed L1 headers to relay messages. waiting for L1Processor to catch up")
	}

	for _, relayedMessage := range relayedMessageEvents {
		message, err := db.BridgeMessages.L1BridgeMessageByHash(relayedMessage.MsgHash)
		if err != nil {
			return err
		}

		if message == nil {
			// Since the transaction processor running prior does not ensure the deposit inclusion, 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 L1 header.
			if latestL1Header == nil || relayedMessage.RawEvent.Timestamp > latestL1Header.Timestamp {
				processLog.Warn("waiting for L1Processor to catch up on L1CrossDomainMessages")
				return errors.New("waiting for L1Processor to catch up")
			} else {
				processLog.Crit("missing indexed L1CrossDomainMessenger message", "message_hash", relayedMessage.MsgHash)
				return fmt.Errorf("missing indexed L1CrossDomainMessager mesesage: 0x%x", relayedMessage.MsgHash)
			}
		}

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

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

	// a-ok!
	return nil
}

342
func l2ProcessContractEventsStandardBridge(processLog log.Logger, db *database.DB, ethClient node.EthClient, events *ProcessedContractEvents) error {
Hamdi Allam's avatar
Hamdi Allam committed
343 344
	rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())

345
	l2ToL1MessagePasserABI, err := bindings.NewL2ToL1MessagePasser(common.Address{}, nil)
346 347 348 349
	if err != nil {
		return err
	}

350
	// (1) Process New Withdrawals
351 352 353 354 355
	initiatedWithdrawalEvents, err := StandardBridgeInitiatedEvents(events)
	if err != nil {
		return err
	}

356
	withdrawals := make([]*database.L2BridgeWithdrawal, len(initiatedWithdrawalEvents))
357 358 359
	for i, initiatedBridgeEvent := range initiatedWithdrawalEvents {
		log := events.eventLog[initiatedBridgeEvent.RawEvent.GUID]

360
		// extract the withdrawal hash from the following MessagePassed event
361
		msgPassedLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].GUID]
362
		msgPassedEvent, err := l2ToL1MessagePasserABI.ParseMessagePassed(*msgPassedLog)
363 364 365 366
		if err != nil {
			return err
		}

367
		withdrawals[i] = &database.L2BridgeWithdrawal{
368 369
			TransactionWithdrawalHash: msgPassedEvent.WithdrawalHash,
			CrossDomainMessengerNonce: &database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
370
			TokenPair:                 database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
371 372 373 374 375 376 377 378 379 380 381 382
			Tx: database.Transaction{
				FromAddress: initiatedBridgeEvent.From,
				ToAddress:   initiatedBridgeEvent.To,
				Amount:      database.U256{Int: initiatedBridgeEvent.Amount},
				Data:        initiatedBridgeEvent.ExtraData,
				Timestamp:   initiatedBridgeEvent.RawEvent.Timestamp,
			},
		}
	}

	if len(withdrawals) > 0 {
		processLog.Info("detected L2StandardBridge withdrawals", "num", len(withdrawals))
383
		err := db.BridgeTransfers.StoreL2BridgeWithdrawals(withdrawals)
384 385 386 387 388
		if err != nil {
			return err
		}
	}

389 390 391 392
	// (2) Process Finalized Deposits
	//  - 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 deposits exist as an integrity check
Hamdi Allam's avatar
Hamdi Allam committed
393

394
	finalizedDepositEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events)
395 396 397 398
	if err != nil {
		return err
	}

399 400
	for _, finalizedDepositEvent := range finalizedDepositEvents {
		deposit, err := db.BridgeTransfers.L1BridgeDepositByCrossDomainMessengerNonce(finalizedDepositEvent.CrossDomainMessengerNonce)
Hamdi Allam's avatar
Hamdi Allam committed
401 402 403
		if err != nil {
			return err
		} else if deposit == nil {
404
			// Indexed CrossDomainMessenger messages ensure we're in a caught up state here
405 406
			processLog.Error("missing indexed L1StandardBridge deposit on finalization", "cross_domain_messenger_nonce", finalizedDepositEvent.CrossDomainMessengerNonce)
			return errors.New("missing indexed L1StandardBridge deposit on finalization")
Hamdi Allam's avatar
Hamdi Allam committed
407
		}
Hamdi Allam's avatar
Hamdi Allam committed
408

409 410 411 412 413 414
		// sanity check on the bridge fields
		if finalizedDepositEvent.From != deposit.Tx.FromAddress || finalizedDepositEvent.To != deposit.Tx.ToAddress ||
			finalizedDepositEvent.Amount.Cmp(deposit.Tx.Amount.Int) != 0 || !bytes.Equal(finalizedDepositEvent.ExtraData, deposit.Tx.Data) ||
			finalizedDepositEvent.LocalToken != deposit.TokenPair.L1TokenAddress || finalizedDepositEvent.RemoteToken != deposit.TokenPair.L2TokenAddress {
			processLog.Error("bridge finalization fields mismatch with initiated fields!", "tx_source_hash", deposit.TransactionSourceHash, "cross_domain_messenger_nonce", deposit.CrossDomainMessengerNonce.Int)
			return errors.New("bridge tx mismatch")
Hamdi Allam's avatar
Hamdi Allam committed
415 416 417
		}
	}

418
	// a-ok!
Hamdi Allam's avatar
Hamdi Allam committed
419 420
	return nil
}