l2_processor.go 8.28 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"),
	}
}

Hamdi Allam's avatar
Hamdi Allam committed
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
func NewL2Processor(ethClient node.EthClient, db *database.DB, l2Contracts L2Contracts) (*L2Processor, error) {
59
	l2ProcessLog := log.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())

Hamdi Allam's avatar
Hamdi Allam committed
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 132
		for i, log := range logs {
			header, ok := l2HeaderMap[log.BlockHash]
			if !ok {
133
				processLog.Error("contract event found with associated header not in the batch", "header", header, "log_index", log.Index)
134
				return errors.New("parsed log with a block hash not in this batch")
135 136
			}

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

		/** Update Database **/

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

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

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

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

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

172 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
	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
222
	finalizationBridgeEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events)
Hamdi Allam's avatar
Hamdi Allam committed
223 224 225 226
	if err != nil {
		return err
	}

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

Hamdi Allam's avatar
Hamdi Allam committed
230 231 232 233 234 235
		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
236 237 238 239
			if err != nil {
				return err
			}

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

Hamdi Allam's avatar
Hamdi Allam committed
250 251 252 253
		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
254 255 256
		}
	}

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

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