Skip to content

Commit

Permalink
Merge pull request #5968 from ethereum-optimism/indexer/contract.events
Browse files Browse the repository at this point in the history
feat(indexer): index all L1 & L2 contract events
  • Loading branch information
hamdiallam authored Jun 12, 2023
2 parents 62c7f3b + 9f0f7ec commit 65ec61d
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 25 deletions.
9 changes: 6 additions & 3 deletions indexer/database/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package database
import (
"errors"

"gorm.io/gorm"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"gorm.io/gorm"

"github.com/google/uuid"
)

/**
Expand All @@ -26,7 +29,7 @@ type TokenPair struct {
}

type Deposit struct {
GUID string `gorm:"primaryKey"`
GUID uuid.UUID `gorm:"primaryKey"`
InitiatedL1EventGUID string

Tx Transaction `gorm:"embedded"`
Expand All @@ -39,7 +42,7 @@ type DepositWithTransactionHash struct {
}

type Withdrawal struct {
GUID string `gorm:"primaryKey"`
GUID uuid.UUID `gorm:"primaryKey"`
InitiatedL2EventGUID string

WithdrawalHash common.Hash `gorm:"serializer:json"`
Expand Down
10 changes: 6 additions & 4 deletions indexer/database/contract_events.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package database

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"gorm.io/gorm"

"github.com/ethereum/go-ethereum/common"

"github.com/google/uuid"
)

/**
* Types
*/

type ContractEvent struct {
GUID string `gorm:"primaryKey"`
GUID uuid.UUID `gorm:"primaryKey"`
BlockHash common.Hash `gorm:"serializer:json"`
TransactionHash common.Hash `gorm:"serializer:json"`

EventSignature hexutil.Bytes `gorm:"serializer:json"`
EventSignature common.Hash `gorm:"serializer:json"`
LogIndex uint64
Timestamp uint64
}
Expand Down
15 changes: 12 additions & 3 deletions indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/indexer/processor"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"

"github.com/urfave/cli"
Expand Down Expand Up @@ -72,22 +73,30 @@ func NewIndexer(ctx *cli.Context) (*Indexer, error) {
return nil, err
}

// L1 Processor
// L1 Processor (hardhat devnet contracts). Make this configurable
l1Contracts := processor.L1Contracts{
OptimismPortal: common.HexToAddress("0x6900000000000000000000000000000000000000"),
L2OutputOracle: common.HexToAddress("0x6900000000000000000000000000000000000001"),
L1CrossDomainMessenger: common.HexToAddress("0x6900000000000000000000000000000000000002"),
L1StandardBridge: common.HexToAddress("0x6900000000000000000000000000000000000003"),
L1ERC721Bridge: common.HexToAddress("0x6900000000000000000000000000000000000004"),
}
l1EthClient, err := node.NewEthClient(ctx.GlobalString(flags.L1EthRPCFlag.Name))
if err != nil {
return nil, err
}
l1Processor, err := processor.NewL1Processor(l1EthClient, db)
l1Processor, err := processor.NewL1Processor(l1EthClient, db, l1Contracts)
if err != nil {
return nil, err
}

// L2Processor
l2Contracts := processor.L2ContractPredeploys() // Make this configurable
l2EthClient, err := node.NewEthClient(ctx.GlobalString(flags.L2EthRPCFlag.Name))
if err != nil {
return nil, err
}
l2Processor, err := processor.NewL2Processor(l2EthClient, db)
l2Processor, err := processor.NewL2Processor(l2EthClient, db, l2Contracts)
if err != nil {
return nil, err
}
Expand Down
123 changes: 114 additions & 9 deletions indexer/processor/l1_processor.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,52 @@
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 L1Contracts struct {
OptimismPortal common.Address
L2OutputOracle common.Address
L1CrossDomainMessenger common.Address
L1StandardBridge common.Address
L1ERC721Bridge 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 (c L1Contracts) 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 L1Processor struct {
processor
}

func NewL1Processor(ethClient node.EthClient, db *database.DB) (*L1Processor, error) {
func NewL1Processor(ethClient node.EthClient, db *database.DB, l1Contracts L1Contracts) (*L1Processor, error) {
l1ProcessLog := log.New("processor", "l1")
l1ProcessLog.Info("initializing processor")

Expand Down Expand Up @@ -41,30 +75,101 @@ func NewL1Processor(ethClient node.EthClient, db *database.DB) (*L1Processor, er
processor: processor{
fetcher: node.NewFetcher(ethClient, fromL1Header),
db: db,
processFn: l1ProcessFn(ethClient),
processFn: l1ProcessFn(l1ProcessLog, ethClient, l1Contracts),
processLog: l1ProcessLog,
},
}

return l1Processor, nil
}

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

contractAddrs := l1Contracts.toSlice()
processLog.Info("processor configured with contracts", "contracts", l1Contracts)

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

/** Watch for Contract Events **/

// index all l2 blocks for now
l1Headers := make([]*database.L1BlockHeader, len(headers))
for i, header := range headers {
l1Headers[i] = &database.L1BlockHeader{
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)
l1HeadersOfInterest := make(map[common.Hash]bool)
for i, log := range logs {
header, ok := l1HeaderMap[log.BlockHash]
if !ok {
processLog.Crit("contract event found with associated header not in the batch", "header", log.BlockHash, "log_index", log.Index)
return errors.New("parsed log with a block hash not in this batch")
}

l1HeadersOfInterest[log.BlockHash] = true
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,
},
}
}

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

// 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
indexedL1Header := []*database.L1BlockHeader{}
for _, header := range headers {
blockHash := header.Hash()
_, hasLogs := l1HeadersOfInterest[blockHash]
if !hasLogs {
continue
}

indexedL1Header = append(indexedL1Header, &database.L1BlockHeader{
BlockHeader: database.BlockHeader{
Hash: header.Hash(),
Hash: blockHash,
ParentHash: header.ParentHash,
Number: database.U256{Int: header.Number},
Timestamp: header.Time,
},
})
}

/** Update Database **/

numIndexedL1Headers := len(indexedL1Header)
if numIndexedL1Headers > 0 {
processLog.Info("saved l1 blocks of interest within batch", "num", numIndexedL1Headers, "batchSize", numHeaders)
err = db.Blocks.StoreL1BlockHeaders(indexedL1Header)
if err != nil {
return err
}

// Since the headers to index are derived from the existence of logs, we know in this branch `numLogs > 0`
processLog.Info("saving contract logs", "size", numLogs)
err = db.ContractEvents.StoreL1ContractEvents(l1ContractEvents)
if err != nil {
return err
}
} else {
processLog.Info("no l1 blocks of interest within batch")
}

return db.Blocks.StoreL1BlockHeaders(l1Headers)
// a-ok!
return nil
}
}
Loading

0 comments on commit 65ec61d

Please sign in to comment.