Commit 1636250a authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into sc/ctb-oo-fix-init

parents 01c947c0 19e581d8
---
'@eth-optimism/indexer': minor
---
Bedrock support
...@@ -432,7 +432,7 @@ jobs: ...@@ -432,7 +432,7 @@ jobs:
name: Test name: Test
command: | command: |
mkdir -p /test-results mkdir -p /test-results
gotestsum --junitfile /test-results/tests.xml DB_USER=postgres gotestsum --junitfile /test-results/tests.xml
working_directory: <<parameters.working_directory>> working_directory: <<parameters.working_directory>>
- when: - when:
condition: condition:
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"errors" "errors"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -20,15 +21,8 @@ var ( ...@@ -20,15 +21,8 @@ var (
type Config struct { type Config struct {
/* Required Params */ /* Required Params */
// BuildEnv identifies the environment this binary is intended for, i.e.
// production, development, etc.
BuildEnv string
// EthNetworkName identifies the intended Ethereum network.
EthNetworkName string
// ChainID identifies the chain being indexed. // ChainID identifies the chain being indexed.
ChainID int64 ChainID uint64
// L1EthRpc is the HTTP provider URL for L1. // L1EthRpc is the HTTP provider URL for L1.
L1EthRpc string L1EthRpc string
...@@ -36,8 +30,8 @@ type Config struct { ...@@ -36,8 +30,8 @@ type Config struct {
// L2EthRpc is the HTTP provider URL for L1. // L2EthRpc is the HTTP provider URL for L1.
L2EthRpc string L2EthRpc string
// L2GenesisBlockHash is the l2 genesis block hash. // L1AddressManagerAddress is the address of the address manager for L1.
L2GenesisBlockHash string L1AddressManagerAddress string
// PollInterval is the delay between querying L2 for more transaction // PollInterval is the delay between querying L2 for more transaction
// and creating a new batch. // and creating a new batch.
...@@ -68,22 +62,8 @@ type Config struct { ...@@ -68,22 +62,8 @@ type Config struct {
// are printed using JSON. // are printed using JSON.
LogTerminal bool LogTerminal bool
// SentryEnable if true, logs any error messages to sentry. SentryDsn // L1StartBlockNumber is the block number to start indexing L1 from.
// must also be set if SentryEnable is true. L1StartBlockNumber uint64
SentryEnable bool
// SentryDsn is the sentry Data Source Name.
SentryDsn string
// SentryTraceRate the frequency with which Sentry should flush buffered
// events.
SentryTraceRate time.Duration
// StartBlockNumber is the block number to start indexing from.
StartBlockNumber uint64
// StartBlockHash is the block hash to start indexing from.
StartBlockHash string
// ConfDepth is the number of confirmations after which headers are // ConfDepth is the number of confirmations after which headers are
// considered confirmed. // considered confirmed.
...@@ -111,6 +91,13 @@ type Config struct { ...@@ -111,6 +91,13 @@ type Config struct {
// DisableIndexer enables/disables the indexer. // DisableIndexer enables/disables the indexer.
DisableIndexer bool DisableIndexer bool
// Bedrock enabled Bedrock indexing.
Bedrock bool
BedrockL1StandardBridgeAddress common.Address
BedrockOptimismPortalAddress common.Address
} }
// NewConfig parses the Config from the provided flags or environment variables. // NewConfig parses the Config from the provided flags or environment variables.
...@@ -118,33 +105,30 @@ type Config struct { ...@@ -118,33 +105,30 @@ type Config struct {
func NewConfig(ctx *cli.Context) (Config, error) { func NewConfig(ctx *cli.Context) (Config, error) {
cfg := Config{ cfg := Config{
/* Required Flags */ /* Required Flags */
BuildEnv: ctx.GlobalString(flags.BuildEnvFlag.Name), ChainID: ctx.GlobalUint64(flags.ChainIDFlag.Name),
EthNetworkName: ctx.GlobalString(flags.EthNetworkNameFlag.Name), L1EthRpc: ctx.GlobalString(flags.L1EthRPCFlag.Name),
ChainID: ctx.GlobalInt64(flags.ChainIDFlag.Name), L2EthRpc: ctx.GlobalString(flags.L2EthRPCFlag.Name),
L1EthRpc: ctx.GlobalString(flags.L1EthRPCFlag.Name), L1AddressManagerAddress: ctx.GlobalString(flags.L1AddressManagerAddressFlag.Name),
L2EthRpc: ctx.GlobalString(flags.L2EthRPCFlag.Name), DBHost: ctx.GlobalString(flags.DBHostFlag.Name),
L2GenesisBlockHash: ctx.GlobalString(flags.L2GenesisBlockHashFlag.Name), DBPort: ctx.GlobalUint64(flags.DBPortFlag.Name),
DBHost: ctx.GlobalString(flags.DBHostFlag.Name), DBUser: ctx.GlobalString(flags.DBUserFlag.Name),
DBPort: ctx.GlobalUint64(flags.DBPortFlag.Name), DBPassword: ctx.GlobalString(flags.DBPasswordFlag.Name),
DBUser: ctx.GlobalString(flags.DBUserFlag.Name), DBName: ctx.GlobalString(flags.DBNameFlag.Name),
DBPassword: ctx.GlobalString(flags.DBPasswordFlag.Name),
DBName: ctx.GlobalString(flags.DBNameFlag.Name),
/* Optional Flags */ /* Optional Flags */
DisableIndexer: ctx.GlobalBool(flags.DisableIndexer.Name), Bedrock: ctx.GlobalBool(flags.BedrockFlag.Name),
LogLevel: ctx.GlobalString(flags.LogLevelFlag.Name), BedrockL1StandardBridgeAddress: common.HexToAddress(ctx.GlobalString(flags.BedrockL1StandardBridgeAddress.Name)),
LogTerminal: ctx.GlobalBool(flags.LogTerminalFlag.Name), BedrockOptimismPortalAddress: common.HexToAddress(ctx.GlobalString(flags.BedrockOptimismPortalAddress.Name)),
SentryEnable: ctx.GlobalBool(flags.SentryEnableFlag.Name), DisableIndexer: ctx.GlobalBool(flags.DisableIndexer.Name),
SentryDsn: ctx.GlobalString(flags.SentryDsnFlag.Name), LogLevel: ctx.GlobalString(flags.LogLevelFlag.Name),
SentryTraceRate: ctx.GlobalDuration(flags.SentryTraceRateFlag.Name), LogTerminal: ctx.GlobalBool(flags.LogTerminalFlag.Name),
StartBlockNumber: ctx.GlobalUint64(flags.StartBlockNumberFlag.Name), L1StartBlockNumber: ctx.GlobalUint64(flags.L1StartBlockNumberFlag.Name),
StartBlockHash: ctx.GlobalString(flags.StartBlockHashFlag.Name), ConfDepth: ctx.GlobalUint64(flags.ConfDepthFlag.Name),
ConfDepth: ctx.GlobalUint64(flags.ConfDepthFlag.Name), MaxHeaderBatchSize: ctx.GlobalUint64(flags.MaxHeaderBatchSizeFlag.Name),
MaxHeaderBatchSize: ctx.GlobalUint64(flags.MaxHeaderBatchSizeFlag.Name), MetricsServerEnable: ctx.GlobalBool(flags.MetricsServerEnableFlag.Name),
MetricsServerEnable: ctx.GlobalBool(flags.MetricsServerEnableFlag.Name), RESTHostname: ctx.GlobalString(flags.RESTHostnameFlag.Name),
RESTHostname: ctx.GlobalString(flags.RESTHostnameFlag.Name), RESTPort: ctx.GlobalUint64(flags.RESTPortFlag.Name),
RESTPort: ctx.GlobalUint64(flags.RESTPortFlag.Name), MetricsHostname: ctx.GlobalString(flags.MetricsHostnameFlag.Name),
MetricsHostname: ctx.GlobalString(flags.MetricsHostnameFlag.Name), MetricsPort: ctx.GlobalUint64(flags.MetricsPortFlag.Name),
MetricsPort: ctx.GlobalUint64(flags.MetricsPortFlag.Name),
} }
err := ValidateConfig(&cfg) err := ValidateConfig(&cfg)
...@@ -168,9 +152,8 @@ func ValidateConfig(cfg *Config) error { ...@@ -168,9 +152,8 @@ func ValidateConfig(cfg *Config) error {
return err return err
} }
// Ensure the Sentry Data Source Name is set when using Sentry. if cfg.Bedrock && (cfg.BedrockL1StandardBridgeAddress == common.Address{} || cfg.BedrockOptimismPortalAddress == common.Address{}) {
if cfg.SentryEnable && cfg.SentryDsn == "" { return errors.New("must specify l1 standard bridge and optimism portal addresses in bedrock mode")
return ErrSentryDSNNotSet
} }
return nil return nil
......
...@@ -197,6 +197,12 @@ func (d *Database) AddIndexedL1Block(block *IndexedL1Block) error { ...@@ -197,6 +197,12 @@ func (d *Database) AddIndexedL1Block(block *IndexedL1Block) error {
VALUES VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
` `
const updateWithdrawalStatement = `
UPDATE withdrawals SET (br_withdrawal_finalized_tx_hash, br_withdrawal_finalized_log_index, br_withdrawal_finalized_success) = ($1, $2, $3)
WHERE br_withdrawal_hash = $4
`
return txn(d.db, func(tx *sql.Tx) error { return txn(d.db, func(tx *sql.Tx) error {
_, err := tx.Exec( _, err := tx.Exec(
insertBlockStatement, insertBlockStatement,
...@@ -209,26 +215,39 @@ func (d *Database) AddIndexedL1Block(block *IndexedL1Block) error { ...@@ -209,26 +215,39 @@ func (d *Database) AddIndexedL1Block(block *IndexedL1Block) error {
return err return err
} }
if len(block.Deposits) == 0 { if len(block.Deposits) > 0 {
return nil for _, deposit := range block.Deposits {
_, err = tx.Exec(
insertDepositStatement,
NewGUID(),
deposit.FromAddress.String(),
deposit.ToAddress.String(),
deposit.L1Token.String(),
deposit.L2Token.String(),
deposit.Amount.String(),
deposit.TxHash.String(),
deposit.LogIndex,
block.Hash.String(),
deposit.Data,
)
if err != nil {
return err
}
}
} }
for _, deposit := range block.Deposits { if len(block.FinalizedWithdrawals) > 0 {
_, err = tx.Exec( for _, wd := range block.FinalizedWithdrawals {
insertDepositStatement, _, err = tx.Exec(
NewGUID(), updateWithdrawalStatement,
deposit.FromAddress.String(), wd.TxHash.String(),
deposit.ToAddress.String(), wd.LogIndex,
deposit.L1Token.String(), wd.Success,
deposit.L2Token.String(), wd.WithdrawalHash.String(),
deposit.Amount.String(), )
deposit.TxHash.String(), if err != nil {
deposit.LogIndex, return err
block.Hash.String(), }
deposit.Data,
)
if err != nil {
return err
} }
} }
...@@ -459,19 +478,21 @@ func (d *Database) GetWithdrawalBatch(hash common.Hash) (*StateBatchJSON, error) ...@@ -459,19 +478,21 @@ func (d *Database) GetWithdrawalBatch(hash common.Hash) (*StateBatchJSON, error)
// GetWithdrawalsByAddress returns the list of Withdrawals indexed for the given // GetWithdrawalsByAddress returns the list of Withdrawals indexed for the given
// address paginated by the given params. // address paginated by the given params.
func (d *Database) GetWithdrawalsByAddress(address common.Address, page PaginationParam) (*PaginatedWithdrawals, error) { func (d *Database) GetWithdrawalsByAddress(address common.Address, page PaginationParam, state FinalizationState) (*PaginatedWithdrawals, error) {
selectWithdrawalsStatement := fmt.Sprintf(` selectWithdrawalsStatement := fmt.Sprintf(`
SELECT SELECT
withdrawals.guid, withdrawals.from_address, withdrawals.to_address, withdrawals.guid, withdrawals.from_address, withdrawals.to_address,
withdrawals.amount, withdrawals.tx_hash, withdrawals.data, withdrawals.amount, withdrawals.tx_hash, withdrawals.data,
withdrawals.l1_token, withdrawals.l2_token, withdrawals.l1_token, withdrawals.l2_token,
l2_tokens.name, l2_tokens.symbol, l2_tokens.decimals, l2_tokens.name, l2_tokens.symbol, l2_tokens.decimals,
l2_blocks.number, l2_blocks.timestamp, withdrawals.br_withdrawal_hash l2_blocks.number, l2_blocks.timestamp, withdrawals.br_withdrawal_hash,
withdrawals.br_withdrawal_finalized_tx_hash, withdrawals.br_withdrawal_finalized_log_index,
withdrawals.br_withdrawal_finalized_success
FROM withdrawals FROM withdrawals
INNER JOIN l2_blocks ON withdrawals.block_hash=l2_blocks.hash INNER JOIN l2_blocks ON withdrawals.block_hash=l2_blocks.hash
INNER JOIN l2_tokens ON withdrawals.l2_token=l2_tokens.address INNER JOIN l2_tokens ON withdrawals.l2_token=l2_tokens.address
WHERE withdrawals.from_address = $1 %s ORDER BY l2_blocks.timestamp LIMIT $2 OFFSET $3; WHERE withdrawals.from_address = $1 %s ORDER BY l2_blocks.timestamp LIMIT $2 OFFSET $3;
`, FinalizationStateAny.SQL()) `, state.SQL())
var withdrawals []WithdrawalJSON var withdrawals []WithdrawalJSON
err := txn(d.db, func(tx *sql.Tx) error { err := txn(d.db, func(tx *sql.Tx) error {
...@@ -485,13 +506,16 @@ func (d *Database) GetWithdrawalsByAddress(address common.Address, page Paginati ...@@ -485,13 +506,16 @@ func (d *Database) GetWithdrawalsByAddress(address common.Address, page Paginati
var withdrawal WithdrawalJSON var withdrawal WithdrawalJSON
var l2Token Token var l2Token Token
var wdHash sql.NullString var wdHash sql.NullString
var finTxHash sql.NullString
var finLogIndex sql.NullInt32
var finSuccess sql.NullBool
if err := rows.Scan( if err := rows.Scan(
&withdrawal.GUID, &withdrawal.FromAddress, &withdrawal.ToAddress, &withdrawal.GUID, &withdrawal.FromAddress, &withdrawal.ToAddress,
&withdrawal.Amount, &withdrawal.TxHash, &withdrawal.Data, &withdrawal.Amount, &withdrawal.TxHash, &withdrawal.Data,
&withdrawal.L1Token, &l2Token.Address, &withdrawal.L1Token, &l2Token.Address,
&l2Token.Name, &l2Token.Symbol, &l2Token.Decimals, &l2Token.Name, &l2Token.Symbol, &l2Token.Decimals,
&withdrawal.BlockNumber, &withdrawal.BlockTimestamp, &withdrawal.BlockNumber, &withdrawal.BlockTimestamp,
&wdHash, &wdHash, &finTxHash, &finLogIndex, &finSuccess,
); err != nil { ); err != nil {
return err return err
} }
...@@ -499,6 +523,16 @@ func (d *Database) GetWithdrawalsByAddress(address common.Address, page Paginati ...@@ -499,6 +523,16 @@ func (d *Database) GetWithdrawalsByAddress(address common.Address, page Paginati
if wdHash.Valid { if wdHash.Valid {
withdrawal.BedrockWithdrawalHash = &wdHash.String withdrawal.BedrockWithdrawalHash = &wdHash.String
} }
if finTxHash.Valid {
withdrawal.BedrockFinalizedTxHash = &finTxHash.String
}
if finLogIndex.Valid {
idx := int(finLogIndex.Int32)
withdrawal.BedrockFinalizedLogIndex = &idx
}
if finSuccess.Valid {
withdrawal.BedrockFinalizedSuccess = &finSuccess.Bool
}
withdrawals = append(withdrawals, withdrawal) withdrawals = append(withdrawals, withdrawal)
} }
......
package db package db
import "github.com/ethereum/go-ethereum/common" import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common"
)
var ETHL1Address common.Address var ETHL1Address common.Address
...@@ -13,14 +16,10 @@ var ETHL1Token = &Token{ ...@@ -13,14 +16,10 @@ var ETHL1Token = &Token{
Decimals: 18, Decimals: 18,
} }
// ETHL2Address is a placeholder address for differentiating ETH transactions
// from ERC20 transactions on L2.
var ETHL2Address = common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000")
// ETHL2Token is a placeholder token for differentiating ETH transactions from // ETHL2Token is a placeholder token for differentiating ETH transactions from
// ERC20 transactions on L2. // ERC20 transactions on L2.
var ETHL2Token = &Token{ var ETHL2Token = &Token{
Address: ETHL2Address.String(), Address: predeploys.LegacyERC20ETH,
Name: "Ethereum", Name: "Ethereum",
Symbol: "ETH", Symbol: "ETH",
Decimals: 18, Decimals: 18,
......
...@@ -6,11 +6,12 @@ import ( ...@@ -6,11 +6,12 @@ import (
// IndexedL1Block contains the L1 block including the deposits in it. // IndexedL1Block contains the L1 block including the deposits in it.
type IndexedL1Block struct { type IndexedL1Block struct {
Hash common.Hash Hash common.Hash
ParentHash common.Hash ParentHash common.Hash
Number uint64 Number uint64
Timestamp uint64 Timestamp uint64
Deposits []Deposit Deposits []Deposit
FinalizedWithdrawals []FinalizedWithdrawal
} }
// String returns the block hash for the indexed l1 block. // String returns the block hash for the indexed l1 block.
......
...@@ -125,8 +125,8 @@ CREATE TABLE IF NOT EXISTS airdrops ( ...@@ -125,8 +125,8 @@ CREATE TABLE IF NOT EXISTS airdrops (
const updateWithdrawalsTable = ` const updateWithdrawalsTable = `
ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_hash VARCHAR NULL; ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_hash VARCHAR NULL;
ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_finalized_tx_hash VARCHAR NULL; ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_finalized_tx_hash VARCHAR NULL;
ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_finalized_log_index BOOLEAN NULL; ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_finalized_log_index INTEGER NULL;
ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_success BOOLEAN NULL; ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_finalized_success BOOLEAN NULL;
CREATE INDEX IF NOT EXISTS withdrawals_br_withdrawal_hash ON withdrawals(br_withdrawal_hash); CREATE INDEX IF NOT EXISTS withdrawals_br_withdrawal_hash ON withdrawals(br_withdrawal_hash);
` `
......
...@@ -27,19 +27,22 @@ func (w Withdrawal) String() string { ...@@ -27,19 +27,22 @@ func (w Withdrawal) String() string {
// WithdrawalJSON contains Withdrawal data suitable for JSON serialization. // WithdrawalJSON contains Withdrawal data suitable for JSON serialization.
type WithdrawalJSON struct { type WithdrawalJSON struct {
GUID string `json:"guid"` GUID string `json:"guid"`
FromAddress string `json:"from"` FromAddress string `json:"from"`
ToAddress string `json:"to"` ToAddress string `json:"to"`
L1Token string `json:"l1Token"` L1Token string `json:"l1Token"`
L2Token *Token `json:"l2Token"` L2Token *Token `json:"l2Token"`
Amount string `json:"amount"` Amount string `json:"amount"`
Data []byte `json:"data"` Data []byte `json:"data"`
LogIndex uint64 `json:"logIndex"` LogIndex uint64 `json:"logIndex"`
BlockNumber uint64 `json:"blockNumber"` BlockNumber uint64 `json:"blockNumber"`
BlockTimestamp string `json:"blockTimestamp"` BlockTimestamp string `json:"blockTimestamp"`
TxHash string `json:"transactionHash"` TxHash string `json:"transactionHash"`
Batch *StateBatchJSON `json:"batch"` Batch *StateBatchJSON `json:"batch"`
BedrockWithdrawalHash *string `json:"bedrockWithdrawalHash"` BedrockWithdrawalHash *string `json:"bedrockWithdrawalHash"`
BedrockFinalizedTxHash *string `json:"bedrockFinalizedTxHash"`
BedrockFinalizedLogIndex *int `json:"bedrockFinalizedLogIndex"`
BedrockFinalizedSuccess *bool `json:"bedrockFinalizedSuccess"`
} }
type FinalizationState int type FinalizationState int
...@@ -64,9 +67,9 @@ func ParseFinalizationState(in string) FinalizationState { ...@@ -64,9 +67,9 @@ func ParseFinalizationState(in string) FinalizationState {
func (f FinalizationState) SQL() string { func (f FinalizationState) SQL() string {
switch f { switch f {
case FinalizationStateFinalized: case FinalizationStateFinalized:
return "AND withdrawals.l1_block_hash IS NOT NULL" return "AND withdrawals.br_withdrawal_finalized_tx_hash IS NOT NULL"
case FinalizationStateUnfinalized: case FinalizationStateUnfinalized:
return "AND withdrawals.l2_block_hash IS NULL" return "AND withdrawals.br_withdrawal_finalized_tx_hash IS NULL"
} }
return "" return ""
......
...@@ -46,11 +46,11 @@ var ( ...@@ -46,11 +46,11 @@ var (
Required: true, Required: true,
EnvVar: prefixEnvVar("L2_ETH_RPC"), EnvVar: prefixEnvVar("L2_ETH_RPC"),
} }
L2GenesisBlockHashFlag = cli.StringFlag{ L1AddressManagerAddressFlag = cli.StringFlag{
Name: "l2-genesis-block-hash", Name: "l1-address-manager-address",
Usage: "Genesis block hash of the L2 chain", Usage: "Address of the L1 address manager",
Required: true, Required: true,
EnvVar: prefixEnvVar("L2_GENESIS_BLOCK_HASH"), EnvVar: prefixEnvVar("L1_ADDRESS_MANAGER_ADDRESS"),
} }
DBHostFlag = cli.StringFlag{ DBHostFlag = cli.StringFlag{
Name: "db-host", Name: "db-host",
...@@ -83,6 +83,23 @@ var ( ...@@ -83,6 +83,23 @@ var (
EnvVar: prefixEnvVar("DB_NAME"), EnvVar: prefixEnvVar("DB_NAME"),
} }
/* Bedrock Flags */
BedrockFlag = cli.BoolFlag{
Name: "bedrock",
Usage: "Whether or not this indexer should operate in Bedrock mode",
EnvVar: prefixEnvVar("BEDROCK"),
}
BedrockL1StandardBridgeAddress = cli.BoolFlag{
Name: "bedrock.l1-standard-bridge-address",
Usage: "Address of the L1 standard bridge",
EnvVar: prefixEnvVar("BEDROCK_L1_STANDARD_BRIDGE"),
}
BedrockOptimismPortalAddress = cli.BoolFlag{
Name: "bedrock.portal-address",
Usage: "Address of the portal",
EnvVar: prefixEnvVar("BEDROCK_OPTIMISM_PORTAL"),
}
/* Optional Flags */ /* Optional Flags */
DisableIndexer = cli.BoolFlag{ DisableIndexer = cli.BoolFlag{
...@@ -120,18 +137,12 @@ var ( ...@@ -120,18 +137,12 @@ var (
Value: 50 * time.Millisecond, Value: 50 * time.Millisecond,
EnvVar: prefixEnvVar("SENTRY_TRACE_RATE"), EnvVar: prefixEnvVar("SENTRY_TRACE_RATE"),
} }
StartBlockNumberFlag = cli.Uint64Flag{ L1StartBlockNumberFlag = cli.Uint64Flag{
Name: "start-block-number", Name: "start-block-number",
Usage: "The block number to start indexing from. Must be use together with start block hash", Usage: "The block number to start indexing from. Must be use together with start block hash",
Value: 0, Value: 0,
EnvVar: prefixEnvVar("START_BLOCK_NUMBER"), EnvVar: prefixEnvVar("START_BLOCK_NUMBER"),
} }
StartBlockHashFlag = cli.StringFlag{
Name: "start-block-hash",
Usage: "The block hash to start indexing from. Must be use together with start block number",
Value: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3",
EnvVar: prefixEnvVar("START_BLOCK_HASH"),
}
ConfDepthFlag = cli.Uint64Flag{ ConfDepthFlag = cli.Uint64Flag{
Name: "conf-depth", Name: "conf-depth",
Usage: "The number of confirmations after which headers are considered confirmed", Usage: "The number of confirmations after which headers are considered confirmed",
...@@ -181,7 +192,7 @@ var requiredFlags = []cli.Flag{ ...@@ -181,7 +192,7 @@ var requiredFlags = []cli.Flag{
ChainIDFlag, ChainIDFlag,
L1EthRPCFlag, L1EthRPCFlag,
L2EthRPCFlag, L2EthRPCFlag,
L2GenesisBlockHashFlag, L1AddressManagerAddressFlag,
DBHostFlag, DBHostFlag,
DBPortFlag, DBPortFlag,
DBUserFlag, DBUserFlag,
...@@ -190,6 +201,9 @@ var requiredFlags = []cli.Flag{ ...@@ -190,6 +201,9 @@ var requiredFlags = []cli.Flag{
} }
var optionalFlags = []cli.Flag{ var optionalFlags = []cli.Flag{
BedrockFlag,
BedrockL1StandardBridgeAddress,
BedrockOptimismPortalAddress,
DisableIndexer, DisableIndexer,
LogLevelFlag, LogLevelFlag,
LogTerminalFlag, LogTerminalFlag,
...@@ -198,8 +212,7 @@ var optionalFlags = []cli.Flag{ ...@@ -198,8 +212,7 @@ var optionalFlags = []cli.Flag{
SentryTraceRateFlag, SentryTraceRateFlag,
ConfDepthFlag, ConfDepthFlag,
MaxHeaderBatchSizeFlag, MaxHeaderBatchSizeFlag,
StartBlockNumberFlag, L1StartBlockNumberFlag,
StartBlockHashFlag,
RESTHostnameFlag, RESTHostnameFlag,
RESTPortFlag, RESTPortFlag,
MetricsServerEnableFlag, MetricsServerEnableFlag,
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/indexer/services" "github.com/ethereum-optimism/optimism/indexer/services"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/indexer/metrics" "github.com/ethereum-optimism/optimism/indexer/metrics"
"github.com/ethereum-optimism/optimism/indexer/server" "github.com/ethereum-optimism/optimism/indexer/server"
...@@ -22,7 +23,6 @@ import ( ...@@ -22,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
sentry "github.com/getsentry/sentry-go"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
...@@ -44,15 +44,9 @@ func Main(gitVersion string) func(ctx *cli.Context) error { ...@@ -44,15 +44,9 @@ func Main(gitVersion string) func(ctx *cli.Context) error {
return err return err
} }
// The call to defer is done here so that any errors logged from
// this point on are posted to Sentry before exiting.
if cfg.SentryEnable {
defer sentry.Flush(2 * time.Second)
}
log.Info("Initializing indexer") log.Info("Initializing indexer")
indexer, err := NewIndexer(cfg, gitVersion) indexer, err := NewIndexer(cfg)
if err != nil { if err != nil {
log.Error("Unable to create indexer", "error", err) log.Error("Unable to create indexer", "error", err)
return err return err
...@@ -87,32 +81,18 @@ type Indexer struct { ...@@ -87,32 +81,18 @@ type Indexer struct {
router *mux.Router router *mux.Router
metrics *metrics.Metrics metrics *metrics.Metrics
db *database.Database
server *http.Server
} }
// NewIndexer initializes the Indexer, gathering any resources // NewIndexer initializes the Indexer, gathering any resources
// that will be needed by the TxIndexer and StateIndexer // that will be needed by the TxIndexer and StateIndexer
// sub-services. // sub-services.
func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { func NewIndexer(cfg Config) (*Indexer, error) {
ctx := context.Background() ctx := context.Background()
// Set up our logging. If Sentry is enabled, we will use our custom
// log handler that logs to stdout and forwards any error messages to
// Sentry for collection. Otherwise, logs will only be posted to stdout.
var logHandler log.Handler var logHandler log.Handler
if cfg.SentryEnable { if cfg.LogTerminal {
err := sentry.Init(sentry.ClientOptions{
Dsn: cfg.SentryDsn,
Environment: cfg.EthNetworkName,
Release: "indexer@" + gitVersion,
TracesSampleRate: traceRateToFloat64(cfg.SentryTraceRate),
Debug: false,
})
if err != nil {
return nil, err
}
logHandler = SentryStreamHandler(os.Stdout, log.JSONFormat())
} else if cfg.LogTerminal {
logHandler = log.StreamHandler(os.Stdout, log.TerminalFormat(true)) logHandler = log.StreamHandler(os.Stdout, log.TerminalFormat(true))
} else { } else {
logHandler = log.StreamHandler(os.Stdout, log.JSONFormat()) logHandler = log.StreamHandler(os.Stdout, log.JSONFormat())
...@@ -149,8 +129,11 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { ...@@ -149,8 +129,11 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) {
log.Info("metrics server enabled", "host", cfg.MetricsHostname, "port", cfg.MetricsPort) log.Info("metrics server enabled", "host", cfg.MetricsHostname, "port", cfg.MetricsPort)
} }
dsn := fmt.Sprintf("host=%s port=%d user=%s dbname=%s sslmode=disable", dsn := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable",
cfg.DBHost, cfg.DBPort, cfg.DBUser, cfg.DBName) cfg.DBHost, cfg.DBPort, cfg.DBName)
if cfg.DBUser != "" {
dsn += fmt.Sprintf(" user=%s", cfg.DBUser)
}
if cfg.DBPassword != "" { if cfg.DBPassword != "" {
dsn += fmt.Sprintf(" password=%s", cfg.DBPassword) dsn += fmt.Sprintf(" password=%s", cfg.DBPassword)
} }
...@@ -159,17 +142,32 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { ...@@ -159,17 +142,32 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) {
return nil, err return nil, err
} }
var addrManager services.AddressManager
if cfg.Bedrock {
addrManager, err = services.NewBedrockAddresses(
l1Client,
cfg.BedrockL1StandardBridgeAddress,
cfg.BedrockOptimismPortalAddress,
)
} else {
addrManager, err = services.NewLegacyAddresses(l1Client, common.HexToAddress(cfg.L1AddressManagerAddress))
}
if err != nil {
return nil, err
}
l1IndexingService, err := l1.NewService(l1.ServiceConfig{ l1IndexingService, err := l1.NewService(l1.ServiceConfig{
Context: ctx, Context: ctx,
Metrics: m, Metrics: m,
L1Client: l1Client, L1Client: l1Client,
RawL1Client: rawl1Client, RawL1Client: rawl1Client,
ChainID: big.NewInt(cfg.ChainID), ChainID: new(big.Int).SetUint64(cfg.ChainID),
AddressManager: addrManager,
DB: db, DB: db,
ConfDepth: cfg.ConfDepth, ConfDepth: cfg.ConfDepth,
MaxHeaderBatchSize: cfg.MaxHeaderBatchSize, MaxHeaderBatchSize: cfg.MaxHeaderBatchSize,
StartBlockNumber: cfg.StartBlockNumber, StartBlockNumber: cfg.L1StartBlockNumber,
StartBlockHash: cfg.StartBlockHash, Bedrock: cfg.Bedrock,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -184,7 +182,7 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { ...@@ -184,7 +182,7 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) {
ConfDepth: cfg.ConfDepth, ConfDepth: cfg.ConfDepth,
MaxHeaderBatchSize: cfg.MaxHeaderBatchSize, MaxHeaderBatchSize: cfg.MaxHeaderBatchSize,
StartBlockNumber: uint64(0), StartBlockNumber: uint64(0),
StartBlockHash: cfg.L2GenesisBlockHash, Bedrock: cfg.Bedrock,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -200,6 +198,7 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { ...@@ -200,6 +198,7 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) {
airdropService: services.NewAirdrop(db, m), airdropService: services.NewAirdrop(db, m),
router: mux.NewRouter(), router: mux.NewRouter(),
metrics: m, metrics: m,
db: db,
}, nil }, nil
} }
...@@ -212,7 +211,7 @@ func (b *Indexer) Serve() error { ...@@ -212,7 +211,7 @@ func (b *Indexer) Serve() error {
b.router.HandleFunc("/v1/l1/status", b.l1IndexingService.GetIndexerStatus).Methods("GET") b.router.HandleFunc("/v1/l1/status", b.l1IndexingService.GetIndexerStatus).Methods("GET")
b.router.HandleFunc("/v1/l2/status", b.l2IndexingService.GetIndexerStatus).Methods("GET") b.router.HandleFunc("/v1/l2/status", b.l2IndexingService.GetIndexerStatus).Methods("GET")
b.router.HandleFunc("/v1/deposits/0x{address:[a-fA-F0-9]{40}}", b.l1IndexingService.GetDeposits).Methods("GET") b.router.HandleFunc("/v1/deposits/0x{address:[a-fA-F0-9]{40}}", b.l1IndexingService.GetDeposits).Methods("GET")
b.router.HandleFunc("/v1/withdrawal/0x{hash:[a-fA-F0-9]{64}}", b.l2IndexingService.GetWithdrawalStatus).Methods("GET") b.router.HandleFunc("/v1/withdrawal/0x{hash:[a-fA-F0-9]{64}}", b.l2IndexingService.GetWithdrawalBatch).Methods("GET")
b.router.HandleFunc("/v1/withdrawals/0x{address:[a-fA-F0-9]{40}}", b.l2IndexingService.GetWithdrawals).Methods("GET") b.router.HandleFunc("/v1/withdrawals/0x{address:[a-fA-F0-9]{40}}", b.l2IndexingService.GetWithdrawals).Methods("GET")
b.router.HandleFunc("/v1/airdrops/0x{address:[a-fA-F0-9]{40}}", b.airdropService.GetAirdrop) b.router.HandleFunc("/v1/airdrops/0x{address:[a-fA-F0-9]{40}}", b.airdropService.GetAirdrop)
b.router.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { b.router.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
...@@ -228,8 +227,27 @@ func (b *Indexer) Serve() error { ...@@ -228,8 +227,27 @@ func (b *Indexer) Serve() error {
port := strconv.FormatUint(b.cfg.RESTPort, 10) port := strconv.FormatUint(b.cfg.RESTPort, 10)
addr := net.JoinHostPort(b.cfg.RESTHostname, port) addr := net.JoinHostPort(b.cfg.RESTHostname, port)
log.Info("indexer REST server listening on", "addr", addr) b.server = &http.Server{
return http.ListenAndServe(addr, middleware(c.Handler(b.router))) Addr: addr,
Handler: middleware(c.Handler(b.router)),
}
errCh := make(chan error, 1)
go func() {
errCh <- b.server.ListenAndServe()
}()
// Capture server startup errors
<-time.After(10 * time.Millisecond)
select {
case err := <-errCh:
return err
default:
log.Info("indexer REST server listening on", "addr", addr)
return nil
}
} }
// Start starts the starts the indexing service on L1 and L2 chains and also // Start starts the starts the indexing service on L1 and L2 chains and also
...@@ -253,6 +271,14 @@ func (b *Indexer) Start() error { ...@@ -253,6 +271,14 @@ func (b *Indexer) Start() error {
// Stop stops the indexing service on L1 and L2 chains. // Stop stops the indexing service on L1 and L2 chains.
func (b *Indexer) Stop() { func (b *Indexer) Stop() {
b.db.Close()
if b.server != nil {
// background context here so it waits for
// conns to close
_ = b.server.Shutdown(context.Background())
}
if !b.cfg.DisableIndexer { if !b.cfg.DisableIndexer {
b.l1IndexingService.Stop() b.l1IndexingService.Stop()
b.l2IndexingService.Stop() b.l2IndexingService.Stop()
...@@ -274,14 +300,3 @@ func dialEthClientWithTimeout(ctx context.Context, url string) ( ...@@ -274,14 +300,3 @@ func dialEthClientWithTimeout(ctx context.Context, url string) (
} }
return ethclient.NewClient(c), c, nil return ethclient.NewClient(c), c, nil
} }
// traceRateToFloat64 converts a time.Duration into a valid float64 for the
// Sentry client. The client only accepts values between 0.0 and 1.0, so this
// method clamps anything greater than 1 second to 1.0.
func traceRateToFloat64(rate time.Duration) float64 {
rate64 := float64(rate) / float64(time.Second)
if rate64 > 1.0 {
rate64 = 1.0
}
return rate64
}
package integration_tests
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"math/big"
"net/http"
"os"
"testing"
"time"
"github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services/l1"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
_ "github.com/lib/pq"
)
func TestBedrockIndexer(t *testing.T) {
dbParams := createTestDB(t)
cfg := op_e2e.DefaultSystemConfig(t)
cfg.DeployConfig.FinalizationPeriodSeconds = 2
sys, err := cfg.Start()
require.NoError(t, err)
defer sys.Close()
l1Client := sys.Clients["l1"]
l2Client := sys.Clients["sequencer"]
fromAddr := cfg.Secrets.Addresses().Alice
// wait a couple of blocks
require.NoError(t, e2eutils.WaitBlock(e2eutils.TimeoutCtx(t, 30*time.Second), l2Client, 10))
l1SB, err := bindings.NewL1StandardBridge(predeploys.DevL1StandardBridgeAddr, l1Client)
require.NoError(t, err)
l2SB, err := bindings.NewL2StandardBridge(predeploys.L2StandardBridgeAddr, l2Client)
require.NoError(t, err)
portal, err := bindings.NewOptimismPortal(predeploys.DevOptimismPortalAddr, l1Client)
require.NoError(t, err)
l1Opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L1ChainIDBig())
require.NoError(t, err)
l2Opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L2ChainIDBig())
require.NoError(t, err)
idxrCfg := indexer.Config{
ChainID: cfg.DeployConfig.L1ChainID,
L1EthRpc: sys.Nodes["l1"].HTTPEndpoint(),
L2EthRpc: sys.Nodes["sequencer"].HTTPEndpoint(),
PollInterval: time.Second,
DBHost: dbParams.Host,
DBPort: dbParams.Port,
DBUser: dbParams.User,
DBPassword: dbParams.Password,
DBName: dbParams.Name,
LogLevel: "info",
LogTerminal: true,
L1StartBlockNumber: 0,
ConfDepth: 1,
MaxHeaderBatchSize: 2,
RESTHostname: "127.0.0.1",
RESTPort: 7980,
DisableIndexer: false,
Bedrock: true,
BedrockL1StandardBridgeAddress: predeploys.DevL1StandardBridgeAddr,
BedrockOptimismPortalAddress: predeploys.DevOptimismPortalAddr,
}
idxr, err := indexer.NewIndexer(idxrCfg)
require.NoError(t, err)
errCh := make(chan error, 1)
go func() {
errCh <- idxr.Start()
}()
t.Cleanup(func() {
idxr.Stop()
require.NoError(t, <-errCh)
})
makeURL := func(path string) string {
return fmt.Sprintf("http://%s:%d/%s", idxrCfg.RESTHostname, idxrCfg.RESTPort, path)
}
t.Run("deposit ETH", func(t *testing.T) {
l1Opts.Value = big.NewInt(params.Ether)
depTx, err := l1SB.DepositETH(l1Opts, 200_000, nil)
require.NoError(t, err)
depReceipt, err := e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, 10*time.Second), l1Client, depTx.Hash())
require.NoError(t, err)
require.Greaterf(t, len(depReceipt.Logs), 0, "must have logs")
var l2Hash common.Hash
for _, eLog := range depReceipt.Logs {
if len(eLog.Topics) == 0 || eLog.Topics[0] != derive.DepositEventABIHash {
continue
}
depLog, err := derive.UnmarshalDepositLogEvent(eLog)
require.NoError(t, err)
tx := types.NewTx(depLog)
l2Hash = tx.Hash()
}
require.NotEqual(t, common.Hash{}, l2Hash)
_, err = e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, 15*time.Second), l2Client, l2Hash)
require.NoError(t, err)
// Poll for indexer deposit
var depPage *db.PaginatedDeposits
require.NoError(t, e2eutils.WaitFor(e2eutils.TimeoutCtx(t, 30*time.Second), 100*time.Millisecond, func() (bool, error) {
res := new(db.PaginatedDeposits)
err := getJSON(makeURL(fmt.Sprintf("v1/deposits/%s", fromAddr)), res)
if err != nil {
return false, err
}
if len(res.Deposits) == 0 {
return false, nil
}
depPage = res
return true, nil
}))
// Make sure deposit is what we expect
require.Equal(t, 1, len(depPage.Deposits))
deposit := depPage.Deposits[0]
require.Equal(t, big.NewInt(params.Ether).String(), deposit.Amount)
require.Equal(t, depTx.Hash().String(), deposit.TxHash)
require.Equal(t, depReceipt.BlockNumber.Uint64(), deposit.BlockNumber)
require.Equal(t, fromAddr.String(), deposit.FromAddress)
require.Equal(t, fromAddr.String(), deposit.ToAddress)
require.EqualValues(t, db.ETHL1Token, deposit.L1Token)
require.Equal(t, l1.ZeroAddress.String(), deposit.L2Token)
require.NotEmpty(t, deposit.GUID)
// Perform withdrawal through bridge
l2Opts.Value = big.NewInt(0.5 * params.Ether)
wdTx, err := l2SB.Withdraw(l2Opts, predeploys.LegacyERC20ETHAddr, big.NewInt(0.5*params.Ether), 0, nil)
require.NoError(t, err)
wdReceipt, err := e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, 30*time.Second), l2Client, wdTx.Hash())
require.NoError(t, err)
var wdPage *db.PaginatedWithdrawals
require.NoError(t, e2eutils.WaitFor(e2eutils.TimeoutCtx(t, 30*time.Second), 100*time.Millisecond, func() (bool, error) {
res := new(db.PaginatedWithdrawals)
err := getJSON(makeURL(fmt.Sprintf("v1/withdrawals/%s", fromAddr)), res)
if err != nil {
return false, err
}
if len(res.Withdrawals) == 0 {
return false, nil
}
wdPage = res
return true, nil
}))
require.Equal(t, 1, len(wdPage.Withdrawals))
withdrawal := wdPage.Withdrawals[0]
require.Nil(t, withdrawal.BedrockFinalizedTxHash)
require.Equal(t, big.NewInt(0.5*params.Ether).String(), withdrawal.Amount)
require.Equal(t, wdTx.Hash().String(), withdrawal.TxHash)
require.Equal(t, wdReceipt.BlockNumber.Uint64(), withdrawal.BlockNumber)
// use fromaddr twice here because the user is withdrawing
// to themselves
require.Equal(t, fromAddr.String(), withdrawal.FromAddress)
require.Equal(t, fromAddr.String(), withdrawal.ToAddress)
require.EqualValues(t, l1.ZeroAddress.String(), withdrawal.L1Token)
require.Equal(t, db.ETHL2Token, withdrawal.L2Token)
require.NotEmpty(t, withdrawal.GUID)
finBlockNum, err := withdrawals.WaitForFinalizationPeriod(
e2eutils.TimeoutCtx(t, time.Minute),
l1Client,
predeploys.DevOptimismPortalAddr,
wdReceipt.BlockNumber,
)
require.NoError(t, err)
finHeader, err := l2Client.HeaderByNumber(context.Background(), big.NewInt(int64(finBlockNum)))
require.NoError(t, err)
rpcClient, err := rpc.Dial(sys.Nodes["sequencer"].HTTPEndpoint())
require.NoError(t, err)
proofClient := withdrawals.NewClient(rpcClient)
wParams, err := withdrawals.FinalizeWithdrawalParameters(context.Background(), proofClient, wdTx.Hash(), finHeader)
require.NoError(t, err)
l1Opts.Value = big.NewInt(0)
finTx, err := portal.FinalizeWithdrawalTransaction(
l1Opts,
bindings.TypesWithdrawalTransaction{
Nonce: wParams.Nonce,
Sender: wParams.Sender,
Target: wParams.Target,
Value: wParams.Value,
GasLimit: wParams.GasLimit,
Data: wParams.Data,
},
wParams.BlockNumber,
wParams.OutputRootProof,
wParams.WithdrawalProof,
)
require.NoError(t, err)
finReceipt, err := e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, time.Minute), l1Client, finTx.Hash())
require.NoError(t, err)
wdPage = nil
require.NoError(t, e2eutils.WaitFor(e2eutils.TimeoutCtx(t, 30*time.Second), 100*time.Millisecond, func() (bool, error) {
res := new(db.PaginatedWithdrawals)
err := getJSON(makeURL(fmt.Sprintf("v1/withdrawals/%s", fromAddr)), res)
if err != nil {
return false, err
}
if res.Withdrawals[0].BedrockFinalizedTxHash == nil {
return false, nil
}
wdPage = res
return true, nil
}))
wd := wdPage.Withdrawals[0]
require.Equal(t, finReceipt.TxHash.String(), *wd.BedrockFinalizedTxHash)
require.True(t, *wd.BedrockFinalizedSuccess)
wdPage = new(db.PaginatedWithdrawals)
err = getJSON(makeURL(fmt.Sprintf("v1/withdrawals/%s?finalized=false", fromAddr)), wdPage)
require.NoError(t, err)
require.Equal(t, 0, len(wdPage.Withdrawals))
})
}
type testDBParams struct {
Host string
Port uint64
User string
Password string
Name string
}
func createTestDB(t *testing.T) *testDBParams {
user := os.Getenv("DB_USER")
name := fmt.Sprintf("indexer_test_%d", time.Now().Unix())
dsn := "postgres://"
if user != "" {
dsn += user
dsn += "@"
}
dsn += "localhost:5432?sslmode=disable"
pg, err := sql.Open(
"postgres",
dsn,
)
require.NoError(t, err)
_, err = pg.Exec("CREATE DATABASE " + name)
require.NoError(t, err)
t.Cleanup(func() {
_, err = pg.Exec("DROP DATABASE " + name)
require.NoError(t, err)
pg.Close()
})
return &testDBParams{
Host: "localhost",
Port: 5432,
Name: name,
User: user,
}
}
func getJSON(url string, out interface{}) error {
res, err := http.Get(url)
if err != nil {
return err
}
if res.StatusCode != 200 {
return fmt.Errorf("non-200 status code %d", res.StatusCode)
}
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
return dec.Decode(out)
}
...@@ -21,6 +21,8 @@ type Metrics struct { ...@@ -21,6 +21,8 @@ type Metrics struct {
WithdrawalsCount *prometheus.CounterVec WithdrawalsCount *prometheus.CounterVec
StateBatchesCount prometheus.Counter
L1CatchingUp prometheus.Gauge L1CatchingUp prometheus.Gauge
L2CatchingUp prometheus.Gauge L2CatchingUp prometheus.Gauge
...@@ -72,6 +74,12 @@ func NewMetrics(monitoredTokens map[string]string) *Metrics { ...@@ -72,6 +74,12 @@ func NewMetrics(monitoredTokens map[string]string) *Metrics {
"symbol", "symbol",
}), }),
StateBatchesCount: promauto.NewCounter(prometheus.CounterOpts{
Name: "state_batches_count",
Help: "The number of state batches indexed.",
Namespace: metricsNamespace,
}),
L1CatchingUp: promauto.NewGauge(prometheus.GaugeOpts{ L1CatchingUp: promauto.NewGauge(prometheus.GaugeOpts{
Name: "l1_catching_up", Name: "l1_catching_up",
Help: "Whether or not L1 is far behind the chain tip.", Help: "Whether or not L1 is far behind the chain tip.",
...@@ -160,6 +168,10 @@ func (m *Metrics) RecordWithdrawal(addr common.Address) { ...@@ -160,6 +168,10 @@ func (m *Metrics) RecordWithdrawal(addr common.Address) {
m.WithdrawalsCount.WithLabelValues(sym).Inc() m.WithdrawalsCount.WithLabelValues(sym).Inc()
} }
func (m *Metrics) RecordStateBatches(count int) {
m.StateBatchesCount.Add(float64(count))
}
func (m *Metrics) SetL1CatchingUp(state bool) { func (m *Metrics) SetL1CatchingUp(state bool) {
var catchingUp float64 var catchingUp float64
if state { if state {
......
package indexer
import (
"errors"
"io"
"github.com/ethereum/go-ethereum/log"
"github.com/getsentry/sentry-go"
)
var jsonFmt = log.JSONFormat()
// SentryStreamHandler creates a log.Handler that behaves similarly to
// log.StreamHandler, however it writes any log with severity greater than or
// equal to log.LvlError to Sentry. In that case, the passed log.Record is
// encoded using JSON rather than the default terminal output, so that it can be
// captured for debugging in the Sentry dashboard.
func SentryStreamHandler(wr io.Writer, fmtr log.Format) log.Handler {
h := log.FuncHandler(func(r *log.Record) error {
_, err := wr.Write(fmtr.Format(r))
// If this record's severity is log.LvlError or higher,
// serialize the record using JSON and write it to Sentry. We
// also capture the error message separately so that it's easy
// to parse what the error is in the dashboard.
//
// NOTE: The log.Lvl* constants are defined in reverse order of
// their severity, i.e. zero (log.LvlCrit) is the highest
// severity.
if r.Lvl <= log.LvlError {
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("context", jsonFmt.Format(r))
sentry.CaptureException(errors.New(r.Msg))
})
}
return err
})
return log.LazyHandler(log.SyncHandler(h))
}
package services
import (
"github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
type AddressManager interface {
L1StandardBridge() (common.Address, *bindings.L1StandardBridge)
StateCommitmentChain() (common.Address, *scc.StateCommitmentChain)
OptimismPortal() (common.Address, *bindings.OptimismPortal)
}
type LegacyAddresses struct {
l1SB *bindings.L1StandardBridge
l1SBAddr common.Address
scc *scc.StateCommitmentChain
sccAddr common.Address
}
var _ AddressManager = (*LegacyAddresses)(nil)
func NewLegacyAddresses(client bind.ContractBackend, addrMgrAddr common.Address) (AddressManager, error) {
mgr, err := bindings.NewAddressManager(addrMgrAddr, client)
if err != nil {
return nil, err
}
l1SBAddr, err := mgr.GetAddress(nil, "Proxy__OVM_L1StandardBridge")
if err != nil {
return nil, err
}
sccAddr, err := mgr.GetAddress(nil, "StateCommitmentChain")
if err != nil {
return nil, err
}
l1SB, err := bindings.NewL1StandardBridge(l1SBAddr, client)
if err != nil {
return nil, err
}
sccContract, err := scc.NewStateCommitmentChain(sccAddr, client)
if err != nil {
return nil, err
}
return &LegacyAddresses{
l1SB: l1SB,
l1SBAddr: l1SBAddr,
scc: sccContract,
sccAddr: sccAddr,
}, nil
}
func (a *LegacyAddresses) L1StandardBridge() (common.Address, *bindings.L1StandardBridge) {
return a.l1SBAddr, a.l1SB
}
func (a *LegacyAddresses) StateCommitmentChain() (common.Address, *scc.StateCommitmentChain) {
return a.sccAddr, a.scc
}
func (a *LegacyAddresses) OptimismPortal() (common.Address, *bindings.OptimismPortal) {
panic("OptimismPortal not configured on legacy networks - this is a programmer error")
}
type BedrockAddresses struct {
l1SB *bindings.L1StandardBridge
l1SBAddr common.Address
portal *bindings.OptimismPortal
portalAddr common.Address
}
var _ AddressManager = (*BedrockAddresses)(nil)
func NewBedrockAddresses(client bind.ContractBackend, l1SBAddr, portalAddr common.Address) (AddressManager, error) {
l1SB, err := bindings.NewL1StandardBridge(l1SBAddr, client)
if err != nil {
return nil, err
}
portal, err := bindings.NewOptimismPortal(portalAddr, client)
if err != nil {
return nil, err
}
return &BedrockAddresses{
l1SB: l1SB,
l1SBAddr: l1SBAddr,
portal: portal,
portalAddr: portalAddr,
}, nil
}
func (b *BedrockAddresses) L1StandardBridge() (common.Address, *bindings.L1StandardBridge) {
return b.l1SBAddr, b.l1SB
}
func (b *BedrockAddresses) StateCommitmentChain() (common.Address, *scc.StateCommitmentChain) {
panic("SCC not configured on legacy networks - this is a programmer error")
}
func (b *BedrockAddresses) OptimismPortal() (common.Address, *bindings.OptimismPortal) {
return b.portalAddr, b.portal
}
...@@ -5,93 +5,85 @@ import ( ...@@ -5,93 +5,85 @@ import (
"errors" "errors"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
// DepositsMap is a collection of deposit objects keyed
// on block hashes.
type DepositsMap map[common.Hash][]db.Deposit type DepositsMap map[common.Hash][]db.Deposit
type WithdrawalsMap map[common.Hash][]db.Withdrawal // Finalizations
// WithdrawalsMap is a collection of withdrawal objects keyed
// on block hashes.
type InitiatedWithdrawalMap map[common.Hash][]db.Withdrawal
// FinalizedWithdrawalsMap is a collection of finalized withdrawal
// objected keyed on block hashes.
type FinalizedWithdrawalsMap map[common.Hash][]db.FinalizedWithdrawal
type Bridge interface { type Bridge interface {
Address() common.Address Address() common.Address
GetDepositsByBlockRange(uint64, uint64) (DepositsMap, error) GetDepositsByBlockRange(context.Context, uint64, uint64) (DepositsMap, error)
GetWithdrawalsByBlockRange(uint64, uint64) (WithdrawalsMap, error)
String() string String() string
} }
type implConfig struct { type implConfig struct {
name string name string
impl string impl string
addr string addr common.Address
}
var defaultBridgeCfgs = map[uint64][]*implConfig{
// Devnet
900: {
{"Standard", "StandardBridge", predeploys.DevL1StandardBridge},
{"ETH", "ETHBridge", predeploys.DevL1StandardBridge},
},
// Goerli
5: {
{"Standard", "StandardBridge", "0xFf94B6C486350aD92561Ba09bad3a59df764Da92"},
{"ETH", "ETHBridge", "0xFf94B6C486350aD92561Ba09bad3a59df764Da92"},
},
} }
var customBridgeCfgs = map[uint64][]*implConfig{ var customBridgeCfgs = map[uint64][]*implConfig{
// Mainnet // Mainnet
1: { 1: {
{"BitBTC", "StandardBridge", "0xaBA2c5F108F7E820C049D5Af70B16ac266c8f128"}, {"BitBTC", "StandardBridge", common.HexToAddress("0xaBA2c5F108F7E820C049D5Af70B16ac266c8f128")},
{"DAI", "StandardBridge", "0x10E6593CDda8c58a1d0f14C5164B376352a55f2F"}, {"DAI", "StandardBridge", common.HexToAddress("0x10E6593CDda8c58a1d0f14C5164B376352a55f2F")},
{"wstETH", "StandardBridge", common.HexToAddress("0x76943C0D61395d8F2edF9060e1533529cAe05dE6")},
}, },
// Kovan // Kovan
42: { 42: {
{"BitBTC", "StandardBridge", "0x0b651A42F32069d62d5ECf4f2a7e5Bd3E9438746"}, {"BitBTC", "StandardBridge", common.HexToAddress("0x0b651A42F32069d62d5ECf4f2a7e5Bd3E9438746")},
{"USX", "StandardBridge", "0x40E862341b2416345F02c41Ac70df08525150dC7"}, {"USX", "StandardBridge", common.HexToAddress("0x40E862341b2416345F02c41Ac70df08525150dC7")},
{"DAI", "StandardBridge", "0xb415e822C4983ecD6B1c1596e8a5f976cf6CD9e3"}, {"DAI", "StandardBridge", common.HexToAddress("0xb415e822C4983ecD6B1c1596e8a5f976cf6CD9e3")},
{"wstETH", "StandardBridge", common.HexToAddress("0x65321bf24210b81500230dCEce14Faa70a9f50a7")},
}, },
} }
func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, ctx context.Context) (map[string]Bridge, error) { func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, addrs services.AddressManager) (map[string]Bridge, error) {
allCfgs := make([]*implConfig, 0) l1SBAddr, _ := addrs.L1StandardBridge()
allCfgs = append(allCfgs, defaultBridgeCfgs[chainID.Uint64()]...) allCfgs := []*implConfig{
{"Standard", "StandardBridge", l1SBAddr},
{"ETH", "ETHBridge", l1SBAddr},
}
allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...) allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...)
bridges := make(map[string]Bridge) bridges := make(map[string]Bridge)
for _, bridge := range allCfgs { for _, bridge := range allCfgs {
switch bridge.impl { switch bridge.impl {
case "StandardBridge": case "StandardBridge":
l1StandardBridgeAddress := common.HexToAddress(bridge.addr) l1SB, err := bindings.NewL1StandardBridge(bridge.addr, client)
l1StandardBridgeFilter, err := bindings.NewL1StandardBridgeFilterer(l1StandardBridgeAddress, client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
standardBridge := &StandardBridge{ standardBridge := &StandardBridge{
name: bridge.name, name: bridge.name,
ctx: ctx, address: bridge.addr,
address: l1StandardBridgeAddress, contract: l1SB,
client: client,
filterer: l1StandardBridgeFilter,
} }
bridges[bridge.name] = standardBridge bridges[bridge.name] = standardBridge
case "ETHBridge": case "ETHBridge":
l1StandardBridgeAddress := common.HexToAddress(bridge.addr) l1SB, err := bindings.NewL1StandardBridge(bridge.addr, client)
l1EthBridgeFilter, err := bindings.NewL1StandardBridgeFilterer(l1StandardBridgeAddress, client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ethBridge := &EthBridge{ ethBridge := &EthBridge{
name: bridge.name, name: bridge.name,
ctx: ctx, address: bridge.addr,
address: l1StandardBridgeAddress, contract: l1SB,
client: client,
filterer: l1EthBridgeFilter,
} }
bridges[bridge.name] = ethBridge bridges[bridge.name] = ethBridge
default: default:
...@@ -100,3 +92,12 @@ func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, ctx context ...@@ -100,3 +92,12 @@ func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, ctx context
} }
return bridges, nil return bridges, nil
} }
func StateCommitmentChainScanner(client bind.ContractFilterer, addrs services.AddressManager) (*scc.StateCommitmentChainFilterer, error) {
sccAddr, _ := addrs.StateCommitmentChain()
filter, err := scc.NewStateCommitmentChainFilterer(sccAddr, client)
if err != nil {
return nil, err
}
return filter, nil
}
package bridge
import (
"context"
"errors"
"math/big"
"github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
// DepositsMap is a collection of deposit objects keyed
// on block hashes.
type DepositsMap map[common.Hash][]db.Deposit
<<<<<<< HEAD
// WithdrawalsMap is a collection of withdrawal objects keyed
// on block hashes.
type WithdrawalsMap map[common.Hash][]db.Withdrawal
// FinalizedWithdrawalsMap is a collection of finalized withdrawal
// objected keyed on block hashes.
=======
type InitiatedWithdrawalMap map[common.Hash][]db.Withdrawal
>>>>>>> 22c039efc (indexer: Upgrade L1 services)
type FinalizedWithdrawalsMap map[common.Hash][]db.FinalizedWithdrawal
type Bridge interface {
Address() common.Address
GetDepositsByBlockRange(context.Context, uint64, uint64) (DepositsMap, error)
String() string
}
type implConfig struct {
name string
impl string
addr common.Address
}
var customBridgeCfgs = map[uint64][]*implConfig{
// Mainnet
1: {
{"BitBTC", "StandardBridge", common.HexToAddress("0xaBA2c5F108F7E820C049D5Af70B16ac266c8f128")},
{"DAI", "StandardBridge", common.HexToAddress("0x10E6593CDda8c58a1d0f14C5164B376352a55f2F")},
{"wstETH", "StandardBridge", common.HexToAddress("0x76943C0D61395d8F2edF9060e1533529cAe05dE6")},
},
// Kovan
42: {
{"BitBTC", "StandardBridge", common.HexToAddress("0x0b651A42F32069d62d5ECf4f2a7e5Bd3E9438746")},
{"USX", "StandardBridge", common.HexToAddress("0x40E862341b2416345F02c41Ac70df08525150dC7")},
{"DAI", "StandardBridge", common.HexToAddress("0xb415e822C4983ecD6B1c1596e8a5f976cf6CD9e3")},
{"wstETH", "StandardBridge", common.HexToAddress("0x65321bf24210b81500230dCEce14Faa70a9f50a7")},
},
}
func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, addrs services.AddressManager) (map[string]Bridge, error) {
l1SBAddr, _ := addrs.L1StandardBridge()
allCfgs := []*implConfig{
{"Standard", "StandardBridge", l1SBAddr},
{"ETH", "ETHBridge", l1SBAddr},
}
allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...)
bridges := make(map[string]Bridge)
for _, bridge := range allCfgs {
switch bridge.impl {
case "StandardBridge":
l1SB, err := bindings.NewL1StandardBridge(bridge.addr, client)
if err != nil {
return nil, err
}
standardBridge := &StandardBridge{
name: bridge.name,
address: bridge.addr,
contract: l1SB,
}
bridges[bridge.name] = standardBridge
case "ETHBridge":
l1SB, err := bindings.NewL1StandardBridge(bridge.addr, client)
if err != nil {
return nil, err
}
ethBridge := &EthBridge{
name: bridge.name,
address: bridge.addr,
contract: l1SB,
}
bridges[bridge.name] = ethBridge
default:
return nil, errors.New("unsupported bridge")
}
}
return bridges, nil
}
func StateCommitmentChainScanner(client bind.ContractFilterer, addrs services.AddressManager) (*scc.StateCommitmentChainFilterer, error) {
sccAddr, _ := addrs.StateCommitmentChain()
filter, err := scc.NewStateCommitmentChainFilterer(sccAddr, client)
if err != nil {
return nil, err
}
return filter, nil
}
...@@ -5,33 +5,40 @@ import ( ...@@ -5,33 +5,40 @@ import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type EthBridge struct { type EthBridge struct {
name string name string
ctx context.Context
address common.Address address common.Address
client bind.ContractFilterer contract *bindings.L1StandardBridge
filterer *bindings.L1StandardBridgeFilterer
} }
func (e *EthBridge) Address() common.Address { func (e *EthBridge) Address() common.Address {
return e.address return e.address
} }
func (e *EthBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, error) { func (e *EthBridge) GetDepositsByBlockRange(ctx context.Context, start, end uint64) (DepositsMap, error) {
depositsByBlockhash := make(DepositsMap) depositsByBlockhash := make(DepositsMap)
opts := &bind.FilterOpts{
Context: ctx,
Start: start,
End: &end,
}
iter, err := FilterETHDepositInitiatedWithRetry(e.ctx, e.filterer, &bind.FilterOpts{ var iter *bindings.L1StandardBridgeETHDepositInitiatedIterator
Start: start, err := backoff.Do(3, backoff.Exponential(), func() error {
End: &end, var err error
iter, err = e.contract.FilterETHDepositInitiated(opts, nil, nil)
return err
}) })
if err != nil { if err != nil {
logger.Error("Error fetching filter", "err", err) return nil, err
} }
defer iter.Close()
for iter.Next() { for iter.Next() {
depositsByBlockhash[iter.Event.Raw.BlockHash] = append( depositsByBlockhash[iter.Event.Raw.BlockHash] = append(
depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{ depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{
...@@ -43,41 +50,8 @@ func (e *EthBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, err ...@@ -43,41 +50,8 @@ func (e *EthBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, err
LogIndex: iter.Event.Raw.Index, LogIndex: iter.Event.Raw.Index,
}) })
} }
if err := iter.Error(); err != nil {
return nil, err
}
return depositsByBlockhash, nil
}
func (s *EthBridge) GetWithdrawalsByBlockRange(start, end uint64) (WithdrawalsMap, error) {
withdrawalsByBlockHash := make(WithdrawalsMap)
iter, err := FilterETHWithdrawalFinalizedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{
Start: start,
End: &end,
})
if err != nil {
logger.Error("Error fetching filter", "err", err)
}
for iter.Next() {
withdrawalsByBlockHash[iter.Event.Raw.BlockHash] = append(
withdrawalsByBlockHash[iter.Event.Raw.BlockHash], db.Withdrawal{
TxHash: iter.Event.Raw.TxHash,
FromAddress: iter.Event.From,
ToAddress: iter.Event.To,
Amount: iter.Event.Amount,
Data: iter.Event.ExtraData,
LogIndex: iter.Event.Raw.Index,
})
}
if err := iter.Error(); err != nil {
return nil, err
}
return withdrawalsByBlockHash, nil return depositsByBlockhash, iter.Error()
} }
func (e *EthBridge) String() string { func (e *EthBridge) String() string {
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"context" "context"
"time" "time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
) )
...@@ -12,61 +12,13 @@ import ( ...@@ -12,61 +12,13 @@ import (
// calls. // calls.
var clientRetryInterval = 5 * time.Second var clientRetryInterval = 5 * time.Second
// FilterETHDepositInitiatedWithRetry retries the given func until it succeeds, // FilterStateBatchAppendedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call. // waiting for clientRetryInterval duration after every call.
func FilterETHDepositInitiatedWithRetry(ctx context.Context, filterer *bindings.L1StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L1StandardBridgeETHDepositInitiatedIterator, error) { func FilterStateBatchAppendedWithRetry(ctx context.Context, filterer *scc.StateCommitmentChainFilterer, opts *bind.FilterOpts) (*scc.StateCommitmentChainStateBatchAppendedIterator, error) {
for { for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout) ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt opts.Context = ctxt
res, err := filterer.FilterETHDepositInitiated(opts, nil, nil) res, err := filterer.FilterStateBatchAppended(opts, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
// FilterERC20DepositInitiatedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterERC20DepositInitiatedWithRetry(ctx context.Context, filterer *bindings.L1StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L1StandardBridgeERC20DepositInitiatedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterERC20DepositInitiated(opts, nil, nil, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
// FilterETHWithdrawalFinalizedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterETHWithdrawalFinalizedWithRetry(ctx context.Context, filterer *bindings.L1StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L1StandardBridgeETHWithdrawalFinalizedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterETHWithdrawalFinalized(opts, nil, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
// FilterERC20WithdrawalFinalizedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterERC20WithdrawalFinalizedWithRetry(ctx context.Context, filterer *bindings.L1StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L1StandardBridgeERC20WithdrawalFinalizedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterERC20WithdrawalFinalized(opts, nil, nil, nil)
cancel() cancel()
if err == nil { if err == nil {
return res, nil return res, nil
......
package bridge
import (
"context"
"github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
type Portal struct {
address common.Address
contract *bindings.OptimismPortal
}
func NewPortal(addrs services.AddressManager) *Portal {
address, contract := addrs.OptimismPortal()
return &Portal{
address: address,
contract: contract,
}
}
func (p *Portal) Address() common.Address {
return p.address
}
func (p *Portal) GetFinalizedWithdrawalsByBlockRange(ctx context.Context, start, end uint64) (FinalizedWithdrawalsMap, error) {
wdsByBlockHash := make(FinalizedWithdrawalsMap)
opts := &bind.FilterOpts{
Context: ctx,
Start: start,
End: &end,
}
var iter *bindings.OptimismPortalWithdrawalFinalizedIterator
err := backoff.Do(3, backoff.Exponential(), func() error {
var err error
iter, err = p.contract.FilterWithdrawalFinalized(opts, nil)
return err
})
if err != nil {
return nil, err
}
defer iter.Close()
for iter.Next() {
wdsByBlockHash[iter.Event.Raw.BlockHash] = append(
wdsByBlockHash[iter.Event.Raw.BlockHash], db.FinalizedWithdrawal{
TxHash: iter.Event.Raw.TxHash,
WithdrawalHash: iter.Event.WithdrawalHash,
Success: iter.Event.Success,
LogIndex: iter.Event.Raw.Index,
},
)
}
return wdsByBlockHash, iter.Error()
}
...@@ -5,33 +5,40 @@ import ( ...@@ -5,33 +5,40 @@ import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type StandardBridge struct { type StandardBridge struct {
name string name string
ctx context.Context
address common.Address address common.Address
client bind.ContractFilterer contract *bindings.L1StandardBridge
filterer *bindings.L1StandardBridgeFilterer
} }
func (s *StandardBridge) Address() common.Address { func (s *StandardBridge) Address() common.Address {
return s.address return s.address
} }
func (s *StandardBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, error) { func (s *StandardBridge) GetDepositsByBlockRange(ctx context.Context, start, end uint64) (DepositsMap, error) {
depositsByBlockhash := make(DepositsMap) depositsByBlockhash := make(DepositsMap)
opts := &bind.FilterOpts{
Context: ctx,
Start: start,
End: &end,
}
iter, err := FilterERC20DepositInitiatedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{ var iter *bindings.L1StandardBridgeERC20DepositInitiatedIterator
Start: start, err := backoff.Do(3, backoff.Exponential(), func() error {
End: &end, var err error
iter, err = s.contract.FilterERC20DepositInitiated(opts, nil, nil, nil)
return err
}) })
if err != nil { if err != nil {
logger.Error("Error fetching filter", "err", err) return nil, err
} }
defer iter.Close()
for iter.Next() { for iter.Next() {
depositsByBlockhash[iter.Event.Raw.BlockHash] = append( depositsByBlockhash[iter.Event.Raw.BlockHash] = append(
depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{ depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{
...@@ -45,43 +52,8 @@ func (s *StandardBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap ...@@ -45,43 +52,8 @@ func (s *StandardBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap
LogIndex: iter.Event.Raw.Index, LogIndex: iter.Event.Raw.Index,
}) })
} }
if err := iter.Error(); err != nil {
return nil, err
}
return depositsByBlockhash, nil
}
func (s *StandardBridge) GetWithdrawalsByBlockRange(start, end uint64) (WithdrawalsMap, error) {
withdrawalsByBlockHash := make(WithdrawalsMap)
iter, err := FilterERC20WithdrawalFinalizedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{
Start: start,
End: &end,
})
if err != nil {
logger.Error("Error fetching filter", "err", err)
}
for iter.Next() {
withdrawalsByBlockHash[iter.Event.Raw.BlockHash] = append(
withdrawalsByBlockHash[iter.Event.Raw.BlockHash], db.Withdrawal{
TxHash: iter.Event.Raw.TxHash,
L1Token: iter.Event.L1Token,
L2Token: iter.Event.L2Token,
FromAddress: iter.Event.From,
ToAddress: iter.Event.To,
Amount: iter.Event.Amount,
Data: iter.Event.ExtraData,
LogIndex: iter.Event.Raw.Index,
})
}
if err := iter.Error(); err != nil {
return nil, err
}
return withdrawalsByBlockHash, nil return depositsByBlockhash, iter.Error()
} }
func (s *StandardBridge) String() string { func (s *StandardBridge) String() string {
......
...@@ -17,9 +17,8 @@ import ( ...@@ -17,9 +17,8 @@ import (
) )
const ( const (
DefaultConnectionTimeout = 30 * time.Second DefaultConnectionTimeout = 30 * time.Second
DefaultConfDepth uint64 = 20 DefaultMaxBatchSize = 100
DefaultMaxBatchSize = 100
) )
type NewHeader struct { type NewHeader struct {
......
package l1 package l1
import ( import (
"github.com/ethereum/go-ethereum/ethclient" "context"
"github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/indexer/services/l1/bridge"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
func QueryERC20(address common.Address, client *ethclient.Client) (*db.Token, error) { func QueryStateBatches(filterer *scc.StateCommitmentChainFilterer, startHeight, endHeight uint64, ctx context.Context) (map[common.Hash][]db.StateBatch, error) {
contract, err := bindings.NewERC20(address, client) batches := make(map[common.Hash][]db.StateBatch)
if err != nil {
return nil, err
}
name, err := contract.Name(&bind.CallOpts{})
if err != nil {
return nil, err
}
symbol, err := contract.Symbol(&bind.CallOpts{}) iter, err := bridge.FilterStateBatchAppendedWithRetry(ctx, filterer, &bind.FilterOpts{
Start: startHeight,
End: &endHeight,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
decimals, err := contract.Decimals(&bind.CallOpts{}) defer iter.Close()
if err != nil { for iter.Next() {
return nil, err batches[iter.Event.Raw.BlockHash] = append(
batches[iter.Event.Raw.BlockHash], db.StateBatch{
Index: iter.Event.BatchIndex,
Root: iter.Event.BatchRoot,
Size: iter.Event.BatchSize,
PrevTotal: iter.Event.PrevTotalElements,
ExtraData: iter.Event.ExtraData,
BlockHash: iter.Event.Raw.BlockHash,
})
} }
return batches, iter.Error()
return &db.Token{
Name: name,
Symbol: symbol,
Decimals: decimals,
}, nil
} }
This diff is collapsed.
...@@ -7,75 +7,76 @@ import ( ...@@ -7,75 +7,76 @@ import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type DepositsMap map[common.Hash][]db.Deposit // Finalizations
type WithdrawalsMap map[common.Hash][]db.Withdrawal type WithdrawalsMap map[common.Hash][]db.Withdrawal
type Bridge interface { type Bridge interface {
Address() common.Address Address() common.Address
GetDepositsByBlockRange(uint64, uint64) (DepositsMap, error) GetWithdrawalsByBlockRange(context.Context, uint64, uint64) (WithdrawalsMap, error)
GetWithdrawalsByBlockRange(uint64, uint64) (WithdrawalsMap, error)
String() string String() string
} }
type implConfig struct { type implConfig struct {
name string name string
impl string impl string
addr string addr common.Address
} }
var defaultBridgeCfgs = map[uint64][]*implConfig{ var defaultBridgeCfgs = []*implConfig{
// Devnet {"Standard", "StandardBridge", predeploys.L2StandardBridgeAddr},
901: {
{"Standard", "StandardBridge", L2StandardBridgeAddr},
},
// Goerli Alpha Testnet
28528: {
{"Standard", "StandardBridge", L2StandardBridgeAddr},
},
} }
var customBridgeCfgs = map[uint64][]*implConfig{ var customBridgeCfgs = map[uint64][]*implConfig{
// Mainnet // Mainnet
10: { 10: {
{"BitBTC", StandardBridgeImpl, "0x158F513096923fF2d3aab2BcF4478536de6725e2"}, {"BitBTC", StandardBridgeImpl, common.HexToAddress("0x158F513096923fF2d3aab2BcF4478536de6725e2")},
//{"DAI", "DAIBridge", "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65"}, //{"DAI", "DAIBridge", "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65"},
{"wstETH", StandardBridgeImpl, common.HexToAddress("0x8E01013243a96601a86eb3153F0d9Fa4fbFb6957")},
}, },
// Kovan // Kovan
69: { 69: {
{"BitBTC", StandardBridgeImpl, "0x0CFb46528a7002a7D8877a5F7a69b9AaF1A9058e"}, {"BitBTC", StandardBridgeImpl, common.HexToAddress("0x0CFb46528a7002a7D8877a5F7a69b9AaF1A9058e")},
{"USX", StandardBridgeImpl, "0xB4d37826b14Cd3CB7257A2A5094507d701fe715f"}, {"USX", StandardBridgeImpl, common.HexToAddress("0xB4d37826b14Cd3CB7257A2A5094507d701fe715f")},
{"wstETH", StandardBridgeImpl, common.HexToAddress("0x2E34e7d705AfaC3C4665b6feF31Aa394A1c81c92")},
//{"DAI", " DAIBridge", "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65"}, //{"DAI", " DAIBridge", "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65"},
}, },
} }
func BridgesByChainID(chainID *big.Int, client bind.ContractFilterer, ctx context.Context) (map[string]Bridge, error) { func BridgesByChainID(chainID *big.Int, client *ethclient.Client, isBedrock bool) (map[string]Bridge, error) {
allCfgs := make([]*implConfig, 0) allCfgs := make([]*implConfig, 0)
allCfgs = append(allCfgs, defaultBridgeCfgs[chainID.Uint64()]...) allCfgs = append(allCfgs, defaultBridgeCfgs...)
allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...) allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...)
var l2L1MP *bindings.L2ToL1MessagePasser
var err error
if isBedrock {
l2L1MP, err = bindings.NewL2ToL1MessagePasser(predeploys.L2ToL1MessagePasserAddr, client)
if err != nil {
return nil, err
}
}
bridges := make(map[string]Bridge) bridges := make(map[string]Bridge)
for _, bridge := range allCfgs { for _, bridge := range allCfgs {
switch bridge.impl { switch bridge.impl {
case "StandardBridge": case "StandardBridge":
l2StandardBridgeAddress := common.HexToAddress(bridge.addr) l2SB, err := bindings.NewL2StandardBridge(bridge.addr, client)
l2StandardBridgeFilter, err := bindings.NewL2StandardBridgeFilterer(l2StandardBridgeAddress, client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
bridges[bridge.name] = &StandardBridge{
standardBridge := &StandardBridge{ name: bridge.name,
name: bridge.name, address: bridge.addr,
ctx: ctx, client: client,
address: l2StandardBridgeAddress, l2SB: l2SB,
client: client, l2L1MP: l2L1MP,
filterer: l2StandardBridgeFilter, isBedrock: isBedrock,
} }
bridges[bridge.name] = standardBridge
default: default:
return nil, errors.New("unsupported bridge") return nil, errors.New("unsupported bridge")
} }
......
package bridge
import (
"context"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
)
// clientRetryInterval is the interval to wait between retrying client API
// calls.
var clientRetryInterval = 5 * time.Second
// FilterWithdrawalInitiatedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterWithdrawalInitiatedWithRetry(ctx context.Context, filterer *bindings.L2StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L2StandardBridgeWithdrawalInitiatedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterWithdrawalInitiated(opts, nil, nil, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
// FilterDepositFinalizedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterDepositFinalizedWithRetry(ctx context.Context, filterer *bindings.L2StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L2StandardBridgeDepositFinalizedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterDepositFinalized(opts, nil, nil, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
...@@ -5,83 +5,112 @@ import ( ...@@ -5,83 +5,112 @@ import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type StandardBridge struct { type StandardBridge struct {
name string name string
ctx context.Context address common.Address
address common.Address client *ethclient.Client
client bind.ContractFilterer l2SB *bindings.L2StandardBridge
filterer *bindings.L2StandardBridgeFilterer l2L1MP *bindings.L2ToL1MessagePasser
isBedrock bool
} }
func (s *StandardBridge) Address() common.Address { func (s *StandardBridge) Address() common.Address {
return s.address return s.address
} }
func (s *StandardBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, error) { func (s *StandardBridge) GetWithdrawalsByBlockRange(ctx context.Context, start, end uint64) (WithdrawalsMap, error) {
depositsByBlockhash := make(DepositsMap) withdrawalsByBlockhash := make(map[common.Hash][]db.Withdrawal)
opts := &bind.FilterOpts{
Context: ctx,
Start: start,
End: &end,
}
iter, err := FilterDepositFinalizedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{ var iter *bindings.L2StandardBridgeWithdrawalInitiatedIterator
Start: start, err := backoff.Do(3, backoff.Exponential(), func() error {
End: &end, var err error
iter, err = s.l2SB.FilterWithdrawalInitiated(opts, nil, nil, nil)
return err
}) })
if err != nil { if err != nil {
logger.Error("Error fetching filter", "err", err) return nil, err
} }
receipts := make(map[common.Hash]*types.Receipt)
defer iter.Close()
for iter.Next() { for iter.Next() {
depositsByBlockhash[iter.Event.Raw.BlockHash] = append( ev := iter.Event
depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{ if s.isBedrock {
TxHash: iter.Event.Raw.TxHash, receipt := receipts[ev.Raw.TxHash]
L1Token: iter.Event.L1Token, if receipt == nil {
L2Token: iter.Event.L2Token, receipt, err = s.client.TransactionReceipt(ctx, ev.Raw.TxHash)
FromAddress: iter.Event.From, if err != nil {
ToAddress: iter.Event.To, return nil, err
Amount: iter.Event.Amount, }
Data: iter.Event.ExtraData, receipts[ev.Raw.TxHash] = receipt
LogIndex: iter.Event.Raw.Index, }
})
}
if err := iter.Error(); err != nil {
return nil, err
}
return depositsByBlockhash, nil var withdrawalInitiated *bindings.L2ToL1MessagePasserMessagePassed
} for _, eLog := range receipt.Logs {
if len(eLog.Topics) == 0 || eLog.Topics[0] != withdrawals.MessagePassedTopic {
continue
}
func (s *StandardBridge) GetWithdrawalsByBlockRange(start, end uint64) (WithdrawalsMap, error) { if withdrawalInitiated != nil {
withdrawalsByBlockhash := make(map[common.Hash][]db.Withdrawal) logger.Warn("detected multiple withdrawal initiated events! ignoring", "tx_hash", ev.Raw.TxHash)
continue
}
iter, err := FilterWithdrawalInitiatedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{ withdrawalInitiated, err = s.l2L1MP.ParseMessagePassed(*eLog)
Start: start, if err != nil {
End: &end, return nil, err
}) }
if err != nil { }
logger.Error("Error fetching filter", "err", err)
}
for iter.Next() { hash, err := withdrawals.WithdrawalHash(withdrawalInitiated)
withdrawalsByBlockhash[iter.Event.Raw.BlockHash] = append( if err != nil {
withdrawalsByBlockhash[iter.Event.Raw.BlockHash], db.Withdrawal{ return nil, err
TxHash: iter.Event.Raw.TxHash, }
L1Token: iter.Event.L1Token,
L2Token: iter.Event.L2Token, withdrawalsByBlockhash[ev.Raw.BlockHash] = append(
FromAddress: iter.Event.From, withdrawalsByBlockhash[ev.Raw.BlockHash], db.Withdrawal{
ToAddress: iter.Event.To, TxHash: ev.Raw.TxHash,
Amount: iter.Event.Amount, L1Token: ev.L1Token,
Data: iter.Event.ExtraData, L2Token: ev.L2Token,
LogIndex: iter.Event.Raw.Index, FromAddress: ev.From,
}) ToAddress: ev.To,
} Amount: ev.Amount,
if err := iter.Error(); err != nil { Data: ev.ExtraData,
return nil, err LogIndex: ev.Raw.Index,
BedrockHash: &hash,
},
)
} else {
withdrawalsByBlockhash[ev.Raw.BlockHash] = append(
withdrawalsByBlockhash[ev.Raw.BlockHash], db.Withdrawal{
TxHash: ev.Raw.TxHash,
L1Token: ev.L1Token,
L2Token: ev.L2Token,
FromAddress: ev.From,
ToAddress: ev.To,
Amount: ev.Amount,
Data: ev.ExtraData,
LogIndex: ev.Raw.Index,
},
)
}
} }
return withdrawalsByBlockhash, nil return withdrawalsByBlockhash, iter.Error()
} }
func (s *StandardBridge) String() string { func (s *StandardBridge) String() string {
......
...@@ -147,8 +147,7 @@ func (f *ConfirmedHeaderSelector) NewHead( ...@@ -147,8 +147,7 @@ func (f *ConfirmedHeaderSelector) NewHead(
return headers, nil return headers, nil
} }
func NewConfirmedHeaderSelector(cfg HeaderSelectorConfig) (*ConfirmedHeaderSelector, func NewConfirmedHeaderSelector(cfg HeaderSelectorConfig) (*ConfirmedHeaderSelector, error) {
error) {
if cfg.ConfDepth == 0 { if cfg.ConfDepth == 0 {
return nil, errors.New("ConfDepth must be greater than zero") return nil, errors.New("ConfDepth must be greater than zero")
} }
......
This diff is collapsed.
package l2 package query
import ( import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
) )
func QueryERC20(address common.Address, client *ethclient.Client) (*db.Token, error) { func NewERC20(address common.Address, client *ethclient.Client) (*db.Token, error) {
contract, err := bindings.NewERC20(address, client) contract, err := bindings.NewERC20(address, client)
if err != nil { if err != nil {
return nil, err return nil, err
......
package query
import (
"context"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
// HeaderByNumberWithRetry retries getting headers.
func HeaderByNumberWithRetry(ctx context.Context, client *ethclient.Client) (*types.Header, error) {
var res *types.Header
err := backoff.DoCtx(ctx, 3, backoff.Exponential(), func() error {
var err error
res, err = client.HeaderByNumber(ctx, nil)
return err
})
return res, err
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment