l1_processor.go 5.25 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 26
type L1Contracts struct {
	OptimismPortal         common.Address
	L2OutputOracle         common.Address
	L1CrossDomainMessenger common.Address
	L1StandardBridge       common.Address
	L1ERC721Bridge         common.Address

	// Some more contracts -- ProxyAdmin, SystemConfig, etcc
27
	// Ignore the auxiliary contracts?
28 29 30 31 32

	// Legacy contracts? We'll add this in to index the legacy chain.
	// Remove afterwards?
}

Hamdi Allam's avatar
Hamdi Allam committed
33
func (c L1Contracts) toSlice() []common.Address {
34 35 36 37 38 39 40 41 42 43 44
	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
}

45 46 47 48
type L1Processor struct {
	processor
}

49
func NewL1Processor(ethClient node.EthClient, db *database.DB, l1Contracts L1Contracts) (*L1Processor, error) {
50
	l1ProcessLog := log.New("processor", "l1")
51
	l1ProcessLog.Info("initializing processor")
52 53 54 55 56 57 58 59

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

	var fromL1Header *types.Header
	if latestHeader != nil {
60
		l1ProcessLog.Info("detected last indexed block", "height", latestHeader.Number.Int, "hash", latestHeader.Hash)
61 62
		l1Header, err := ethClient.BlockHeaderByHash(latestHeader.Hash)
		if err != nil {
63
			l1ProcessLog.Error("unable to fetch header for last indexed block", "hash", latestHeader.Hash, "err", err)
64 65 66 67 68 69 70 71 72 73 74 75 76 77
			return nil, err
		}

		fromL1Header = l1Header
	} else {
		// we shouldn't start from genesis with l1. Need a "genesis" height to be defined here
		l1ProcessLog.Info("no indexed state, starting from genesis")
		fromL1Header = nil
	}

	l1Processor := &L1Processor{
		processor: processor{
			fetcher:    node.NewFetcher(ethClient, fromL1Header),
			db:         db,
78
			processFn:  l1ProcessFn(l1ProcessLog, ethClient, l1Contracts),
79 80 81 82 83 84 85
			processLog: l1ProcessLog,
		},
	}

	return l1Processor, nil
}

86 87 88
func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1Contracts) func(db *database.DB, headers []*types.Header) error {
	rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())

Hamdi Allam's avatar
Hamdi Allam committed
89
	contractAddrs := l1Contracts.toSlice()
90 91
	processLog.Info("processor configured with contracts", "contracts", l1Contracts)

92
	return func(db *database.DB, headers []*types.Header) error {
93 94
		numHeaders := len(headers)
		l1HeaderMap := make(map[common.Hash]*types.Header)
95 96
		for _, header := range headers {
			l1HeaderMap[header.Hash()] = header
97 98
		}

99
		/** Watch for Contract Events **/
100 101 102 103 104 105 106 107 108

		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)
		l1ContractEvents := make([]*database.L1ContractEvent, numLogs)
109
		l1HeadersOfInterest := make(map[common.Hash]bool)
110 111 112
		for i, log := range logs {
			header, ok := l1HeaderMap[log.BlockHash]
			if !ok {
Hamdi Allam's avatar
Hamdi Allam committed
113
				processLog.Crit("contract event found with associated header not in the batch", "header", log.BlockHash, "log_index", log.Index)
114 115 116
				return errors.New("parsed log with a block hash not in this batch")
			}

117
			l1HeadersOfInterest[log.BlockHash] = true
118 119 120 121 122 123 124 125 126 127 128 129
			l1ContractEvents[i] = &database.L1ContractEvent{
				ContractEvent: database.ContractEvent{
					GUID:            uuid.New(),
					BlockHash:       log.BlockHash,
					TransactionHash: log.TxHash,
					EventSignature:  log.Topics[0],
					LogIndex:        uint64(log.Index),
					Timestamp:       header.Time,
				},
			}
		}

130
		/** Index L1 Blocks that have an optimism event **/
131

132 133
		// we iterate on the original array to maintain ordering. probably can find a more efficient
		// way to iterate over the `l1HeadersOfInterest` map while maintaining ordering
Hamdi Allam's avatar
Hamdi Allam committed
134
		indexedL1Header := []*database.L1BlockHeader{}
135 136
		for _, header := range headers {
			blockHash := header.Hash()
Hamdi Allam's avatar
Hamdi Allam committed
137 138
			_, hasLogs := l1HeadersOfInterest[blockHash]
			if !hasLogs {
139 140 141
				continue
			}

Hamdi Allam's avatar
Hamdi Allam committed
142
			indexedL1Header = append(indexedL1Header, &database.L1BlockHeader{
143 144 145 146 147 148 149
				BlockHeader: database.BlockHeader{
					Hash:       blockHash,
					ParentHash: header.ParentHash,
					Number:     database.U256{Int: header.Number},
					Timestamp:  header.Time,
				},
			})
150 151
		}

152 153
		/** Update Database **/

Hamdi Allam's avatar
Hamdi Allam committed
154 155 156 157
		numIndexedL1Headers := len(indexedL1Header)
		if numIndexedL1Headers > 0 {
			processLog.Info("saved l1 blocks of interest within batch", "num", numIndexedL1Headers, "batchSize", numHeaders)
			err = db.Blocks.StoreL1BlockHeaders(indexedL1Header)
158 159 160 161
			if err != nil {
				return err
			}

Hamdi Allam's avatar
Hamdi Allam committed
162
			// Since the headers to index are derived from the existence of logs, we know in this branch `numLogs > 0`
163
			processLog.Info("saving contract logs", "size", numLogs)
164 165 166 167
			err = db.ContractEvents.StoreL1ContractEvents(l1ContractEvents)
			if err != nil {
				return err
			}
168 169
		} else {
			processLog.Info("no l1 blocks of interest within batch")
170 171
		}

172 173
		// a-ok!
		return nil
174 175
	}
}