Skip to content

Commit

Permalink
Added 3 new API endpoitns for retrieving estimated fees
Browse files Browse the repository at this point in the history
  • Loading branch information
kpachhai committed Dec 9, 2024
1 parent a5d377b commit 9d689fe
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 39 deletions.
150 changes: 115 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,41 +224,6 @@ The REST API is available at `http://localhost:8080`.

#### Transaction Endpoints

- **Get Transaction by Hash**

- **Endpoint**: `/transactions/:tx_hash`
- **Example**: `curl http://localhost:8080/transactions/tx_hash_here`
- **Output**:

```bash
{
"ID": 3,
"TxHash": "3cMm96hvrC4Pg6ffKgspodRUCiqqDztYzVumaJtGeD3772hmP",
"BlockHash": "2MENB3iJJRJPkvq212sCCrJgAWaWite5CijugKbuf7zEVvVqe7",
"Sponsor": "00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9",
"MaxFee": 53000,
"Success": true,
"Fee": 48500,
"Actions": [
{
"ActionType": "Transfer",
"ActionTypeID": 0,
"Input": {
"asset_address": "0x00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb",
"memo": "",
"to": "0x006835bfa9c67557da9fe6c7ad69089e17c6cad3e18284e037c78aa307e3c0c840",
"value": 5000000000
},
"Output": {
"receiver_balance": 5000000000,
"sender_balance": 852999994999876700
}
}
],
"Timestamp": "2024-12-03T21:18:36Z"
}
```

- **Get All Transactions**

- **Endpoint**: `/transactions`
Expand Down Expand Up @@ -335,6 +300,41 @@ The REST API is available at `http://localhost:8080`.
}
```

- **Get Transaction by Hash**

- **Endpoint**: `/transactions/:tx_hash`
- **Example**: `curl http://localhost:8080/transactions/tx_hash_here`
- **Output**:

```bash
{
"ID": 3,
"TxHash": "3cMm96hvrC4Pg6ffKgspodRUCiqqDztYzVumaJtGeD3772hmP",
"BlockHash": "2MENB3iJJRJPkvq212sCCrJgAWaWite5CijugKbuf7zEVvVqe7",
"Sponsor": "00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9",
"MaxFee": 53000,
"Success": true,
"Fee": 48500,
"Actions": [
{
"ActionType": "Transfer",
"ActionTypeID": 0,
"Input": {
"asset_address": "0x00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb",
"memo": "",
"to": "0x006835bfa9c67557da9fe6c7ad69089e17c6cad3e18284e037c78aa307e3c0c840",
"value": 5000000000
},
"Output": {
"receiver_balance": 5000000000,
"sender_balance": 852999994999876700
}
}
],
"Timestamp": "2024-12-03T21:18:36Z"
}
```

- **Get Transactions by Block**

- **Endpoint**: `/transactions/block/:identifier`
Expand Down Expand Up @@ -457,6 +457,86 @@ The REST API is available at `http://localhost:8080`.
}
```

- **Get Aggregated Estimated Fees for different Transactions**

- **Endpoint**: `/transactions/estimated_fee`
- **Description**: Retrieve the average, minimum, and maximum fees for all transaction types and names over a specified time interval.
- **Query Parameters**:
- `interval` (optional): Time interval for which the estimated fee is calculated (e.g., 1m, 1h, 1d). Default is `1m`.
- **Example**: `curl http://localhost:8080/transactions/estimated_fee?interval=1h`
- **Output**:

```bash
{
"avg_fee": 61650,
"min_fee": 48500,
"max_fee": 74800,
"tx_count": 2,
"fees": [
{
"action_name": "Transfer",
"action_type": 0,
"avg_fee": 48500,
"max_fee": 48500,
"min_fee": 48500,
"tx_count": 1
},
{
"action_name": "CreateAsset",
"action_type": 4,
"avg_fee": 74800,
"max_fee": 74800,
"min_fee": 74800,
"tx_count": 1
}
]
}
```

- **Get Estimated Fee for different Transactions by action type**

- **Endpoint**: `/transactions/estimated_fee/action_type/:action_type`
- **Description**: Retrieve the average, minimum, and maximum fees for transactions of a specific action type.
- **Path Parameters**:
- `action_type`: The ID of the action type (e.g., 0 for "Transfer").
- **Query Parameters**:
- `interval` (optional): Time interval for which the estimated fee is calculated (e.g., 1m, 1h, 1d). Default is `1m`.
- **Example**: `curl http://localhost:8080/transactions/estimated_fee/action_type/0?interval=1h`
- **Output**:

