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()) } }