l2_processor.go 4.81 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
	"github.com/google/uuid"
11

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

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

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

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

53 54 55 56
type L2Processor struct {
	processor
}

57
func NewL2Processor(ethClient node.EthClient, db *database.DB, l2Contracts L2Contracts) (*L2Processor, error) {
58
	l2ProcessLog := log.New("processor", "l2")
59
	l2ProcessLog.Info("initializing processor")
60 61 62 63 64 65 66 67

	latestHeader, err := db.Blocks.FinalizedL2BlockHeader()
	if err != nil {
		return nil, err
	}

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

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

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

	return l2Processor, nil
}

93 94 95
func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2Contracts) func(db *database.DB, headers []*types.Header) error {
	rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())

Hamdi Allam's avatar
Hamdi Allam committed
96
	contractAddrs := l2Contracts.toSlice()
97
	processLog.Info("processor configured with contracts", "contracts", l2Contracts)
98
	return func(db *database.DB, headers []*types.Header) error {
99 100
		numHeaders := len(headers)

101
		/** Index All L2 Blocks **/
102 103

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

			l2HeaderMap[blockHash] = header
		}

119
		/** Watch for Contract Events **/
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161

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

		numLogs := len(logs)
		l2ContractEvents := make([]*database.L2ContractEvent, numLogs)
		for i, log := range logs {
			header, ok := l2HeaderMap[log.BlockHash]
			if !ok {
				// Log the individual headers in the batch?
				processLog.Crit("contract event found with associated header not in the batch", "header", header, "log_index", log.Index)
				return errors.New("parsed log with a block hash not in this batch")
			}

			l2ContractEvents[i] = &database.L2ContractEvent{
				ContractEvent: database.ContractEvent{
					GUID:            uuid.New(),
					BlockHash:       log.BlockHash,
					TransactionHash: log.TxHash,
					EventSignature:  log.Topics[0],
					LogIndex:        uint64(log.Index),
					Timestamp:       header.Time,
				},
			}
		}

		/** Update Database **/

		err = db.Blocks.StoreL2BlockHeaders(l2Headers)
		if err != nil {
			return err
		}

		if numLogs > 0 {
			processLog.Info("detected new contract logs", "size", numLogs)
			err = db.ContractEvents.StoreL2ContractEvents(l2ContractEvents)
			if err != nil {
				return err
			}
162 163
		}

164 165
		// a-ok!
		return nil
166 167
	}
}