165 lines
4.5 KiB
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())
|
|
}
|
|
}
|
|
|