Skip to content

Commit 6893226

Browse files
committed
feat: add cost statistics
1 parent be78973 commit 6893226

File tree

4 files changed

+76
-1
lines changed

4 files changed

+76
-1
lines changed

internal/api/api.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828
// @tag.description Object Queries
2929
// @tag.name system
3030
// @tag.description System status and health monitoring endpoints
31+
// @tag.name statistics
32+
// @tag.description Statistics and analytics endpoints
3133
// @schemes http https
3234

3335
var scalarHTML []byte
@@ -94,6 +96,9 @@ func Start(
9496
router.GET("/objects", handleGetAllObjectIDs)
9597
router.GET("/objects/:id", handleGetObjectByID)
9698

99+
// Statistics endpoints
100+
router.GET("/statistics/costs", handleGetCostStatistics)
101+
97102
// Setup metrics endpoint
98103
router.GET("/metrics", gin.WrapH(promhttp.Handler()))
99104

@@ -340,6 +345,27 @@ func handleGetAllObjectIDs(c *gin.Context) {
340345
c.JSON(http.StatusOK, result)
341346
}
342347

348+
// handleGetCostStatistics godoc
349+
// @Summary Get Cost Statistics
350+
// @Description Retrieves transaction fee statistics including total fees, averages, min/max values, and slot information.
351+
// @Tags statistics
352+
// @Accept json
353+
// @Produce json
354+
// @Success 200 {object} database.CostStatistics "Cost statistics"
355+
// @Failure 500 {object} map[string]string "Internal server error"
356+
// @Router /statistics/costs [get]
357+
func handleGetCostStatistics(c *gin.Context) {
358+
db := c.MustGet("db").(*database.Database)
359+
360+
stats, err := db.GetCostStatistics(c.Request.Context())
361+
if err != nil {
362+
ServerError(c, fmt.Errorf("failed to get cost statistics: %w", err))
363+
return
364+
}
365+
366+
c.JSON(http.StatusOK, stats)
367+
}
368+
343369
func ServerError(c *gin.Context, err error) {
344370
logging.GetLogger().
345371
Error("server error", "error", err, "path", c.Request.URL.Path)

internal/database/operation.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ type ApplyBatchParams struct {
3333
PreviousMerkleRoot *string
3434
BlockchainConfirmedAt *time.Time
3535
Slot int64
36+
TxID string
37+
TxFee uint32
3638
Entries []Entry
3739
}
3840

@@ -50,6 +52,9 @@ func (d *Database) ApplyOracleFile(
5052
if p.Slot <= 0 {
5153
return nil, nil, errors.New("slot must be > 0")
5254
}
55+
if p.TxID == "" {
56+
return nil, nil, errors.New("txid is required")
57+
}
5358

5459
var outFile OracleFile
5560
var outTrie Trie
@@ -110,6 +115,8 @@ func (d *Database) ApplyOracleFile(
110115
TrieLibrary: p.TrieLibrary,
111116
BlockchainConfirmedAt: p.BlockchainConfirmedAt,
112117
Slot: p.Slot,
118+
TxID: p.TxID,
119+
TxFee: p.TxFee,
113120
}
114121
if err := tx.Create(tr).Error; err != nil {
115122
return err
@@ -608,3 +615,39 @@ func (d *Database) GetObjectValuesAtTimestamp(
608615
}
609616
return out, nil
610617
}
618+
619+
type CostStatistics struct {
620+
TotalFees uint64 `json:"totalFees"`
621+
AverageFee float64 `json:"averageFee"`
622+
MinFee uint32 `json:"minFee"`
623+
MaxFee uint32 `json:"maxFee"`
624+
TotalTransactions int64 `json:"totalTransactions"`
625+
LatestSlot int64 `json:"latestSlot"`
626+
EarliestSlot int64 `json:"earliestSlot"`
627+
}
628+
629+
// GetCostStatistics returns transaction fee statistics from the trie table
630+
func (d *Database) GetCostStatistics(
631+
ctx context.Context,
632+
) (*CostStatistics, error) {
633+
var stats CostStatistics
634+
635+
err := d.db.WithContext(ctx).
636+
Model(&Trie{}).
637+
Select(`
638+
COALESCE(SUM(tx_fee), 0) as total_fees,
639+
COALESCE(AVG(tx_fee), 0) as average_fee,
640+
COALESCE(MIN(tx_fee), 0) as min_fee,
641+
COALESCE(MAX(tx_fee), 0) as max_fee,
642+
COUNT(*) as total_transactions,
643+
COALESCE(MAX(slot), 0) as latest_slot,
644+
COALESCE(MIN(slot), 0) as earliest_slot
645+
`).
646+
Scan(&stats).Error
647+
648+
if err != nil {
649+
return nil, fmt.Errorf("failed to get cost statistics: %w", err)
650+
}
651+
652+
return &stats, nil
653+
}

internal/database/oracle.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ type Trie struct {
4545
TrieLibrary *string `gorm:"column:trie_library"`
4646
BlockchainConfirmedAt *time.Time `gorm:"column:blockchain_confirmed_at;index:trie_confirmed_idx"`
4747
Slot int64 `gorm:"column:slot;not null;index:trie_slot_idx"`
48+
TxID string `gorm:"column:txid;not null;uniqueIndex"`
49+
TxFee uint32 `gorm:"column:tx_fee;not null"`
4850
CreatedAt time.Time `gorm:"column:created_at;not null;default:now()"`
4951

5052
// belongs to OracleFile (ON DELETE CASCADE)

internal/strategy/chain_event_processor_strategy.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1458,7 +1458,11 @@ func (s *ChainEventProcessorActor) processTransactionEvent(
14581458
PreviousMerkleRoot: prevRootPtr,
14591459
BlockchainConfirmedAt: &txEvent.EventTimestamp,
14601460
Slot: int64(txEvent.EventContext.SlotNumber),
1461-
Entries: entries,
1461+
TxID: txHash,
1462+
TxFee: uint32(
1463+
txEvent.EventTransaction.Transaction.Fee(),
1464+
),
1465+
Entries: entries,
14621466
}
14631467

14641468
if _, _, err := s.db.ApplyOracleFile(context.Background(), params); err != nil {

0 commit comments

Comments
 (0)