134 lines
3.4 KiB
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(¤tBlock)
|
|
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
|
|
}
|
|
|