diff --git a/go/indexer/Makefile b/go/indexer/Makefile index dccde03fb75f1..c20b3efab3ada 100644 --- a/go/indexer/Makefile +++ b/go/indexer/Makefile @@ -26,7 +26,7 @@ test: lint: golangci-lint run ./... -bindings: bindings-l1bridge bindings-l2bridge bindings-l1erc20 bindings-l2erc20 bindings-scc +bindings: bindings-l1bridge bindings-l2bridge bindings-l1erc20 bindings-l2erc20 bindings-scc bindings-address-manager bindings-l1bridge: $(eval temp := $(shell mktemp)) @@ -130,6 +130,7 @@ bindings-address-manager: bindings-l1erc20 \ bindings-l2erc20 \ bindings-scc \ + bindings-address-manager clean \ test \ lint diff --git a/go/indexer/config.go b/go/indexer/config.go index 07158cbd2531d..a78bed6f5018a 100644 --- a/go/indexer/config.go +++ b/go/indexer/config.go @@ -96,6 +96,12 @@ type Config struct { // batch. MaxHeaderBatchSize uint64 + // RESTHostname is the hostname at which the REST server is running. + RESTHostname string + + // RESTPort is the port at which the REST server is running. + RESTPort uint64 + // MetricsServerEnable if true, will create a metrics client and log to // Prometheus. MetricsServerEnable bool @@ -118,8 +124,8 @@ func NewConfig(ctx *cli.Context) (Config, error) { BuildEnv: ctx.GlobalString(flags.BuildEnvFlag.Name), EthNetworkName: ctx.GlobalString(flags.EthNetworkNameFlag.Name), ChainID: ctx.GlobalInt64(flags.ChainIDFlag.Name), - L1EthRpc: ctx.GlobalString(flags.L1EthRpcFlag.Name), - L2EthRpc: ctx.GlobalString(flags.L2EthRpcFlag.Name), + L1EthRpc: ctx.GlobalString(flags.L1EthRPCFlag.Name), + L2EthRpc: ctx.GlobalString(flags.L2EthRPCFlag.Name), L1AddressManagerAddress: ctx.GlobalString(flags.L1AddressManagerAddressFlag.Name), L2GenesisBlockHash: ctx.GlobalString(flags.L2GenesisBlockHashFlag.Name), DBHost: ctx.GlobalString(flags.DBHostFlag.Name), @@ -139,6 +145,8 @@ func NewConfig(ctx *cli.Context) (Config, error) { ConfDepth: ctx.GlobalUint64(flags.ConfDepthFlag.Name), MaxHeaderBatchSize: ctx.GlobalUint64(flags.MaxHeaderBatchSizeFlag.Name), MetricsServerEnable: ctx.GlobalBool(flags.MetricsServerEnableFlag.Name), + RESTHostname: ctx.GlobalString(flags.RESTHostnameFlag.Name), + RESTPort: ctx.GlobalUint64(flags.RESTPortFlag.Name), MetricsHostname: ctx.GlobalString(flags.MetricsHostnameFlag.Name), MetricsPort: ctx.GlobalUint64(flags.MetricsPortFlag.Name), } diff --git a/go/indexer/crypto_test.go b/go/indexer/crypto_test.go index 19f1a00dd1600..796f0ea288f82 100644 --- a/go/indexer/crypto_test.go +++ b/go/indexer/crypto_test.go @@ -6,13 +6,15 @@ import ( "testing" indexer "github.com/ethereum-optimism/optimism/go/indexer" + l2common "github.com/ethereum-optimism/optimism/l2geth/common" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) -// TestParseAddress asserts that ParseAddress correctly parses 40-characater -// hexidecimal strings with optional 0x prefix into valid 20-byte addresses. -func TestParseAddress(t *testing.T) { +// TestParseL1Address asserts that ParseL1Address correctly parses +// 40-characater hexidecimal strings with optional 0x prefix into valid 20-byte +// addresses for the L1 chain. +func TestParseL1Address(t *testing.T) { tests := []struct { name string addr string @@ -44,7 +46,52 @@ func TestParseAddress(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - addr, err := indexer.ParseAddress(test.addr) + addr, err := indexer.ParseL1Address(test.addr) + require.Equal(t, err, test.expErr) + if test.expErr != nil { + return + } + require.Equal(t, addr, test.expAddr) + }) + } +} + +// TestParseL2Address asserts that ParseL2Address correctly parses +// 40-characater hexidecimal strings with optional 0x prefix into valid 20-byte +// addresses for the L2 chain. +func TestParseL2Address(t *testing.T) { + tests := []struct { + name string + addr string + expErr error + expAddr l2common.Address + }{ + { + name: "empty address", + addr: "", + expErr: errors.New("invalid address: "), + }, + { + name: "only 0x", + addr: "0x", + expErr: errors.New("invalid address: 0x"), + }, + { + name: "non hex character", + addr: "0xaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + expErr: errors.New("invalid address: 0xaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + }, + { + name: "valid address", + addr: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + expErr: nil, + expAddr: l2common.BytesToAddress(bytes.Repeat([]byte{170}, 20)), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + addr, err := indexer.ParseL2Address(test.addr) require.Equal(t, err, test.expErr) if test.expErr != nil { return diff --git a/go/indexer/db/db.go b/go/indexer/db/db.go index 9f9a08c77613e..30a2a752d24cc 100644 --- a/go/indexer/db/db.go +++ b/go/indexer/db/db.go @@ -3,303 +3,34 @@ package db import ( "database/sql" "errors" - "math/big" - - "github.com/google/uuid" l2common "github.com/ethereum-optimism/optimism/l2geth/common" "github.com/ethereum/go-ethereum/common" - _ "github.com/lib/pq" -) -const createL1BlocksTable = ` -CREATE TABLE IF NOT EXISTS l1_blocks ( - hash VARCHAR NOT NULL PRIMARY KEY, - parent_hash VARCHAR NOT NULL, - number INTEGER NOT NULL, - timestamp INTEGER NOT NULL -) -` - -const createL2BlocksTable = ` -CREATE TABLE IF NOT EXISTS l2_blocks ( - hash VARCHAR NOT NULL PRIMARY KEY, - parent_hash VARCHAR NOT NULL, - number INTEGER NOT NULL, - timestamp INTEGER NOT NULL -) -` - -const createDepositsTable = ` -CREATE TABLE IF NOT EXISTS deposits ( - guid VARCHAR PRIMARY KEY NOT NULL, - from_address VARCHAR NOT NULL, - to_address VARCHAR NOT NULL, - l1_token VARCHAR NOT NULL REFERENCES l1_tokens(address), - l2_token VARCHAR NOT NULL, - amount VARCHAR NOT NULL, - data BYTEA NOT NULL, - log_index INTEGER NOT NULL, - block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash), - tx_hash VARCHAR NOT NULL -) -` - -const createL1TokensTable = ` -CREATE TABLE IF NOT EXISTS l1_tokens ( - address VARCHAR NOT NULL PRIMARY KEY, - name VARCHAR NOT NULL, - symbol VARCHAR NOT NULL, - decimals INTEGER NOT NULL -) -` - -const createL2TokensTable = ` -CREATE TABLE IF NOT EXISTS l2_tokens ( - address TEXT NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - symbol TEXT NOT NULL, - decimals INTEGER NOT NULL -) -` - -const createStateBatchesTable = ` -CREATE TABLE IF NOT EXISTS state_batches ( - index INTEGER NOT NULL PRIMARY KEY, - root VARCHAR NOT NULL, - size INTEGER NOT NULL, - prev_total INTEGER NOT NULL, - extra_data BYTEA NOT NULL, - block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash) -); -CREATE INDEX IF NOT EXISTS state_batches_block_hash ON state_batches(block_hash); -CREATE INDEX IF NOT EXISTS state_batches_size ON state_batches(size); -CREATE INDEX IF NOT EXISTS state_batches_prev_total ON state_batches(prev_total); -` - -const createWithdrawalsTable = ` -CREATE TABLE IF NOT EXISTS withdrawals ( - guid VARCHAR PRIMARY KEY NOT NULL, - from_address VARCHAR NOT NULL, - to_address VARCHAR NOT NULL, - l1_token VARCHAR NOT NULL, - l2_token VARCHAR NOT NULL REFERENCES l2_tokens(address), - amount VARCHAR NOT NULL, - data BYTEA NOT NULL, - log_index INTEGER NOT NULL, - block_hash VARCHAR NOT NULL REFERENCES l2_blocks(hash), - tx_hash VARCHAR NOT NULL, - state_batch INTEGER REFERENCES state_batches(index) + // NOTE: Only postgresql backend is supported at the moment. + _ "github.com/lib/pq" ) -` - -const insertETHL1Token = ` -INSERT INTO l1_tokens - (address, name, symbol, decimals) -VALUES ('0x0000000000000000000000000000000000000000', 'Ethereum', 'ETH', 18) -ON CONFLICT (address) DO NOTHING; -` - -// earlier transactions used 0x0000000000000000000000000000000000000000 as -// address of ETH so insert both that and -// 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 -const insertETHL2Token = ` -INSERT INTO l2_tokens - (address, name, symbol, decimals) -VALUES ('0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000', 'Ethereum', 'ETH', 18) -ON CONFLICT (address) DO NOTHING; -INSERT INTO l2_tokens - (address, name, symbol, decimals) -VALUES ('0x0000000000000000000000000000000000000000', 'Ethereum', 'ETH', 18) -ON CONFLICT (address) DO NOTHING; -` - -const createL1L2NumberIndex = ` -CREATE UNIQUE INDEX IF NOT EXISTS l1_blocks_number ON l1_blocks(number); -CREATE UNIQUE INDEX IF NOT EXISTS l2_blocks_number ON l2_blocks(number); -` - -type PaginationParam struct { - Limit uint64 - Offset uint64 -} - -var schema = []string{ - createL1BlocksTable, - createL2BlocksTable, - createL1TokensTable, - createL2TokensTable, - createStateBatchesTable, - insertETHL1Token, - insertETHL2Token, - createDepositsTable, - createWithdrawalsTable, - createL1L2NumberIndex, -} - -type TxnEnqueuedEvent struct { - BlockNumber uint64 - Timestamp uint64 - TxHash common.Hash - Data []byte -} - -func (e TxnEnqueuedEvent) String() string { - return e.TxHash.String() -} - -type Deposit struct { - GUID string - TxHash common.Hash - L1Token common.Address - L2Token common.Address - FromAddress common.Address - ToAddress common.Address - Amount *big.Int - Data []byte - LogIndex uint -} - -func (d Deposit) String() string { - return d.TxHash.String() -} - -type Withdrawal struct { - GUID string - TxHash l2common.Hash - L1Token l2common.Address - L2Token l2common.Address - FromAddress l2common.Address - ToAddress l2common.Address - Amount *big.Int - Data []byte - LogIndex uint -} - -func (w Withdrawal) String() string { - return w.TxHash.String() -} - -type IndexedL1Block struct { - Hash common.Hash - ParentHash common.Hash - Number uint64 - Timestamp uint64 - Deposits []Deposit -} - -func (b IndexedL1Block) String() string { - return b.Hash.String() -} - -type IndexedL2Block struct { - Hash l2common.Hash - ParentHash l2common.Hash - Number uint64 - Timestamp uint64 - Withdrawals []Withdrawal -} - -func (b IndexedL2Block) String() string { - return b.Hash.String() -} - -func (b *IndexedL1Block) Events() []TxnEnqueuedEvent { - nDeposits := len(b.Deposits) - if nDeposits == 0 { - return nil - } - - var events = make([]TxnEnqueuedEvent, 0, nDeposits) - for _, deposit := range b.Deposits { - events = append(events, TxnEnqueuedEvent{ - BlockNumber: b.Number, - Timestamp: b.Timestamp, - TxHash: deposit.TxHash, - Data: deposit.Data, // TODO: copy? - }) - } - - return events -} -type StateBatch struct { - Index *big.Int - Root common.Hash - Size *big.Int - PrevTotal *big.Int - ExtraData []byte - BlockHash common.Hash -} - -type StateBatchJSON struct { - Index uint64 `json:"index"` - Root string `json:"root"` - Size uint64 `json:"size"` - PrevTotal uint64 `json:"prevTotal"` - ExtraData []byte `json:"extraData"` - BlockHash string `json:"blockHash"` - BlockNumber uint64 `json:"blockNumber"` - BlockTimestamp uint64 `json:"blockTimestamp"` -} - -type Token struct { - Address string `json:"address"` - Name string `json:"name"` - Symbol string `json:"symbol"` - Decimals uint8 `json:"decimals"` -} - -var ETHL1Token = &Token{ - Address: "0x0000000000000000000000000000000000000000", - Name: "Ethereum", - Symbol: "ETH", - Decimals: 18, -} - -var ETHL2Address = l2common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000") - -var ETHL2Token = &Token{ - Address: "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", - Name: "Ethereum", - Symbol: "ETH", - Decimals: 18, -} - -type DepositJSON struct { - GUID string `json:"guid"` - FromAddress string `json:"from"` - ToAddress string `json:"to"` - L1Token *Token `json:"l1Token"` - L2Token string `json:"l2Token"` - Amount string `json:"amount"` - Data []byte `json:"data"` - LogIndex uint64 `json:"logIndex"` - BlockNumber uint64 `json:"blockNumber"` - BlockTimestamp string `json:"blockTimestamp"` - TxHash string `json:"transactionHash"` +// Database contains the database instance and the connection string. +type Database struct { + db *sql.DB + config string } -type WithdrawalJSON struct { - GUID string `json:"guid"` - FromAddress string `json:"from"` - ToAddress string `json:"to"` - L1Token string `json:"l1Token"` - L2Token *Token `json:"l2Token"` - Amount string `json:"amount"` - Data []byte `json:"data"` - LogIndex uint64 `json:"logIndex"` - BlockNumber uint64 `json:"blockNumber"` - BlockTimestamp string `json:"blockTimestamp"` - TxHash string `json:"transactionHash"` - Batch *StateBatchJSON `json:"batch"` +// Close closes the database. +// NOTE: "It is rarely necessary to close a DB." +// See: https://pkg.go.dev/database/sql#Open +func (d *Database) Close() error { + return d.db.Close() } -type Database struct { - db *sql.DB - config string +// Config returns the db connection string. +func (d *Database) Config() string { + return d.config } +// GetL1TokenByAddress returns the ERC20 Token corresponding to the given +// address on L1. func (d *Database) GetL1TokenByAddress(address string) (*Token, error) { const selectL1TokenStatement = ` SELECT name, symbol, decimals FROM l1_tokens WHERE address = $1; @@ -348,6 +79,8 @@ func (d *Database) GetL1TokenByAddress(address string) (*Token, error) { return token, nil } +// GetL2TokenByAddress returns the ERC20 Token corresponding to the given +// address on L2. func (d *Database) GetL2TokenByAddress(address string) (*Token, error) { const selectL2TokenStatement = ` SELECT name, symbol, decimals FROM l2_tokens WHERE address = $1; @@ -396,6 +129,9 @@ func (d *Database) GetL2TokenByAddress(address string) (*Token, error) { return token, nil } +// AddL1Token inserts the Token details for the given address into the known L1 +// tokens database. +// NOTE: a Token MUST have a unique address func (d *Database) AddL1Token(address string, token *Token) error { const insertTokenStatement = ` INSERT INTO l1_tokens @@ -424,6 +160,9 @@ func (d *Database) AddL1Token(address string, token *Token) error { }) } +// AddL2Token inserts the Token details for the given address into the known L2 +// tokens database. +// NOTE: a Token MUST have a unique address func (d *Database) AddL2Token(address string, token *Token) error { const insertTokenStatement = ` INSERT INTO l2_tokens @@ -452,6 +191,9 @@ func (d *Database) AddL2Token(address string, token *Token) error { }) } +// AddIndexedL1Block inserts the indexed block i.e. the L1 block containing all +// scanned Deposits into the known deposits database. +// NOTE: the block hash MUST be unique func (d *Database) AddIndexedL1Block(block *IndexedL1Block) error { const insertBlockStatement = ` INSERT INTO l1_blocks @@ -513,6 +255,9 @@ func (d *Database) AddIndexedL1Block(block *IndexedL1Block) error { }) } +// AddIndexedL2Block inserts the indexed block i.e. the L2 block containing all +// scanned Withdrawals into the known withdrawals database. +// NOTE: the block hash MUST be unique func (d *Database) AddIndexedL2Block(block *IndexedL2Block) error { const insertBlockStatement = ` INSERT INTO l2_blocks @@ -574,6 +319,8 @@ func (d *Database) AddIndexedL2Block(block *IndexedL2Block) error { }) } +// AddStateBatch inserts the state batches into the known state batches +// database. func (d *Database) AddStateBatch(batches []StateBatch) error { const insertStateBatchStatement = ` INSERT INTO state_batches @@ -606,7 +353,9 @@ func (d *Database) AddStateBatch(batches []StateBatch) error { }) } -func (d *Database) GetDepositsByAddress(address common.Address, page PaginationParam) ([]DepositJSON, error) { +// GetDepositsByAddress returns the list of Deposits indexed for the given +// address paginated by the given params. +func (d *Database) GetDepositsByAddress(address common.Address, page PaginationParam) (*PaginatedDeposits, error) { const selectDepositsStatement = ` SELECT deposits.guid, deposits.from_address, deposits.to_address, @@ -634,17 +383,17 @@ func (d *Database) GetDepositsByAddress(address common.Address, page PaginationP for rows.Next() { var deposit DepositJSON - var l1_token Token + var l1Token Token if err := rows.Scan( &deposit.GUID, &deposit.FromAddress, &deposit.ToAddress, &deposit.Amount, &deposit.TxHash, &deposit.Data, - &l1_token.Address, &deposit.L2Token, - &l1_token.Name, &l1_token.Symbol, &l1_token.Decimals, + &l1Token.Address, &deposit.L2Token, + &l1Token.Name, &l1Token.Symbol, &l1Token.Decimals, &deposit.BlockNumber, &deposit.BlockTimestamp, ); err != nil { return err } - deposit.L1Token = &l1_token + deposit.L1Token = &l1Token deposits = append(deposits, deposit) } @@ -654,9 +403,43 @@ func (d *Database) GetDepositsByAddress(address common.Address, page PaginationP if err != nil { return nil, err } - return deposits, nil + + const selectDepositCountStatement = ` + SELECT + count(*) + FROM deposits + INNER JOIN l1_blocks ON deposits.block_hash=l1_blocks.hash + INNER JOIN l1_tokens ON deposits.l1_token=l1_tokens.address + WHERE deposits.from_address = $1; + ` + + var count uint64 + + err = txn(d.db, func(tx *sql.Tx) error { + queryStmt, err := tx.Prepare(selectDepositCountStatement) + if err != nil { + return err + } + + row := queryStmt.QueryRow(address.String()) + if err != nil { + return err + } + + row.Scan(&count) + return nil + }) + + page.Total = count + + return &PaginatedDeposits{ + &page, + deposits, + }, nil } +// GetWithdrawalBatch returns the StateBatch corresponding to the given +// withdrawal transaction hash. func (d *Database) GetWithdrawalBatch(hash l2common.Hash) (*StateBatchJSON, error) { const selectWithdrawalBatchStatement = ` SELECT @@ -685,11 +468,11 @@ func (d *Database) GetWithdrawalBatch(hash l2common.Hash) (*StateBatchJSON, erro return row.Err() } - var index, size, prev_total, block_number, block_timestamp uint64 - var root, block_hash string - var extra_data []byte - err = row.Scan(&index, &root, &size, &prev_total, &extra_data, &block_hash, - &block_number, &block_timestamp) + var index, size, prevTotal, blockNumber, blockTimestamp uint64 + var root, blockHash string + var extraData []byte + err = row.Scan(&index, &root, &size, &prevTotal, &extraData, &blockHash, + &blockNumber, &blockTimestamp) if err != nil { if errors.Is(err, sql.ErrNoRows) { batch = nil @@ -702,11 +485,11 @@ func (d *Database) GetWithdrawalBatch(hash l2common.Hash) (*StateBatchJSON, erro Index: index, Root: root, Size: size, - PrevTotal: prev_total, - ExtraData: extra_data, - BlockHash: block_hash, - BlockNumber: block_number, - BlockTimestamp: block_timestamp, + PrevTotal: prevTotal, + ExtraData: extraData, + BlockHash: blockHash, + BlockNumber: blockNumber, + BlockTimestamp: blockTimestamp, } return nil @@ -718,7 +501,9 @@ func (d *Database) GetWithdrawalBatch(hash l2common.Hash) (*StateBatchJSON, erro return batch, nil } -func (d *Database) GetWithdrawalsByAddress(address l2common.Address, page PaginationParam) ([]WithdrawalJSON, error) { +// GetWithdrawalsByAddress returns the list of Withdrawals indexed for the given +// address paginated by the given params. +func (d *Database) GetWithdrawalsByAddress(address l2common.Address, page PaginationParam) (*PaginatedWithdrawals, error) { const selectWithdrawalsStatement = ` SELECT withdrawals.guid, withdrawals.from_address, withdrawals.to_address, @@ -746,17 +531,17 @@ func (d *Database) GetWithdrawalsByAddress(address l2common.Address, page Pagina for rows.Next() { var withdrawal WithdrawalJSON - var l2_token Token + var l2Token Token if err := rows.Scan( &withdrawal.GUID, &withdrawal.FromAddress, &withdrawal.ToAddress, &withdrawal.Amount, &withdrawal.TxHash, &withdrawal.Data, - &withdrawal.L1Token, &l2_token.Address, - &l2_token.Name, &l2_token.Symbol, &l2_token.Decimals, + &withdrawal.L1Token, &l2Token.Address, + &l2Token.Name, &l2Token.Symbol, &l2Token.Decimals, &withdrawal.BlockNumber, &withdrawal.BlockTimestamp, ); err != nil { return err } - withdrawal.L2Token = &l2_token + withdrawal.L2Token = &l2Token withdrawals = append(withdrawals, withdrawal) } @@ -772,19 +557,41 @@ func (d *Database) GetWithdrawalsByAddress(address l2common.Address, page Pagina withdrawals[i].Batch = batch } - return withdrawals, nil -} + const selectWithdrawalCountStatement = ` + SELECT + count(*) + FROM withdrawals + INNER JOIN l2_blocks ON withdrawals.block_hash=l2_blocks.hash + INNER JOIN l2_tokens ON withdrawals.l2_token=l2_tokens.address + WHERE withdrawals.from_address = $1; + ` -type L1BlockLocator struct { - Number uint64 `json:"number"` - Hash common.Hash `json:"hash"` -} + var count uint64 + + err = txn(d.db, func(tx *sql.Tx) error { + queryStmt, err := tx.Prepare(selectWithdrawalCountStatement) + if err != nil { + return err + } -type L2BlockLocator struct { - Number uint64 `json:"number"` - Hash l2common.Hash `json:"hash"` + row := queryStmt.QueryRow(address.String()) + if err != nil { + return err + } + + row.Scan(&count) + return nil + }) + + page.Total = count + + return &PaginatedWithdrawals{ + &page, + withdrawals, + }, nil } +// GetHighestL1Block returns the highest known L1 block. func (d *Database) GetHighestL1Block() (*L1BlockLocator, error) { const selectHighestBlockStatement = ` SELECT number, hash FROM l1_blocks ORDER BY number DESC LIMIT 1 @@ -827,6 +634,7 @@ func (d *Database) GetHighestL1Block() (*L1BlockLocator, error) { return highestBlock, nil } +// GetHighestL2Block returns the highest known L2 block. func (d *Database) GetHighestL2Block() (*L2BlockLocator, error) { const selectHighestBlockStatement = ` SELECT number, hash FROM l2_blocks ORDER BY number DESC LIMIT 1 @@ -869,6 +677,7 @@ func (d *Database) GetHighestL2Block() (*L2BlockLocator, error) { return highestBlock, nil } +// GetIndexedL1BlockByHash returns the L1 block by it's hash. func (d *Database) GetIndexedL1BlockByHash(hash common.Hash) (*IndexedL1Block, error) { const selectBlockByHashStatement = ` SELECT @@ -916,81 +725,9 @@ func (d *Database) GetIndexedL1BlockByHash(hash common.Hash) (*IndexedL1Block, e } return block, nil - -} - -func (d *Database) GetEventsByBlockHash(hash common.Hash) ([]TxnEnqueuedEvent, error) { - const selectEventsByBlockHashStatement = ` - SELECT - b.number, b.timestamp, - d.tx_hash, d.data - FROM - blocks AS b, - deposits AS d - WHERE b.hash = d.block_hash AND b.hash = $1 - ` - - var events []TxnEnqueuedEvent - err := txn(d.db, func(tx *sql.Tx) error { - queryStmt, err := tx.Prepare(selectEventsByBlockHashStatement) - if err != nil { - return err - } - - rows, err := queryStmt.Query(hash.String()) - if err != nil { - return err - } - - for rows.Next() { - event, err := scanTxnEnqueuedEvent(rows) - if err != nil { - return err - } - - events = append(events, event) - } - - return nil - }) - if err != nil { - return nil, err - } - - return events, nil -} - -func scanTxnEnqueuedEvent(rows *sql.Rows) (TxnEnqueuedEvent, error) { - var number uint64 - var timestamp uint64 - var txHash string - var data []byte - err := rows.Scan( - &number, - ×tamp, - &txHash, - &data, - ) - if err != nil { - return TxnEnqueuedEvent{}, err - } - - return TxnEnqueuedEvent{ - BlockNumber: number, - Timestamp: timestamp, - TxHash: common.HexToHash(txHash), - Data: data, - }, nil -} - -func (d *Database) Close() error { - return d.db.Close() -} - -func (d *Database) Config() string { - return d.config } +// NewDatabase returns the database for the given connection string. func NewDatabase(config string) (*Database, error) { db, err := sql.Open("postgres", config) if err != nil { @@ -1014,29 +751,3 @@ func NewDatabase(config string) (*Database, error) { config: config, }, nil } - -func txn(db *sql.DB, apply func(*sql.Tx) error) error { - tx, err := db.Begin() - if err != nil { - return err - } - defer func() { - if p := recover(); p != nil { - tx.Rollback() - panic(p) - } - }() - - err = apply(tx) - if err != nil { - // Don't swallow application error - _ = tx.Rollback() - return err - } - - return tx.Commit() -} - -func NewGUID() string { - return uuid.New().String() -} diff --git a/go/indexer/db/deposit.go b/go/indexer/db/deposit.go new file mode 100644 index 0000000000000..d10b0d78aa48b --- /dev/null +++ b/go/indexer/db/deposit.go @@ -0,0 +1,40 @@ +package db + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// Deposit contains transaction data for deposits made via the L1 to L2 bridge. +type Deposit struct { + GUID string + TxHash common.Hash + L1Token common.Address + L2Token common.Address + FromAddress common.Address + ToAddress common.Address + Amount *big.Int + Data []byte + LogIndex uint +} + +// String returns the tx hash for the deposit. +func (d Deposit) String() string { + return d.TxHash.String() +} + +// DepositJSON contains Deposit data suitable for JSON serialization. +type DepositJSON struct { + GUID string `json:"guid"` + FromAddress string `json:"from"` + ToAddress string `json:"to"` + L1Token *Token `json:"l1Token"` + L2Token string `json:"l2Token"` + Amount string `json:"amount"` + Data []byte `json:"data"` + LogIndex uint64 `json:"logIndex"` + BlockNumber uint64 `json:"blockNumber"` + BlockTimestamp string `json:"blockTimestamp"` + TxHash string `json:"transactionHash"` +} diff --git a/go/indexer/db/eth.go b/go/indexer/db/eth.go new file mode 100644 index 0000000000000..d7254a6810de6 --- /dev/null +++ b/go/indexer/db/eth.go @@ -0,0 +1,25 @@ +package db + +import l2common "github.com/ethereum-optimism/optimism/l2geth/common" + +// ETHL1Token is a placeholder token for differentiating ETH transactions from +// ERC20 transactions on L1. +var ETHL1Token = &Token{ + Address: "0x0000000000000000000000000000000000000000", + Name: "Ethereum", + Symbol: "ETH", + Decimals: 18, +} + +// ETHL2Address is a placeholder address for differentiating ETH transactions +// from ERC20 transactions on L2. +var ETHL2Address = l2common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000") + +// ETHL2Token is a placeholder token for differentiating ETH transactions from +// ERC20 transactions on L2. +var ETHL2Token = &Token{ + Address: "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", + Name: "Ethereum", + Symbol: "ETH", + Decimals: 18, +} diff --git a/go/indexer/db/guid.go b/go/indexer/db/guid.go new file mode 100644 index 0000000000000..42032d4740ccf --- /dev/null +++ b/go/indexer/db/guid.go @@ -0,0 +1,8 @@ +package db + +import "github.com/google/uuid" + +// NewGUID returns a new guid. +func NewGUID() string { + return uuid.New().String() +} diff --git a/go/indexer/db/l1block.go b/go/indexer/db/l1block.go new file mode 100644 index 0000000000000..be9c84222c6d9 --- /dev/null +++ b/go/indexer/db/l1block.go @@ -0,0 +1,34 @@ +package db + +import ( + l2common "github.com/ethereum-optimism/optimism/l2geth/common" + "github.com/ethereum/go-ethereum/common" +) + +// IndexedL1Block contains the L1 block including the deposits in it. +type IndexedL1Block struct { + Hash common.Hash + ParentHash common.Hash + Number uint64 + Timestamp uint64 + Deposits []Deposit +} + +// String returns the block hash for the indexed l1 block. +func (b IndexedL1Block) String() string { + return b.Hash.String() +} + +// IndexedL2Block contains the L2 block including the withdrawals in it. +type IndexedL2Block struct { + Hash l2common.Hash + ParentHash l2common.Hash + Number uint64 + Timestamp uint64 + Withdrawals []Withdrawal +} + +// String returns the block hash for the indexed l2 block. +func (b IndexedL2Block) String() string { + return b.Hash.String() +} diff --git a/go/indexer/db/locator.go b/go/indexer/db/locator.go new file mode 100644 index 0000000000000..f2e4da100831e --- /dev/null +++ b/go/indexer/db/locator.go @@ -0,0 +1,18 @@ +package db + +import ( + l2common "github.com/ethereum-optimism/optimism/l2geth/common" + "github.com/ethereum/go-ethereum/common" +) + +// L1BlockLocator contains the block locator for a L1 block. +type L1BlockLocator struct { + Number uint64 `json:"number"` + Hash common.Hash `json:"hash"` +} + +// L2BlockLocator contains the block locator for a L2 block. +type L2BlockLocator struct { + Number uint64 `json:"number"` + Hash l2common.Hash `json:"hash"` +} diff --git a/go/indexer/db/pagination.go b/go/indexer/db/pagination.go new file mode 100644 index 0000000000000..d29d2a119aae7 --- /dev/null +++ b/go/indexer/db/pagination.go @@ -0,0 +1,20 @@ +package db + +// PaginationParam holds the pagination fields passed through by the REST +// middleware and queried by the database to page through deposits and +// withdrawals. +type PaginationParam struct { + Limit uint64 `json:"limit"` + Offset uint64 `json:"offset"` + Total uint64 `json:"total"` +} + +type PaginatedDeposits struct { + Param *PaginationParam `json:"pagination"` + Deposits []DepositJSON `json:"items"` +} + +type PaginatedWithdrawals struct { + Param *PaginationParam `json:"pagination"` + Withdrawals []WithdrawalJSON `json:"items"` +} diff --git a/go/indexer/db/sql.go b/go/indexer/db/sql.go new file mode 100644 index 0000000000000..eea84fdbaeaaf --- /dev/null +++ b/go/indexer/db/sql.go @@ -0,0 +1,121 @@ +package db + +const createL1BlocksTable = ` +CREATE TABLE IF NOT EXISTS l1_blocks ( + hash VARCHAR NOT NULL PRIMARY KEY, + parent_hash VARCHAR NOT NULL, + number INTEGER NOT NULL, + timestamp INTEGER NOT NULL +) +` + +const createL2BlocksTable = ` +CREATE TABLE IF NOT EXISTS l2_blocks ( + hash VARCHAR NOT NULL PRIMARY KEY, + parent_hash VARCHAR NOT NULL, + number INTEGER NOT NULL, + timestamp INTEGER NOT NULL +) +` + +const createDepositsTable = ` +CREATE TABLE IF NOT EXISTS deposits ( + guid VARCHAR PRIMARY KEY NOT NULL, + from_address VARCHAR NOT NULL, + to_address VARCHAR NOT NULL, + l1_token VARCHAR NOT NULL REFERENCES l1_tokens(address), + l2_token VARCHAR NOT NULL, + amount VARCHAR NOT NULL, + data BYTEA NOT NULL, + log_index INTEGER NOT NULL, + block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash), + tx_hash VARCHAR NOT NULL +) +` + +const createL1TokensTable = ` +CREATE TABLE IF NOT EXISTS l1_tokens ( + address VARCHAR NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + symbol VARCHAR NOT NULL, + decimals INTEGER NOT NULL +) +` + +const createL2TokensTable = ` +CREATE TABLE IF NOT EXISTS l2_tokens ( + address TEXT NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + symbol TEXT NOT NULL, + decimals INTEGER NOT NULL +) +` + +const createStateBatchesTable = ` +CREATE TABLE IF NOT EXISTS state_batches ( + index INTEGER NOT NULL PRIMARY KEY, + root VARCHAR NOT NULL, + size INTEGER NOT NULL, + prev_total INTEGER NOT NULL, + extra_data BYTEA NOT NULL, + block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash) +); +CREATE INDEX IF NOT EXISTS state_batches_block_hash ON state_batches(block_hash); +CREATE INDEX IF NOT EXISTS state_batches_size ON state_batches(size); +CREATE INDEX IF NOT EXISTS state_batches_prev_total ON state_batches(prev_total); +` + +const createWithdrawalsTable = ` +CREATE TABLE IF NOT EXISTS withdrawals ( + guid VARCHAR PRIMARY KEY NOT NULL, + from_address VARCHAR NOT NULL, + to_address VARCHAR NOT NULL, + l1_token VARCHAR NOT NULL, + l2_token VARCHAR NOT NULL REFERENCES l2_tokens(address), + amount VARCHAR NOT NULL, + data BYTEA NOT NULL, + log_index INTEGER NOT NULL, + block_hash VARCHAR NOT NULL REFERENCES l2_blocks(hash), + tx_hash VARCHAR NOT NULL, + state_batch INTEGER REFERENCES state_batches(index) +) +` + +const insertETHL1Token = ` +INSERT INTO l1_tokens + (address, name, symbol, decimals) +VALUES ('0x0000000000000000000000000000000000000000', 'Ethereum', 'ETH', 18) +ON CONFLICT (address) DO NOTHING; +` + +// earlier transactions used 0x0000000000000000000000000000000000000000 as +// address of ETH so insert both that and +// 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 +const insertETHL2Token = ` +INSERT INTO l2_tokens + (address, name, symbol, decimals) +VALUES ('0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000', 'Ethereum', 'ETH', 18) +ON CONFLICT (address) DO NOTHING; +INSERT INTO l2_tokens + (address, name, symbol, decimals) +VALUES ('0x0000000000000000000000000000000000000000', 'Ethereum', 'ETH', 18) +ON CONFLICT (address) DO NOTHING; +` + +const createL1L2NumberIndex = ` +CREATE UNIQUE INDEX IF NOT EXISTS l1_blocks_number ON l1_blocks(number); +CREATE UNIQUE INDEX IF NOT EXISTS l2_blocks_number ON l2_blocks(number); +` + +var schema = []string{ + createL1BlocksTable, + createL2BlocksTable, + createL1TokensTable, + createL2TokensTable, + createStateBatchesTable, + insertETHL1Token, + insertETHL2Token, + createDepositsTable, + createWithdrawalsTable, + createL1L2NumberIndex, +} diff --git a/go/indexer/db/state_batch.go b/go/indexer/db/state_batch.go new file mode 100644 index 0000000000000..c8d820a4a1c2a --- /dev/null +++ b/go/indexer/db/state_batch.go @@ -0,0 +1,30 @@ +package db + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// StateBatch is the state batch containing merkle root of the withdrawals +// periodically written to L1. +type StateBatch struct { + Index *big.Int + Root common.Hash + Size *big.Int + PrevTotal *big.Int + ExtraData []byte + BlockHash common.Hash +} + +// StateBatchJSON contains StateBatch data suitable for JSON serialization. +type StateBatchJSON struct { + Index uint64 `json:"index"` + Root string `json:"root"` + Size uint64 `json:"size"` + PrevTotal uint64 `json:"prevTotal"` + ExtraData []byte `json:"extraData"` + BlockHash string `json:"blockHash"` + BlockNumber uint64 `json:"blockNumber"` + BlockTimestamp uint64 `json:"blockTimestamp"` +} diff --git a/go/indexer/db/token.go b/go/indexer/db/token.go new file mode 100644 index 0000000000000..59bb80656c028 --- /dev/null +++ b/go/indexer/db/token.go @@ -0,0 +1,11 @@ +package db + +// Token contains the token details of the ERC20 contract at the given address. +// NOTE: The Token address will almost definitely be different on L1 and L2, so +// we need to track it on both chains when handling transactions. +type Token struct { + Address string `json:"address"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Decimals uint8 `json:"decimals"` +} diff --git a/go/indexer/db/txn.go b/go/indexer/db/txn.go new file mode 100644 index 0000000000000..40215506b0bbf --- /dev/null +++ b/go/indexer/db/txn.go @@ -0,0 +1,26 @@ +package db + +import "database/sql" + +func txn(db *sql.DB, apply func(*sql.Tx) error) error { + tx, err := db.Begin() + if err != nil { + return err + } + defer func() { + if p := recover(); p != nil { + // Ignore since we're panicking anyway + _ = tx.Rollback() + panic(p) + } + }() + + err = apply(tx) + if err != nil { + // Don't swallow application error + _ = tx.Rollback() + return err + } + + return tx.Commit() +} diff --git a/go/indexer/db/withdrawal.go b/go/indexer/db/withdrawal.go new file mode 100644 index 0000000000000..0b98a462a746e --- /dev/null +++ b/go/indexer/db/withdrawal.go @@ -0,0 +1,41 @@ +package db + +import ( + "math/big" + + l2common "github.com/ethereum-optimism/optimism/l2geth/common" +) + +// Withdrawal contains transaction data for withdrawals made via the L2 to L1 bridge. +type Withdrawal struct { + GUID string + TxHash l2common.Hash + L1Token l2common.Address + L2Token l2common.Address + FromAddress l2common.Address + ToAddress l2common.Address + Amount *big.Int + Data []byte + LogIndex uint +} + +// String returns the tx hash for the withdrawal. +func (w Withdrawal) String() string { + return w.TxHash.String() +} + +// WithdrawalJSON contains Withdrawal data suitable for JSON serialization. +type WithdrawalJSON struct { + GUID string `json:"guid"` + FromAddress string `json:"from"` + ToAddress string `json:"to"` + L1Token string `json:"l1Token"` + L2Token *Token `json:"l2Token"` + Amount string `json:"amount"` + Data []byte `json:"data"` + LogIndex uint64 `json:"logIndex"` + BlockNumber uint64 `json:"blockNumber"` + BlockTimestamp string `json:"blockTimestamp"` + TxHash string `json:"transactionHash"` + Batch *StateBatchJSON `json:"batch"` +} diff --git a/go/indexer/flags/flags.go b/go/indexer/flags/flags.go index 3a62c54f3e48c..fc8feacb8de61 100644 --- a/go/indexer/flags/flags.go +++ b/go/indexer/flags/flags.go @@ -29,28 +29,28 @@ var ( EnvVar: prefixEnvVar("ETH_NETWORK_NAME"), } ChainIDFlag = cli.StringFlag{ - Name: "chain-id", - Usage: "Ethereum chain ID", + Name: "chain-id", + Usage: "Ethereum chain ID", Required: true, - EnvVar: prefixEnvVar("CHAIN_ID"), + EnvVar: prefixEnvVar("CHAIN_ID"), } - L1EthRpcFlag = cli.StringFlag{ + L1EthRPCFlag = cli.StringFlag{ Name: "l1-eth-rpc", Usage: "HTTP provider URL for L1", Required: true, EnvVar: prefixEnvVar("L1_ETH_RPC"), } - L2EthRpcFlag = cli.StringFlag{ + L2EthRPCFlag = cli.StringFlag{ Name: "l2-eth-rpc", Usage: "HTTP provider URL for L2", Required: true, EnvVar: prefixEnvVar("L2_ETH_RPC"), } L1AddressManagerAddressFlag = cli.StringFlag{ - Name: "l1-address-manager-address", - Usage: "Address of the L1 address manager", + Name: "l1-address-manager-address", + Usage: "Address of the L1 address manager", Required: true, - EnvVar: prefixEnvVar("L1_ADDRESS_MANAGER_ADDRESS"), + EnvVar: prefixEnvVar("L1_ADDRESS_MANAGER_ADDRESS"), } L2GenesisBlockHashFlag = cli.StringFlag{ Name: "l2-genesis-block-hash", @@ -150,6 +150,18 @@ var ( Value: 2000, EnvVar: prefixEnvVar("MAX_HEADER_BATCH_SIZE"), } + RESTHostnameFlag = cli.StringFlag{ + Name: "rest-hostname", + Usage: "The hostname of the REST server", + Value: "127.0.0.1", + EnvVar: prefixEnvVar("REST_HOSTNAME"), + } + RESTPortFlag = cli.Uint64Flag{ + Name: "rest-port", + Usage: "The port of the REST server", + Value: 8080, + EnvVar: prefixEnvVar("REST_PORT"), + } MetricsServerEnableFlag = cli.BoolFlag{ Name: "metrics-server-enable", Usage: "Whether or not to run the embedded metrics server", @@ -173,8 +185,8 @@ var requiredFlags = []cli.Flag{ BuildEnvFlag, EthNetworkNameFlag, ChainIDFlag, - L1EthRpcFlag, - L2EthRpcFlag, + L1EthRPCFlag, + L2EthRPCFlag, L1AddressManagerAddressFlag, L2GenesisBlockHashFlag, DBHostFlag, @@ -195,6 +207,8 @@ var optionalFlags = []cli.Flag{ MaxHeaderBatchSizeFlag, StartBlockNumberFlag, StartBlockHashFlag, + RESTHostnameFlag, + RESTPortFlag, MetricsServerEnableFlag, MetricsHostnameFlag, MetricsPortFlag, diff --git a/go/indexer/go.mod b/go/indexer/go.mod index 64e50c41a695c..21f7298e621bf 100644 --- a/go/indexer/go.mod +++ b/go/indexer/go.mod @@ -6,6 +6,7 @@ require ( github.com/ethereum-optimism/optimism/l2geth v0.0.0-20220104205740-f39387287484 github.com/ethereum/go-ethereum v1.10.14 github.com/getsentry/sentry-go v0.12.0 + github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/lib/pq v1.0.0 github.com/prometheus/client_golang v1.0.0 @@ -31,7 +32,6 @@ require ( github.com/go-stack/stack v1.8.0 // indirect github.com/golang/protobuf v1.4.3 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/huin/goupnp v1.0.2 // indirect diff --git a/go/indexer/go.sum b/go/indexer/go.sum index 0a16c23144ce2..7b2d77e8eef75 100644 --- a/go/indexer/go.sum +++ b/go/indexer/go.sum @@ -238,7 +238,6 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/go/indexer/indexer.go b/go/indexer/indexer.go index 1deb63f969112..c863a0284bf63 100644 --- a/go/indexer/indexer.go +++ b/go/indexer/indexer.go @@ -6,6 +6,7 @@ import ( "math/big" "net/http" "os" + "strconv" "time" "github.com/ethereum-optimism/optimism/go/indexer/metrics" @@ -19,7 +20,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" - "github.com/getsentry/sentry-go" + sentry "github.com/getsentry/sentry-go" "github.com/gorilla/mux" "github.com/urfave/cli" ) @@ -201,7 +202,8 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { }, nil } -func (b *Indexer) Serve(ctx context.Context) { +// Serve spins up a REST API server at the given hostname and port. +func (b *Indexer) Serve() error { c := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, }) @@ -213,25 +215,41 @@ func (b *Indexer) Serve(ctx context.Context) { b.router.HandleFunc("/v1/withdrawals/0x{address:[a-fA-F0-9]{40}}", b.l2IndexingService.GetWithdrawals).Methods("GET") b.router.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) - w.Write([]byte("OK")) + _, err := w.Write([]byte("OK")) + if err != nil { + log.Error("Error handling /healthz", "error", err) + } }) middleware := server.LoggingMiddleware(log.New("service", "server")) - http.ListenAndServe(":8080", middleware(c.Handler(b.router))) + + port := strconv.FormatUint(b.cfg.RESTPort, 10) + addr := fmt.Sprintf("%s:%s", b.cfg.RESTHostname, port) + + log.Info("indexer REST server listening on", "addr", addr) + return http.ListenAndServe(addr, middleware(c.Handler(b.router))) } +// Start starts the starts the indexing service on L1 and L2 chains and also +// starts the REST server. func (b *Indexer) Start() error { if b.cfg.DisableIndexer { log.Info("indexer disabled, only serving data") } else { - b.l1IndexingService.Start() - b.l2IndexingService.Start() + err := b.l1IndexingService.Start() + if err != nil { + return err + } + err = b.l2IndexingService.Start() + if err != nil { + return err + } } - b.Serve(b.ctx) - return nil + return b.Serve() } +// Stop stops the indexing service on L1 and L2 chains. func (b *Indexer) Stop() { if !b.cfg.DisableIndexer { b.l1IndexingService.Stop() @@ -277,7 +295,3 @@ func traceRateToFloat64(rate time.Duration) float64 { } return rate64 } - -func gasPriceFromGwei(gasPriceInGwei uint64) *big.Int { - return new(big.Int).SetUint64(gasPriceInGwei * 1e9) -} diff --git a/go/indexer/server/server.go b/go/indexer/server/server.go index 63ef31cefb7b4..c5f3871b5c807 100644 --- a/go/indexer/server/server.go +++ b/go/indexer/server/server.go @@ -2,22 +2,25 @@ package server import ( "encoding/json" - "github.com/ethereum/go-ethereum/log" "net/http" "runtime/debug" "time" + + "github.com/ethereum/go-ethereum/log" ) +// RespondWithError writes the given error code and message to the writer. func RespondWithError(w http.ResponseWriter, code int, message string) { RespondWithJSON(w, code, map[string]string{"error": message}) } +// RespondWithJSON writes the given payload marshalled as JSON to the writer. func RespondWithJSON(w http.ResponseWriter, code int, payload interface{}) { response, _ := json.Marshal(payload) w.WriteHeader(code) w.Header().Set("Content-Type", "application/json") - w.Write(response) + _, _ = w.Write(response) } // responseWriter is a minimal wrapper for http.ResponseWriter that allows the @@ -44,8 +47,6 @@ func (rw *responseWriter) WriteHeader(code int) { rw.status = code rw.ResponseWriter.WriteHeader(code) rw.wroteHeader = true - - return } // LoggingMiddleware logs the incoming HTTP request & its duration. diff --git a/go/indexer/services/l1/bridge/filter.go b/go/indexer/services/l1/bridge/filter.go index 6b109e5942d38..f9b6f34555d39 100644 --- a/go/indexer/services/l1/bridge/filter.go +++ b/go/indexer/services/l1/bridge/filter.go @@ -26,7 +26,6 @@ func FilterStateBatchAppendedWithRetry(filterer *scc.StateCommitmentChainFiltere return res, err default: logger.Error("Error fetching filter", "err", err) - break } time.Sleep(clientRetryInterval) } @@ -45,7 +44,6 @@ func FilterETHDepositInitiatedWithRetry(filterer *l1bridge.L1StandardBridgeFilte return res, err default: logger.Error("Error fetching filter", "err", err) - break } time.Sleep(clientRetryInterval) } @@ -64,7 +62,6 @@ func FilterERC20DepositInitiatedWithRetry(filterer *l1bridge.L1StandardBridgeFil return res, err default: logger.Error("Error fetching filter", "err", err) - break } time.Sleep(clientRetryInterval) } diff --git a/go/indexer/services/l1/confirmed_headers.go b/go/indexer/services/l1/confirmed_headers.go index 62f58592aa8ff..eb518f1f67a20 100644 --- a/go/indexer/services/l1/confirmed_headers.go +++ b/go/indexer/services/l1/confirmed_headers.go @@ -145,7 +145,7 @@ func HeaderByNumber(ctx context.Context, client *rpc.Client, height *big.Int) (* if err == nil && head == nil { err = ethereum.NotFound } - return head, nil + return head, err } func (f *ConfirmedHeaderSelector) NewHead( diff --git a/go/indexer/services/l1/service.go b/go/indexer/services/l1/service.go index feeaa9fd7f727..76d19d6caac9d 100644 --- a/go/indexer/services/l1/service.go +++ b/go/indexer/services/l1/service.go @@ -35,10 +35,6 @@ var logger = log.New("service", "l1") // and it cannot be remotely fetched var errNoChainID = errors.New("no chain id provided") -// errWrongChainID represents the error when the configured chain id is not -// correct -var errWrongChainID = errors.New("wrong chain id provided") - var errNoNewBlocks = errors.New("no new blocks") // clientRetryInterval is the interval to wait between retrying client API @@ -58,7 +54,6 @@ func HeaderByNumberWithRetry(ctx context.Context, return res, err default: log.Error("Error fetching header", "err", err) - break } time.Sleep(clientRetryInterval) } @@ -194,10 +189,12 @@ func (s *Service) Loop(ctx context.Context) { atomic.StoreUint64(&s.latestHeader, header.Number.Uint64()) for { err := s.Update(header) - if err != nil && err != errNoNewBlocks { - logger.Error("Unable to update indexer ", "err", err) + if err != nil { + if err != errNoNewBlocks { + logger.Error("Unable to update indexer ", "err", err) + } + break } - break } case <-s.ctx.Done(): return @@ -509,11 +506,11 @@ func (s *Service) Start() error { return nil } -func (s *Service) Stop() error { +func (s *Service) Stop() { s.cancel() s.wg.Wait() - if err := s.cfg.DB.Close(); err != nil { - return err + err := s.cfg.DB.Close() + if err != nil { + logger.Error("Error closing db", "err", err) } - return nil } diff --git a/go/indexer/services/l2/bridge/filter.go b/go/indexer/services/l2/bridge/filter.go index d443a3bf10fc3..50d1b7f9da266 100644 --- a/go/indexer/services/l2/bridge/filter.go +++ b/go/indexer/services/l2/bridge/filter.go @@ -25,7 +25,6 @@ func FilterWithdrawalInitiatedWithRetry(filterer *l2bridge.L2StandardBridgeFilte return res, err default: logger.Error("Error fetching filter", "err", err) - break } time.Sleep(clientRetryInterval) } diff --git a/go/indexer/services/l2/service.go b/go/indexer/services/l2/service.go index 905b803faf330..54896f96ffdae 100644 --- a/go/indexer/services/l2/service.go +++ b/go/indexer/services/l2/service.go @@ -2,7 +2,6 @@ package l2 import ( "context" - "encoding/json" "errors" "fmt" "math/big" @@ -12,6 +11,7 @@ import ( "time" "github.com/ethereum-optimism/optimism/go/indexer/metrics" + "github.com/ethereum-optimism/optimism/go/indexer/server" "github.com/prometheus/client_golang/prometheus" "github.com/ethereum-optimism/optimism/go/indexer/db" @@ -50,24 +50,11 @@ func HeaderByNumberWithRetry(ctx context.Context, return res, err default: log.Error("Error fetching header", "err", err) - break } time.Sleep(clientRetryInterval) } } -func respondWithError(w http.ResponseWriter, code int, message string) { - respondWithJSON(w, code, map[string]string{"error": message}) -} - -func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { - response, _ := json.Marshal(payload) - - w.WriteHeader(code) - w.Header().Set("Content-Type", "application/json") - w.Write(response) -} - type ServiceConfig struct { Context context.Context Metrics *metrics.Metrics @@ -177,10 +164,12 @@ func (s *Service) Loop(ctx context.Context) { logger.Info("Received new header", "header", header.Hash) for { err := s.Update(header) - if err != nil && err != errNoNewBlocks { - logger.Error("Unable to update indexer ", "err", err) + if err != nil { + if err != errNoNewBlocks { + logger.Error("Unable to update indexer ", "err", err) + } + break } - break } case <-s.ctx.Done(): return @@ -323,7 +312,7 @@ func (s *Service) Update(newHeader *types.Header) error { func (s *Service) GetIndexerStatus(w http.ResponseWriter, r *http.Request) { highestBlock, err := s.cfg.DB.GetHighestL2Block() if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) + server.RespondWithError(w, http.StatusInternalServerError, err.Error()) return } @@ -337,7 +326,7 @@ func (s *Service) GetIndexerStatus(w http.ResponseWriter, r *http.Request) { Highest: *highestBlock, } - respondWithJSON(w, http.StatusOK, status) + server.RespondWithJSON(w, http.StatusOK, status) } func (s *Service) GetWithdrawalBatch(w http.ResponseWriter, r *http.Request) { @@ -345,11 +334,11 @@ func (s *Service) GetWithdrawalBatch(w http.ResponseWriter, r *http.Request) { batch, err := s.cfg.DB.GetWithdrawalBatch(common.HexToHash(vars["hash"])) if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) + server.RespondWithError(w, http.StatusInternalServerError, err.Error()) return } - respondWithJSON(w, http.StatusOK, batch) + server.RespondWithJSON(w, http.StatusOK, batch) } func (s *Service) GetWithdrawals(w http.ResponseWriter, r *http.Request) { @@ -358,7 +347,7 @@ func (s *Service) GetWithdrawals(w http.ResponseWriter, r *http.Request) { limitStr := r.URL.Query().Get("limit") limit, err := strconv.ParseUint(limitStr, 10, 64) if err != nil && limitStr != "" { - respondWithError(w, http.StatusInternalServerError, err.Error()) + server.RespondWithError(w, http.StatusInternalServerError, err.Error()) return } if limit == 0 { @@ -368,7 +357,7 @@ func (s *Service) GetWithdrawals(w http.ResponseWriter, r *http.Request) { offsetStr := r.URL.Query().Get("offset") offset, err := strconv.ParseUint(offsetStr, 10, 64) if err != nil && offsetStr != "" { - respondWithError(w, http.StatusInternalServerError, err.Error()) + server.RespondWithError(w, http.StatusInternalServerError, err.Error()) return } @@ -379,11 +368,11 @@ func (s *Service) GetWithdrawals(w http.ResponseWriter, r *http.Request) { withdrawals, err := s.cfg.DB.GetWithdrawalsByAddress(common.HexToAddress(vars["address"]), page) if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) + server.RespondWithError(w, http.StatusInternalServerError, err.Error()) return } - respondWithJSON(w, http.StatusOK, withdrawals) + server.RespondWithJSON(w, http.StatusOK, withdrawals) } func (s *Service) subscribeNewHeads(ctx context.Context, heads chan *types.Header) { @@ -486,11 +475,11 @@ func (s *Service) Start() error { return nil } -func (s *Service) Stop() error { +func (s *Service) Stop() { s.cancel() s.wg.Wait() - if err := s.cfg.DB.Close(); err != nil { - return err + err := s.cfg.DB.Close() + if err != nil { + logger.Error("Error closing db", "err", err) } - return nil }