diff --git a/indexer/db/db.go b/indexer/db/db.go index fcfa060146a38..cdb4bbf0ace35 100644 --- a/indexer/db/db.go +++ b/indexer/db/db.go @@ -193,20 +193,10 @@ func (d *Database) AddIndexedL1Block(block *IndexedL1Block) error { const insertDepositStatement = ` INSERT INTO deposits - (guid, from_address, to_address, l1_token, l2_token, amount, tx_hash, log_index, l1_block_hash, data) + (guid, from_address, to_address, l1_token, l2_token, amount, tx_hash, log_index, block_hash, data) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ` - - const insertWithdrawalStatement = ` - INSERT INTO withdrawals - (guid, from_address, to_address, l1_token, l2_token, amount, tx_hash, log_index, l1_block_hash, data) - VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) - ON CONFLICT (tx_hash) - DO UPDATE SET l1_block_hash = $9; - ` - return txn(d.db, func(tx *sql.Tx) error { _, err := tx.Exec( insertBlockStatement, @@ -242,29 +232,6 @@ func (d *Database) AddIndexedL1Block(block *IndexedL1Block) error { } } - if len(block.Withdrawals) == 0 { - return nil - } - - for _, withdrawal := range block.Withdrawals { - _, err = tx.Exec( - insertWithdrawalStatement, - NewGUID(), - withdrawal.FromAddress.String(), - withdrawal.ToAddress.String(), - withdrawal.L1Token.String(), - withdrawal.L2Token.String(), - withdrawal.Amount.String(), - withdrawal.TxHash.String(), - withdrawal.LogIndex, - block.Hash.String(), - withdrawal.Data, - ) - if err != nil { - return err - } - } - return nil }) } @@ -282,9 +249,9 @@ func (d *Database) AddIndexedL2Block(block *IndexedL2Block) error { const insertWithdrawalStatement = ` INSERT INTO withdrawals - (guid, from_address, to_address, l1_token, l2_token, amount, tx_hash, log_index, l2_block_hash, data) + (guid, from_address, to_address, l1_token, l2_token, amount, tx_hash, log_index, block_hash, data, br_withdrawal_hash) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ` return txn(d.db, func(tx *sql.Tx) error { _, err := tx.Exec( @@ -315,6 +282,37 @@ func (d *Database) AddIndexedL2Block(block *IndexedL2Block) error { withdrawal.LogIndex, block.Hash.String(), withdrawal.Data, + nullableHash(withdrawal.BedrockHash), + ) + if err != nil { + return err + } + } + + return nil + }) +} + +// AddStateBatch inserts the state batches into the known state batches +// database. +func (d *Database) AddStateBatch(batches []StateBatch) error { + const insertStateBatchStatement = ` + INSERT INTO state_batches + (index, root, size, prev_total, extra_data, block_hash) + VALUES + ($1, $2, $3, $4, $5, $6) + ` + + return txn(d.db, func(tx *sql.Tx) error { + for _, sb := range batches { + _, err := tx.Exec( + insertStateBatchStatement, + sb.Index.Uint64(), + sb.Root.String(), + sb.Size.Uint64(), + sb.PrevTotal.Uint64(), + sb.ExtraData, + sb.BlockHash.String(), ) if err != nil { return err @@ -336,7 +334,7 @@ func (d *Database) GetDepositsByAddress(address common.Address, page PaginationP l1_tokens.name, l1_tokens.symbol, l1_tokens.decimals, l1_blocks.number, l1_blocks.timestamp FROM deposits - INNER JOIN l1_blocks ON deposits.l1_block_hash=l1_blocks.hash + 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 ORDER BY l1_blocks.timestamp LIMIT $2 OFFSET $3; ` @@ -375,7 +373,7 @@ func (d *Database) GetDepositsByAddress(address common.Address, page PaginationP SELECT count(*) FROM deposits - INNER JOIN l1_blocks ON deposits.l1_block_hash=l1_blocks.hash + 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; ` @@ -401,43 +399,54 @@ func (d *Database) GetDepositsByAddress(address common.Address, page PaginationP }, nil } -// GetWithdrawalStatus returns the finalization status corresponding to the -// given withdrawal transaction hash. -func (d *Database) GetWithdrawalStatus(hash common.Hash) (*WithdrawalJSON, error) { - const selectWithdrawalStatement = ` +// GetWithdrawalBatch returns the StateBatch corresponding to the given +// withdrawal transaction hash. +func (d *Database) GetWithdrawalBatch(hash common.Hash) (*StateBatchJSON, error) { + const selectWithdrawalBatchStatement = ` SELECT - withdrawals.guid, withdrawals.from_address, withdrawals.to_address, - withdrawals.amount, withdrawals.tx_hash, withdrawals.data, - withdrawals.l1_token, withdrawals.l2_token, - l2_tokens.name, l2_tokens.symbol, l2_tokens.decimals, - l1_blocks.number, l1_blocks.timestamp, - l2_blocks.number, l2_blocks.timestamp - FROM withdrawals - INNER JOIN l1_blocks ON withdrawals.l1_block_hash=l1_blocks.hash - INNER JOIN l2_blocks ON withdrawals.l2_block_hash=l2_blocks.hash - INNER JOIN l2_tokens ON withdrawals.l2_token=l2_tokens.address - WHERE withdrawals.tx_hash = $1; + state_batches.index, state_batches.root, state_batches.size, state_batches.prev_total, state_batches.extra_data, state_batches.block_hash, + l1_blocks.number, l1_blocks.timestamp + FROM state_batches + INNER JOIN l1_blocks ON state_batches.block_hash = l1_blocks.hash + WHERE size + prev_total >= ( + SELECT + number + FROM + withdrawals + INNER JOIN l2_blocks ON withdrawals.block_hash = l2_blocks.hash where tx_hash=$1 + ) ORDER BY "index" LIMIT 1; ` - var withdrawal *WithdrawalJSON + var batch *StateBatchJSON err := txn(d.db, func(tx *sql.Tx) error { - row := tx.QueryRow(selectWithdrawalStatement, hash.String()) + row := tx.QueryRow(selectWithdrawalBatchStatement, hash.String()) if row.Err() != nil { return row.Err() } - var l2Token Token - if err := row.Scan( - &withdrawal.GUID, &withdrawal.FromAddress, &withdrawal.ToAddress, - &withdrawal.Amount, &withdrawal.TxHash, &withdrawal.Data, - &withdrawal.L1Token, &l2Token.Address, - &l2Token.Name, &l2Token.Symbol, &l2Token.Decimals, - &withdrawal.L1BlockNumber, &withdrawal.L1BlockTimestamp, - &withdrawal.L2BlockNumber, &withdrawal.L2BlockTimestamp, - ); err != nil { + 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 + return nil + } return err } - withdrawal.L2Token = &l2Token + + batch = &StateBatchJSON{ + Index: index, + Root: root, + Size: size, + PrevTotal: prevTotal, + ExtraData: extraData, + BlockHash: blockHash, + BlockNumber: blockNumber, + BlockTimestamp: blockTimestamp, + } return nil }) @@ -445,24 +454,24 @@ func (d *Database) GetWithdrawalStatus(hash common.Hash) (*WithdrawalJSON, error return nil, err } - return withdrawal, nil + return batch, nil } // GetWithdrawalsByAddress returns the list of Withdrawals indexed for the given // address paginated by the given params. func (d *Database) GetWithdrawalsByAddress(address common.Address, page PaginationParam) (*PaginatedWithdrawals, error) { - const selectWithdrawalsStatement = ` + selectWithdrawalsStatement := fmt.Sprintf(` SELECT withdrawals.guid, withdrawals.from_address, withdrawals.to_address, withdrawals.amount, withdrawals.tx_hash, withdrawals.data, withdrawals.l1_token, withdrawals.l2_token, l2_tokens.name, l2_tokens.symbol, l2_tokens.decimals, - l2_blocks.number, l2_blocks.timestamp + l2_blocks.number, l2_blocks.timestamp, withdrawals.br_withdrawal_hash FROM withdrawals - INNER JOIN l2_blocks ON withdrawals.l2_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 - WHERE withdrawals.from_address = $1 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()) var withdrawals []WithdrawalJSON err := txn(d.db, func(tx *sql.Tx) error { @@ -475,16 +484,21 @@ func (d *Database) GetWithdrawalsByAddress(address common.Address, page Paginati for rows.Next() { var withdrawal WithdrawalJSON var l2Token Token + var wdHash sql.NullString if err := rows.Scan( &withdrawal.GUID, &withdrawal.FromAddress, &withdrawal.ToAddress, &withdrawal.Amount, &withdrawal.TxHash, &withdrawal.Data, &withdrawal.L1Token, &l2Token.Address, &l2Token.Name, &l2Token.Symbol, &l2Token.Decimals, - &withdrawal.L2BlockNumber, &withdrawal.L2BlockTimestamp, + &withdrawal.BlockNumber, &withdrawal.BlockTimestamp, + &wdHash, ); err != nil { return err } withdrawal.L2Token = &l2Token + if wdHash.Valid { + withdrawal.BedrockWithdrawalHash = &wdHash.String + } withdrawals = append(withdrawals, withdrawal) } @@ -495,11 +509,16 @@ func (d *Database) GetWithdrawalsByAddress(address common.Address, page Paginati return nil, err } + for i := range withdrawals { + batch, _ := d.GetWithdrawalBatch(common.HexToHash(withdrawals[i].TxHash)) + withdrawals[i].Batch = batch + } + const selectWithdrawalCountStatement = ` SELECT count(*) FROM withdrawals - INNER JOIN l2_blocks ON withdrawals.l2_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 WHERE withdrawals.from_address = $1; ` @@ -647,9 +666,9 @@ func (d *Database) GetIndexedL1BlockByHash(hash common.Hash) (*IndexedL1Block, e } const getAirdropQuery = ` -SELECT +SELECT address, voter_amount, multisig_signer_amount, gitcoin_amount, - active_bridged_amount, op_user_amount, op_repeat_user_amount, + active_bridged_amount, op_user_amount, op_repeat_user_amount, bonus_amount, total_amount FROM airdrops WHERE address = $1 @@ -681,3 +700,12 @@ func (d *Database) GetAirdrop(address common.Address) (*Airdrop, error) { } return airdrop, nil } + +func nullableHash(in *common.Hash) *string { + if in == nil { + return nil + } + + out := in.String() + return &out +} diff --git a/indexer/db/eth.go b/indexer/db/eth.go index 1cbb2d2443b7b..a6ca9881ef9c5 100644 --- a/indexer/db/eth.go +++ b/indexer/db/eth.go @@ -2,10 +2,12 @@ package db import "github.com/ethereum/go-ethereum/common" +var ETHL1Address common.Address + // ETHL1Token is a placeholder token for differentiating ETH transactions from // ERC20 transactions on L1. var ETHL1Token = &Token{ - Address: "0x0000000000000000000000000000000000000000", + Address: ETHL1Address.String(), Name: "Ethereum", Symbol: "ETH", Decimals: 18, @@ -18,7 +20,7 @@ var ETHL2Address = common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD00 // ETHL2Token is a placeholder token for differentiating ETH transactions from // ERC20 transactions on L2. var ETHL2Token = &Token{ - Address: "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", + Address: ETHL2Address.String(), Name: "Ethereum", Symbol: "ETH", Decimals: 18, diff --git a/indexer/db/l1block.go b/indexer/db/l1block.go index 73dd33348b985..839d75010a58b 100644 --- a/indexer/db/l1block.go +++ b/indexer/db/l1block.go @@ -6,12 +6,11 @@ import ( // 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 - Withdrawals []Withdrawal + Hash common.Hash + ParentHash common.Hash + Number uint64 + Timestamp uint64 + Deposits []Deposit } // String returns the block hash for the indexed l1 block. @@ -25,7 +24,6 @@ type IndexedL2Block struct { ParentHash common.Hash Number uint64 Timestamp uint64 - Deposits []Deposit Withdrawals []Withdrawal } diff --git a/indexer/db/sql.go b/indexer/db/sql.go index 8a85abcb3f633..667eca841e9e2 100644 --- a/indexer/db/sql.go +++ b/indexer/db/sql.go @@ -28,10 +28,8 @@ CREATE TABLE IF NOT EXISTS deposits ( amount VARCHAR NOT NULL, data BYTEA NOT NULL, log_index INTEGER NOT NULL, - l1_block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash), - l2_block_hash VARCHAR REFERENCES l2_blocks(hash), - tx_hash VARCHAR NOT NULL, - failed BOOLEAN NOT NULL DEFAULT false + block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash), + tx_hash VARCHAR NOT NULL ) ` @@ -53,6 +51,20 @@ CREATE TABLE IF NOT EXISTS l2_tokens ( ) ` +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, @@ -63,9 +75,9 @@ CREATE TABLE IF NOT EXISTS withdrawals ( amount VARCHAR NOT NULL, data BYTEA NOT NULL, log_index INTEGER NOT NULL, - l1_block_hash VARCHAR REFERENCES l1_blocks(hash), - l2_block_hash VARCHAR NOT NULL REFERENCES l2_blocks(hash), - tx_hash VARCHAR NOT NULL + block_hash VARCHAR NOT NULL REFERENCES l2_blocks(hash), + tx_hash VARCHAR NOT NULL, + state_batch INTEGER REFERENCES state_batches(index) ) ` @@ -110,15 +122,25 @@ CREATE TABLE IF NOT EXISTS airdrops ( ) ` +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_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_success BOOLEAN NULL; +CREATE INDEX IF NOT EXISTS withdrawals_br_withdrawal_hash ON withdrawals(br_withdrawal_hash); +` + var schema = []string{ createL1BlocksTable, createL2BlocksTable, createL1TokensTable, createL2TokensTable, + createStateBatchesTable, insertETHL1Token, insertETHL2Token, createDepositsTable, createWithdrawalsTable, createL1L2NumberIndex, createAirdropsTable, + updateWithdrawalsTable, } diff --git a/indexer/db/state_batch.go b/indexer/db/state_batch.go new file mode 100644 index 0000000000000..c8d820a4a1c2a --- /dev/null +++ b/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/indexer/db/withdrawal.go b/indexer/db/withdrawal.go index da7fb1cbee799..2595b315f122d 100644 --- a/indexer/db/withdrawal.go +++ b/indexer/db/withdrawal.go @@ -17,6 +17,7 @@ type Withdrawal struct { Amount *big.Int Data []byte LogIndex uint + BedrockHash *common.Hash } // String returns the tx hash for the withdrawal. @@ -26,17 +27,54 @@ func (w Withdrawal) String() 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"` - L1BlockNumber uint64 `json:"l1BlockNumber"` - L1BlockTimestamp string `json:"l1BlockTimestamp"` - L2BlockNumber uint64 `json:"l2BlockNumber"` - L2BlockTimestamp string `json:"l2BlockTimestamp"` - TxHash string `json:"transactionHash"` + 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"` + BedrockWithdrawalHash *string `json:"bedrockWithdrawalHash"` +} + +type FinalizationState int + +const ( + FinalizationStateAny FinalizationState = iota + FinalizationStateFinalized + FinalizationStateUnfinalized +) + +func ParseFinalizationState(in string) FinalizationState { + switch in { + case "true": + return FinalizationStateFinalized + case "false": + return FinalizationStateUnfinalized + default: + return FinalizationStateAny + } +} + +func (f FinalizationState) SQL() string { + switch f { + case FinalizationStateFinalized: + return "AND withdrawals.l1_block_hash IS NOT NULL" + case FinalizationStateUnfinalized: + return "AND withdrawals.l2_block_hash IS NULL" + } + + return "" +} + +type FinalizedWithdrawal struct { + WithdrawalHash common.Hash + TxHash common.Hash + Success bool + LogIndex uint } diff --git a/indexer/services/l1/service.go b/indexer/services/l1/service.go index 7d026528ba650..ac2591c350b6c 100644 --- a/indexer/services/l1/service.go +++ b/indexer/services/l1/service.go @@ -298,12 +298,11 @@ func (s *Service) Update(newHeader *types.Header) error { } block := &db.IndexedL1Block{ - Hash: blockHash, - ParentHash: header.ParentHash, - Number: number, - Timestamp: header.Time, - Deposits: deposits, - Withdrawals: withdrawals, + Hash: blockHash, + ParentHash: header.ParentHash, + Number: number, + Timestamp: header.Time, + Deposits: deposits, } err := s.cfg.DB.AddIndexedL1Block(block) diff --git a/indexer/services/l2/service.go b/indexer/services/l2/service.go index 410b5179ca7ed..31bfcaa4351ca 100644 --- a/indexer/services/l2/service.go +++ b/indexer/services/l2/service.go @@ -286,7 +286,6 @@ func (s *Service) Update(newHeader *types.Header) error { for i, header := range headers { blockHash := header.Hash() number := header.Number.Uint64() - deposits := depositsByBlockHash[blockHash] withdrawals := withdrawalsByBlockHash[blockHash] if len(withdrawals) == 0 && i != len(headers)-1 { @@ -298,7 +297,6 @@ func (s *Service) Update(newHeader *types.Header) error { ParentHash: header.ParentHash, Number: number, Timestamp: header.Time, - Deposits: deposits, Withdrawals: withdrawals, } @@ -359,15 +357,7 @@ func (s *Service) GetIndexerStatus(w http.ResponseWriter, r *http.Request) { } func (s *Service) GetWithdrawalStatus(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - withdrawal, err := s.cfg.DB.GetWithdrawalStatus(common.HexToHash(vars["hash"])) - if err != nil { - server.RespondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - server.RespondWithJSON(w, http.StatusOK, withdrawal) + // Temporary stub until rest of indexer is landed } func (s *Service) GetWithdrawals(w http.ResponseWriter, r *http.Request) {