1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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
162
163
164
165
166
167
package processor
import (
"context"
"errors"
"reflect"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/google/uuid"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
type L2Contracts struct {
L2CrossDomainMessenger common.Address
L2StandardBridge common.Address
L2ERC721Bridge common.Address
L2ToL1MessagePasser common.Address
// Some more contracts -- ProxyAdmin, SystemConfig, etcc
// Ignore the auxiliary contracts?
// 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"),
}
}
func (c L2Contracts) toSlice() []common.Address {
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
}
type L2Processor struct {
processor
}
func NewL2Processor(ethClient node.EthClient, db *database.DB, l2Contracts L2Contracts) (*L2Processor, error) {
l2ProcessLog := log.New("processor", "l2")
l2ProcessLog.Info("initializing processor")
latestHeader, err := db.Blocks.FinalizedL2BlockHeader()
if err != nil {
return nil, err
}
var fromL2Header *types.Header
if latestHeader != nil {
l2ProcessLog.Info("detected last indexed block", "height", latestHeader.Number.Int, "hash", latestHeader.Hash)
l2Header, err := ethClient.BlockHeaderByHash(latestHeader.Hash)
if err != nil {
l2ProcessLog.Error("unable to fetch header for last indexed block", "hash", latestHeader.Hash, "err", err)
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,
processFn: l2ProcessFn(l2ProcessLog, ethClient, l2Contracts),
processLog: l2ProcessLog,
},
}
return l2Processor, nil
}
func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2Contracts) func(db *database.DB, headers []*types.Header) error {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())
contractAddrs := l2Contracts.toSlice()
processLog.Info("processor configured with contracts", "contracts", l2Contracts)
return func(db *database.DB, headers []*types.Header) error {
numHeaders := len(headers)
/** Index All L2 Blocks **/
l2Headers := make([]*database.L2BlockHeader, len(headers))
l2HeaderMap := make(map[common.Hash]*types.Header)
for i, header := range headers {
blockHash := header.Hash()
l2Headers[i] = &database.L2BlockHeader{
BlockHeader: database.BlockHeader{
Hash: blockHash,
ParentHash: header.ParentHash,
Number: database.U256{Int: header.Number},
Timestamp: header.Time,
},
}
l2HeaderMap[blockHash] = header
}
/** Watch for Contract Events **/
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
}
}
// a-ok!
return nil
}
}