Files
explorer-monorepo/backend/indexer/track2/tx_indexer.go

165 lines
4.5 KiB
Go

package track2
import (
"context"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/jackc/pgx/v5/pgxpool"
)
// TransactionIndexer indexes transactions for Track 2
type TransactionIndexer struct {
db *pgxpool.Pool
client *ethclient.Client
chainID int
}
// NewTransactionIndexer creates a new transaction indexer
func NewTransactionIndexer(db *pgxpool.Pool, client *ethclient.Client, chainID int) *TransactionIndexer {
return &TransactionIndexer{
db: db,
client: client,
chainID: chainID,
}
}
// IndexTransaction indexes a single transaction
func (ti *TransactionIndexer) IndexTransaction(ctx context.Context, txHash common.Hash, blockNumber uint64, txIndex uint) error {
// Check if transaction already indexed
var exists bool
checkQuery := `SELECT EXISTS(SELECT 1 FROM transactions WHERE chain_id = $1 AND hash = $2)`
err := ti.db.QueryRow(ctx, checkQuery, ti.chainID, txHash.Hex()).Scan(&exists)
if err != nil {
return fmt.Errorf("failed to check transaction existence: %w", err)
}
if exists {
return nil // Already indexed
}
// Get transaction receipt
receipt, err := ti.client.TransactionReceipt(ctx, txHash)
if err != nil {
return fmt.Errorf("failed to get transaction receipt: %w", err)
}
// Get transaction
tx, _, err := ti.client.TransactionByHash(ctx, txHash)
if err != nil {
return fmt.Errorf("failed to get transaction: %w", err)
}
// Get block for timestamp
block, err := ti.client.BlockByNumber(ctx, big.NewInt(int64(blockNumber)))
if err != nil {
return fmt.Errorf("failed to get block: %w", err)
}
// Determine status
status := "success"
if receipt.Status == 0 {
status = "failed"
}
// Get sender address
signer := types.NewEIP155Signer(big.NewInt(int64(ti.chainID)))
fromAddr, err := types.Sender(signer, tx)
if err != nil {
return fmt.Errorf("failed to get sender address: %w", err)
}
// Insert transaction
insertQuery := `
INSERT INTO transactions (
chain_id, hash, block_number, block_hash, transaction_index,
from_address, to_address, value, gas, gas_price, gas_used,
cumulative_gas_used, status, timestamp
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
ON CONFLICT (chain_id, hash) DO NOTHING
`
toAddr := ""
if tx.To() != nil {
toAddr = tx.To().Hex()
}
_, err = ti.db.Exec(ctx, insertQuery,
ti.chainID,
txHash.Hex(),
blockNumber,
block.Hash().Hex(),
txIndex,
fromAddr.Hex(),
toAddr,
tx.Value().String(),
tx.Gas(),
tx.GasPrice().String(),
receipt.GasUsed,
receipt.CumulativeGasUsed,
status,
time.Unix(int64(block.Time()), 0),
)
if err != nil {
return fmt.Errorf("failed to insert transaction: %w", err)
}
// Update address statistics
ti.updateAddressStats(ctx, fromAddr.Hex(), toAddr, tx.Value())
return nil
}
// IndexBlockTransactions indexes all transactions in a block
func (ti *TransactionIndexer) IndexBlockTransactions(ctx context.Context, blockNumber uint64) error {
block, err := ti.client.BlockByNumber(ctx, big.NewInt(int64(blockNumber)))
if err != nil {
return fmt.Errorf("failed to get block: %w", err)
}
for i, tx := range block.Transactions() {
if err := ti.IndexTransaction(ctx, tx.Hash(), blockNumber, uint(i)); err != nil {
fmt.Printf("Failed to index transaction %s: %v\n", tx.Hash().Hex(), err)
}
}
return nil
}
// updateAddressStats updates address statistics
func (ti *TransactionIndexer) updateAddressStats(ctx context.Context, from, to string, value *big.Int) {
// Update from address
if from != "" {
updateQuery := `
INSERT INTO addresses (address, chain_id, tx_count_sent, total_sent_wei, first_seen_timestamp, last_seen_timestamp)
VALUES ($1, $2, 1, $3, NOW(), NOW())
ON CONFLICT (address) DO UPDATE SET
tx_count_sent = addresses.tx_count_sent + 1,
total_sent_wei = addresses.total_sent_wei + $3::numeric,
last_seen_timestamp = NOW(),
updated_at = NOW()
`
ti.db.Exec(ctx, updateQuery, from, ti.chainID, value.String())
}
// Update to address
if to != "" {
updateQuery := `
INSERT INTO addresses (address, chain_id, tx_count_received, total_received_wei, first_seen_timestamp, last_seen_timestamp)
VALUES ($1, $2, 1, $3, NOW(), NOW())
ON CONFLICT (address) DO UPDATE SET
tx_count_received = addresses.tx_count_received + 1,
total_received_wei = addresses.total_received_wei + $3::numeric,
last_seen_timestamp = NOW(),
updated_at = NOW()
`
ti.db.Exec(ctx, updateQuery, to, ti.chainID, value.String())
}
}