```bash
{
"action_name": "Transfer",
"action_type": 0,
"avg_fee": 48500,
"max_fee": 48500,
"min_fee": 48500,
"tx_count": 1
}
```

- **Get Estimated Fee for different Transactions by action name**

- **Endpoint**: `/transactions/estimated_fee/action_name/:action_name`
- **Description**: Retrieve the average, minimum, and maximum fees for transactions of a specific action name.
- **Path Parameters**:
- `action_name`: The name of the action (e.g., "Transfer", "CreateAsset", etc). Case-insensitive.
- **Query Parameters**:
- `interval` (optional): Time interval for which the estimated fee is calculated (e.g., 1m, 1h, 1d). Default is `1m`.
- **Example**: `curl http://localhost:8080/transactions/estimated_fee/action_name/createasset?interval=1h`
- **Output**:

```bash
{
"action_name": "CreateAsset",
"action_type": 4,
"avg_fee": 74800,
"max_fee": 74800,
"min_fee": 74800,
"tx_count": 1
}
```

#### Action Endpoints

- **Get All Actions**
Expand Down
153 changes: 153 additions & 0 deletions api/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,156 @@ func GetTransactionsByUser(db *sql.DB) gin.HandlerFunc {
})
}
}

// GetEstimatedFeeByActionType retrieves the estimated fee for a specific action type
func GetEstimatedFeeByActionType(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
actionType := c.Param("action_type")
interval := c.DefaultQuery("interval", "1m")

result, err := calculateEstimatedFee(db, "action_type", actionType, interval)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
}

// GetEstimatedFeeByActionName retrieves the estimated fee for a specific action name
func GetEstimatedFeeByActionName(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
actionName := c.Param("action_name")
interval := c.DefaultQuery("interval", "1m")

result, err := calculateEstimatedFee(db, "LOWER(action_name)", strings.ToLower(actionName), interval)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
}

func GetAggregateEstimatedFees(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
interval := c.DefaultQuery("interval", "1m")

// Ensure both database timestamps and query intervals are in UTC
rows, err := db.Query(`
SELECT
a.action_type,
a.action_name,
COALESCE(AVG(t.fee), 0) AS avg_fee,
COALESCE(MIN(t.fee), 0) AS min_fee,
COALESCE(MAX(t.fee), 0) AS max_fee,
COUNT(*) AS tx_count
FROM actions a
JOIN transactions t ON a.tx_hash = t.tx_hash
WHERE t.timestamp AT TIME ZONE 'UTC' >= (NOW() AT TIME ZONE 'UTC') - $1::interval
GROUP BY a.action_type, a.action_name`, interval)
if err != nil {
log.Printf("SQL Query Error: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to retrieve estimated fees"})
return
}
defer rows.Close()

var aggregated struct {
AvgFee float64 `json:"avg_fee"`
MinFee float64 `json:"min_fee"`
MaxFee float64 `json:"max_fee"`
TxCount int `json:"tx_count"`
Fees []gin.H `json:"fees"`
}

for rows.Next() {
var item struct {
ActionType int `json:"action_type"`
ActionName string `json:"action_name"`
AvgFee float64 `json:"avg_fee"`
MinFee float64 `json:"min_fee"`
MaxFee float64 `json:"max_fee"`
TxCount int `json:"tx_count"`
}
if err := rows.Scan(&item.ActionType, &item.ActionName, &item.AvgFee, &item.MinFee, &item.MaxFee, &item.TxCount); err != nil {
log.Printf("Row Scan Error: %v", err)
continue
}

aggregated.Fees = append(aggregated.Fees, gin.H{
"action_type": item.ActionType,
"action_name": item.ActionName,
"avg_fee": item.AvgFee,
"min_fee": item.MinFee,
"max_fee": item.MaxFee,
"tx_count": item.TxCount,
})
aggregated.AvgFee += item.AvgFee * float64(item.TxCount) // Weighted average
aggregated.TxCount += item.TxCount
if aggregated.MinFee == 0 || item.MinFee < aggregated.MinFee {
aggregated.MinFee = item.MinFee
}
if item.MaxFee > aggregated.MaxFee {
aggregated.MaxFee = item.MaxFee
}
}

if aggregated.TxCount > 0 {
aggregated.AvgFee /= float64(aggregated.TxCount) // Final weighted average
}

c.JSON(http.StatusOK, aggregated)
}
}

