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

import (
4 5 6 7
	"context"
	"errors"
	"reflect"

8 9
	"github.com/ethereum-optimism/optimism/indexer/database"
	"github.com/ethereum-optimism/optimism/indexer/node"
10 11
	"github.com/ethereum-optimism/optimism/op-bindings/bindings"
	"github.com/google/uuid"
12

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

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

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

	// 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"),
	}
}

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

54 55 56 57
type L2Processor struct {
	processor
}

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

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

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

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

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

	return l2Processor, nil
}

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

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

102
		/** Index all L2 blocks **/
103 104

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

			l2HeaderMap[blockHash] = header
		}

120
		/** Watch for Contract Events **/
121 122 123 124

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

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

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

		/** Update Database **/

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

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

			// forward along contract events to the bridge processor
Hamdi Allam's avatar
Hamdi Allam committed
159
			err = l2BridgeProcessContractEvents(processLog, db, ethClient, processedContractEvents)
Hamdi Allam's avatar
Hamdi Allam committed
160 161 162
			if err != nil {
				return err
			}
163 164
		}

165
		// a-ok!
166
		return nil
167 168
	}
}
Hamdi Allam's avatar
Hamdi Allam committed
169

Hamdi Allam's avatar
Hamdi Allam committed
170
func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethClient node.EthClient, events *ProcessedContractEvents) error {
Hamdi Allam's avatar
Hamdi Allam committed
171 172
	rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())

173 174 175 176 177 178 179 180 181 182 183 184 185
	l2ToL1MessagePasserABI, err := bindings.L2ToL1MessagePasserMetaData.GetAbi()
	if err != nil {
		return err
	}

	messagePassedEventAbi := l2ToL1MessagePasserABI.Events["MessagePassed"]

	// Process New Withdrawals
	initiatedWithdrawalEvents, err := StandardBridgeInitiatedEvents(events)
	if err != nil {
		return err
	}

186
	withdrawals := make([]*database.L2BridgeWithdrawal, len(initiatedWithdrawalEvents))
187 188 189 190 191 192 193 194 195 196 197
	for i, initiatedBridgeEvent := range initiatedWithdrawalEvents {
		log := events.eventLog[initiatedBridgeEvent.RawEvent.GUID]

		// extract the withdrawal hash from the MessagePassed event
		var msgPassedData bindings.L2ToL1MessagePasserMessagePassed
		msgPassedLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].GUID]
		err := UnpackLog(&msgPassedData, msgPassedLog, messagePassedEventAbi.Name, l2ToL1MessagePasserABI)
		if err != nil {
			return err
		}

198 199 200 201 202 203
		withdrawals[i] = &database.L2BridgeWithdrawal{
			GUID:                      uuid.New(),
			InitiatedL2EventGUID:      initiatedBridgeEvent.RawEvent.GUID,
			CrossDomainMessengerNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
			WithdrawalHash:            msgPassedData.WithdrawalHash,
			TokenPair:                 database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
204 205 206 207 208 209 210 211 212 213 214 215
			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))
216
		err := db.BridgeTransfers.StoreL2BridgeWithdrawals(withdrawals)
217 218 219 220 221 222
		if err != nil {
			return err
		}
	}

	// Finalize Deposits
Hamdi Allam's avatar
Hamdi Allam committed
223
	finalizationBridgeEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events)
Hamdi Allam's avatar
Hamdi Allam committed
224 225 226 227
	if err != nil {
		return err
	}

228 229 230 231 232 233 234
	latestL1Header, err := db.Blocks.LatestL1BlockHeader()
	if err != nil {
		return err
	} else if len(finalizationBridgeEvents) > 0 && latestL1Header == nil {
		return errors.New("no indexed L1 state to process any L2 bridge finalizations")
	}

Hamdi Allam's avatar
Hamdi Allam committed
235 236
	for _, finalizedBridgeEvent := range finalizationBridgeEvents {
		nonce := finalizedBridgeEvent.CrossDomainMessengerNonce
Hamdi Allam's avatar
Hamdi Allam committed
237

238
		deposit, err := db.BridgeTransfers.L1BridgeDepositByCrossDomainMessengerNonce(nonce)
Hamdi Allam's avatar
Hamdi Allam committed
239 240 241 242
		if err != nil {
			processLog.Error("error querying associated deposit messsage using nonce", "cross_domain_messenger_nonce", nonce)
			return err
		} else if deposit == nil {
243 244 245 246
			// Check if the L1Processor is behind or really has missed an event. Since L2 timestamps
			// are derived from L1, we can simply compare timestamps
			if finalizedBridgeEvent.RawEvent.Timestamp > latestL1Header.Timestamp {
				processLog.Warn("behind on indexed L1StandardBridge deposits")
Hamdi Allam's avatar
Hamdi Allam committed
247 248
				return errors.New("waiting for L1Processor to catch up")
			} else {
249
				processLog.Crit("missing indexed L1StandardBridge deposit for this finalization event")
Hamdi Allam's avatar
Hamdi Allam committed
250
				return errors.New("missing deposit message")
Hamdi Allam's avatar
Hamdi Allam committed
251
			}
Hamdi Allam's avatar
Hamdi Allam committed
252
		}
Hamdi Allam's avatar
Hamdi Allam committed
253

254
		err = db.BridgeTransfers.MarkFinalizedL1BridgeDepositEvent(deposit.GUID, finalizedBridgeEvent.RawEvent.GUID)
Hamdi Allam's avatar
Hamdi Allam committed
255 256 257
		if err != nil {
			processLog.Error("error finalizing deposit", "err", err)
			return err
Hamdi Allam's avatar
Hamdi Allam committed
258 259 260
		}
	}

261 262
	if len(finalizationBridgeEvents) > 0 {
		processLog.Info("finalized L1StandardBridge deposits", "size", len(finalizationBridgeEvents))
Hamdi Allam's avatar
Hamdi Allam committed
263 264
	}

Hamdi Allam's avatar
Hamdi Allam committed
265
	// a-ok
Hamdi Allam's avatar
Hamdi Allam committed
266 267
	return nil
}