Files
explorer-monorepo/backend/analytics/calculator.go

134 lines
3.4 KiB
Go

package analytics
import (
"context"
"fmt"
"github.com/jackc/pgx/v5/pgxpool"
)
// Calculator calculates network analytics
type Calculator struct {
db *pgxpool.Pool
chainID int
}
// NewCalculator creates a new analytics calculator
func NewCalculator(db *pgxpool.Pool, chainID int) *Calculator {
return &Calculator{
db: db,
chainID: chainID,
}
}
// NetworkStats represents network statistics
type NetworkStats struct {
CurrentBlock int64 `json:"current_block"`
TPS float64 `json:"tps"`
GPS float64 `json:"gps"`
AvgGasPrice int64 `json:"avg_gas_price"`
PendingTransactions int `json:"pending_transactions"`
BlockTime float64 `json:"block_time_seconds"`
}
// CalculateNetworkStats calculates current network statistics
func (c *Calculator) CalculateNetworkStats(ctx context.Context) (*NetworkStats, error) {
// Get current block
var currentBlock int64
err := c.db.QueryRow(ctx,
`SELECT MAX(number) FROM blocks WHERE chain_id = $1`,
c.chainID,
).Scan(&currentBlock)
if err != nil {
return nil, fmt.Errorf("failed to get current block: %w", err)
}
// Get transactions in last 10 blocks
var txCount int
var totalGas int64
var blockTimeSum float64
query := `
SELECT
COUNT(*) as tx_count,
SUM(gas_used) as total_gas,
EXTRACT(EPOCH FROM (MAX(timestamp) - MIN(timestamp))) / COUNT(DISTINCT block_number) as avg_block_time
FROM transactions
WHERE chain_id = $1 AND block_number > $2
`
err = c.db.QueryRow(ctx, query, c.chainID, currentBlock-10).Scan(&txCount, &totalGas, &blockTimeSum)
if err != nil {
txCount = 0
totalGas = 0
blockTimeSum = 0
}
// Calculate TPS and GPS
tps := float64(txCount) / (blockTimeSum * 10)
gps := float64(totalGas) / (blockTimeSum * 10)
// Get average gas price
var avgGasPrice int64
c.db.QueryRow(ctx,
`SELECT AVG(gas_price) FROM transactions WHERE chain_id = $1 AND block_number > $2 AND gas_price IS NOT NULL`,
c.chainID, currentBlock-100,
).Scan(&avgGasPrice)
// Get pending transactions
var pendingTx int
c.db.QueryRow(ctx,
`SELECT COUNT(*) FROM mempool_transactions WHERE chain_id = $1 AND status = 'pending'`,
c.chainID,
).Scan(&pendingTx)
return &NetworkStats{
CurrentBlock: currentBlock,
TPS: tps,
GPS: gps,
AvgGasPrice: avgGasPrice,
PendingTransactions: pendingTx,
BlockTime: blockTimeSum,
}, nil
}
// TopContracts gets top contracts by transaction count
func (c *Calculator) TopContracts(ctx context.Context, limit int) ([]ContractStats, error) {
query := `
SELECT
to_address as contract_address,
COUNT(*) as tx_count,
SUM(value) as total_value
FROM transactions
WHERE chain_id = $1 AND to_address IS NOT NULL
GROUP BY to_address
ORDER BY tx_count DESC
LIMIT $2
`
rows, err := c.db.Query(ctx, query, c.chainID, limit)
if err != nil {
return nil, fmt.Errorf("failed to query top contracts: %w", err)
}
defer rows.Close()
var contracts []ContractStats
for rows.Next() {
var contract ContractStats
if err := rows.Scan(&contract.Address, &contract.TransactionCount, &contract.TotalValue); err != nil {
continue
}
contracts = append(contracts, contract)
}
return contracts, nil
}
// ContractStats represents contract statistics
type ContractStats struct {
Address string
TransactionCount int64
TotalValue string
}