l2_processor.go 8.31 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 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 221 222
	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
	}

	withdrawals := make([]*database.Withdrawal, len(initiatedWithdrawalEvents))
	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
		}

		withdrawals[i] = &database.Withdrawal{
			GUID:                 uuid.New(),
			InitiatedL2EventGUID: initiatedBridgeEvent.RawEvent.GUID,
			SentMessageNonce:     database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
			WithdrawalHash:       msgPassedData.WithdrawalHash,
			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,
			},
		}
	}

	if len(withdrawals) > 0 {
		processLog.Info("detected L2StandardBridge withdrawals", "num", len(withdrawals))
		err := db.Bridge.StoreWithdrawals(withdrawals)
		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
	}

Hamdi Allam's avatar
Hamdi Allam committed
228 229
	for _, finalizedBridgeEvent := range finalizationBridgeEvents {
		nonce := finalizedBridgeEvent.CrossDomainMessengerNonce
Hamdi Allam's avatar
Hamdi Allam committed
230

Hamdi Allam's avatar
Hamdi Allam committed
231 232 233 234 235 236
		deposit, err := db.Bridge.DepositByMessageNonce(nonce)
		if err != nil {
			processLog.Error("error querying associated deposit messsage using nonce", "cross_domain_messenger_nonce", nonce)
			return err
		} else if deposit == nil {
			latestNonce, err := db.Bridge.LatestDepositMessageNonce()
Hamdi Allam's avatar
Hamdi Allam committed
237 238 239 240
			if err != nil {
				return err
			}

241
			// Check if the L1Processor is behind or really has missed an event
Hamdi Allam's avatar
Hamdi Allam committed
242
			if latestNonce == nil || nonce.Cmp(latestNonce) > 0 {
243
				processLog.Warn("behind on indexed L1 deposits")
Hamdi Allam's avatar
Hamdi Allam committed
244 245
				return errors.New("waiting for L1Processor to catch up")
			} else {
246
				processLog.Crit("missing indexed deposit for this finalization event")
Hamdi Allam's avatar
Hamdi Allam committed
247
				return errors.New("missing deposit message")
Hamdi Allam's avatar
Hamdi Allam committed
248
			}
Hamdi Allam's avatar
Hamdi Allam committed
249
		}
Hamdi Allam's avatar
Hamdi Allam committed
250

Hamdi Allam's avatar
Hamdi Allam committed
251 252 253 254
		err = db.Bridge.MarkFinalizedDepositEvent(deposit.GUID, finalizedBridgeEvent.RawEvent.GUID)
		if err != nil {
			processLog.Error("error finalizing deposit", "err", err)
			return err
Hamdi Allam's avatar
Hamdi Allam committed
255 256 257
		}
	}

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

Hamdi Allam's avatar
Hamdi Allam committed
262
	// a-ok
Hamdi Allam's avatar
Hamdi Allam committed
263 264
	return nil
}