func calculateEstimatedFee(db *sql.DB, column, value, interval string) (gin.H, error) {
var result struct {
AvgFee sql.NullFloat64
MinFee sql.NullFloat64
MaxFee sql.NullFloat64
TxCount int
ActionType int
ActionName string
}

query := `
SELECT
COALESCE(AVG(t.fee), 0) AS avg_fee,
COALESCE(MIN(t.fee), 0) AS min_fee,
COALESCE(MAX(t.fee), 0) AS max_fee,
COUNT(*) AS tx_count,
a.action_type,
a.action_name
FROM actions a
JOIN transactions t ON a.tx_hash = t.tx_hash
WHERE ` + column + ` = $1 AND t.timestamp AT TIME ZONE 'UTC' >= (NOW() AT TIME ZONE 'UTC') - $2::interval
GROUP BY a.action_type, a.action_name`

err := db.QueryRow(query, value, interval).Scan(
&result.AvgFee, &result.MinFee, &result.MaxFee,
&result.TxCount, &result.ActionType, &result.ActionName,
)
if err != nil {
if err == sql.ErrNoRows {
// Return empty values if no rows are found
return gin.H{
"avg_fee": 0.0,
"min_fee": 0.0,
"max_fee": 0.0,
"tx_count": 0,
}, nil
}
log.Printf("QueryRow Error: %v", err)
return nil, err
}

return gin.H{
"avg_fee": result.AvgFee.Float64,
"min_fee": result.MinFee.Float64,
"max_fee": result.MaxFee.Float64,
"tx_count": result.TxCount,
"action_type": result.ActionType,
"action_name": result.ActionName,
}, nil
}
2 changes: 2 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ func CreateSchema(db *sql.DB) error {
CREATE INDEX IF NOT EXISTS idx_tx_hash ON transactions(tx_hash);
CREATE INDEX IF NOT EXISTS idx_transactions_block_hash ON transactions(block_hash);
CREATE INDEX IF NOT EXISTS idx_sponsor ON transactions(sponsor);
CREATE INDEX IF NOT EXISTS idx_transactions_timestamp ON transactions (timestamp);
CREATE INDEX IF NOT EXISTS idx_action_type ON actions(action_type);
CREATE INDEX IF NOT EXISTS idx_action_name_lower ON actions (LOWER(action_name));
CREATE INDEX IF NOT EXISTS idx_assets_creator ON assets(asset_creator);
CREATE INDEX IF NOT EXISTS idx_assets_type ON assets(asset_type_id);
Expand Down
2 changes: 1 addition & 1 deletion grpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (s *Server) AcceptBlock(ctx context.Context, req *pb.BlockRequest) (*emptyp
blockHash := executedBlock.BlockID.String()
parentHash := blk.Prnt.String()
stateRoot := blk.StateRoot.String()
timestamp := time.UnixMilli(blk.Tmstmp).Format(time.RFC3339)
timestamp := time.UnixMilli(blk.Tmstmp).UTC().Format(time.RFC3339)
blockSize := blk.Size()
txCount := len(blk.Txs)
avgTxSize := 0.0
Expand Down
9 changes: 6 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,12 @@ func main() {
r.GET("/blocks/:identifier", api.GetBlock(database)) // Fetch by block height or hash

r.GET("/transactions", api.GetAllTransactions(database))
r.GET("/transactions/:tx_hash", api.GetTransactionByHash(database)) // Fetch by transaction hash
r.GET("/transactions/block/:identifier", api.GetTransactionsByBlock(database)) // Fetch transactions by block height or hash
r.GET("/transactions/user/:user", api.GetTransactionsByUser(database)) // Fetch transactions by user with pagination
r.GET("/transactions/:tx_hash", api.GetTransactionByHash(database)) // Fetch by transaction hash
r.GET("/transactions/block/:identifier", api.GetTransactionsByBlock(database)) // Fetch transactions by block height or hash
r.GET("/transactions/user/:user", api.GetTransactionsByUser(database)) // Fetch transactions by user with pagination
r.GET("/transactions/estimated_fee/action_type/:action_type", api.GetEstimatedFeeByActionType(database)) // Fetch estimated fee by action type
r.GET("/transactions/estimated_fee/action_name/:action_name", api.GetEstimatedFeeByActionName(database)) // Fetch estimated fee by action name
r.GET("/transactions/estimated_fee", api.GetAggregateEstimatedFees(database)) // Fetch aggregate estimated fees

r.GET("/actions", api.GetAllActions(database))
r.GET("/actions/:tx_hash", api.GetActionsByTransactionHash(database)) // Fetch actions by transaction hash
Expand Down

0 comments on commit 9d689fe

Please sign in to comment.