Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions api/home_charts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package api

import (
"context"
"errors"
"slices"

"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/db"
)

func getTransactionTimestampMs(tx db.Transaction) int64 {
if tx.SettledAt != nil {
return tx.SettledAt.UnixMilli()
}
return tx.UpdatedAt.UnixMilli()
}

func getNetSat(tx db.Transaction) int64 {
amountSat := int64(tx.AmountMsat / 1000)
feeSat := int64(tx.FeeMsat / 1000)

switch tx.Type {
case constants.TRANSACTION_TYPE_INCOMING:
return amountSat
case constants.TRANSACTION_TYPE_OUTGOING:
return -(amountSat + feeSat)
default:
return 0
}
}

func (api *api) GetHomeChartsData(ctx context.Context, from uint64) (*HomeChartsResponse, error) {
lnClient := api.svc.GetLNClient()
if lnClient == nil {
return nil, errors.New("LNClient not started")
}

transactions, _, err := api.svc.GetTransactionsService().ListTransactions(
ctx,
from,
0,
0,
0,
false,
false,
nil,
lnClient,
nil,
false,
)
Comment on lines +39 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This chart endpoint still replays the raw transaction stream.

transactions/transactions_service.go:675-677 treats the 0 limit here as “no limit”, so every poll pulls the full matching set and later emits one point per transaction. For a 7-day sparkline refreshed every 5s, busy nodes will end up doing an O(n) query/sort/serialize cycle on each hit. Aggregate or downsample the window server-side, or cap the returned points.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/home_charts.go` around lines 39 - 51, The endpoint is pulling the full
transaction stream because api.svc.GetTransactionsService().ListTransactions is
called with 0 for the limit (treated as "no limit"), causing O(n) work per poll;
change the call in home_charts.go to request a bounded number of points (e.g., a
maxPoints constant) or a server-side aggregated/downsampled window instead of
raw transactions, or add/enable an aggregation parameter to ListTransactions so
the service returns pre-aggregated/downsampled results for the sparkline; update
the parameters to pass that limit or aggregation flag and ensure the
chart-building code uses those aggregated points rather than replaying every
transaction.

if err != nil {
return nil, err
}

points := make([]HomeChartsTxPoint, 0, len(transactions))
hasIncomingDeposit := false
var netFlowsSat int64

for _, tx := range transactions {
netSat := getNetSat(tx)
if tx.Type == constants.TRANSACTION_TYPE_INCOMING && tx.AmountMsat > 0 {
hasIncomingDeposit = true
}
netFlowsSat += netSat
points = append(points, HomeChartsTxPoint{
Timestamp: getTransactionTimestampMs(tx),
NetSat: netSat,
})
}

slices.SortFunc(points, func(a, b HomeChartsTxPoint) int {
if a.Timestamp < b.Timestamp {
return -1
}
if a.Timestamp > b.Timestamp {
return 1
}
return 0
})

return &HomeChartsResponse{
TxPoints: points,
HasIncomingDeposit: hasIncomingDeposit,
NetFlowsSat: netFlowsSat,
}, nil
}
12 changes: 12 additions & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type API interface {
SignMessage(ctx context.Context, message string) (*SignMessageResponse, error)
RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate *uint64, sendAll bool) (*RedeemOnchainFundsResponse, error)
GetBalances(ctx context.Context) (*BalancesResponse, error)
GetHomeChartsData(ctx context.Context, from uint64) (*HomeChartsResponse, error)
ListTransactions(ctx context.Context, appId *uint, limit uint64, offset uint64) (*ListTransactionsResponse, error)
ListOnchainTransactions(ctx context.Context) ([]lnclient.OnchainTransaction, error)
SendPayment(ctx context.Context, invoice string, amountMsat *uint64, metadata map[string]interface{}) (*SendPaymentResponse, error)
Expand Down Expand Up @@ -364,6 +365,17 @@ type ListTransactionsResponse struct {
Transactions []Transaction `json:"transactions"`
}

type HomeChartsTxPoint struct {
Timestamp int64 `json:"timestamp"`
NetSat int64 `json:"netSat"`
}

type HomeChartsResponse struct {
TxPoints []HomeChartsTxPoint `json:"txPoints"`
HasIncomingDeposit bool `json:"hasIncomingDeposit"`
NetFlowsSat int64 `json:"netFlowsSat"`
}

// TODO: camelCase
type Transaction struct {
Type string `json:"type"`
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"react-lottie": "^1.2.4",
"react-qr-code": "^2.0.12",
"react-router-dom": "^6.21.0",
"recharts": "^3.8.0",
"sonner": "^2.0.7",
"swr": "^2.3.6",
"tailwind-merge": "^3.4.1",
Expand Down
Loading
Loading