l2_processor.go 4.8 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
	latestHeader, err := db.Blocks.LatestL2BlockHeader()
62 63 64 65 66 67
	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
			return nil, err
		}

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

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

	return l2Processor, nil
}

93
func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2Contracts) ProcessFn {
94 95
	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

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

		numLogs := len(logs)
		l2ContractEvents := make([]*database.L2ContractEvent, numLogs)
		for i, log := range logs {
			header, ok := l2HeaderMap[log.BlockHash]
			if !ok {
132
				processLog.Error("contract event found with associated header not in the batch", "header", header, "log_index", log.Index)
133
				return errors.New("parsed log with a block hash not in this batch")
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
			}

			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 **/

150
		processLog.Info("saving l2 blocks", "size", numHeaders)
151 152
		err = db.Blocks.StoreL2BlockHeaders(l2Headers)
		if err != nil {
153
			return err
154 155 156
		}

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

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