GetBalanceAtTime retrieves the balance state at a specific point in time. It finds the most recent snapshot before the target time and applies any subsequent transactions to calculate the exact balance state. If fromSource is true, it skips using snapshots and calculates directly from all transactio
(ctx context.Context, balanceID string, targetTime time.Time, fromSource bool)
| 1604 | // - *Balance: The calculated balance state at the target time |
| 1605 | // - error: An APIError if any issues occur during the operation |
| 1606 | func (d Datasource) GetBalanceAtTime(ctx context.Context, balanceID string, targetTime time.Time, fromSource bool) (*model.Balance, error) { |
| 1607 | // Add context timeout |
| 1608 | ctx, cancel := context.WithTimeout(ctx, 30*time.Second) |
| 1609 | defer cancel() |
| 1610 | |
| 1611 | // Validate inputs |
| 1612 | if err := validateBalanceTimeParams(balanceID, targetTime); err != nil { |
| 1613 | return nil, err |
| 1614 | } |
| 1615 | |
| 1616 | // Start transaction |
| 1617 | tx, err := d.Conn.BeginTx(ctx, &sql.TxOptions{ |
| 1618 | ReadOnly: true, |
| 1619 | Isolation: sql.LevelRepeatableRead, |
| 1620 | }) |
| 1621 | if err != nil { |
| 1622 | return nil, apierror.NewAPIError(apierror.ErrInternalServer, "Failed to start transaction", err) |
| 1623 | } |
| 1624 | defer func() { |
| 1625 | if err != nil { |
| 1626 | if rollbackErr := tx.Rollback(); rollbackErr != nil { |
| 1627 | logrus.WithError(rollbackErr).WithField("original_error", err.Error()).Error("GetBalanceAtTime: failed to rollback transaction") |
| 1628 | } |
| 1629 | } |
| 1630 | }() |
| 1631 | |
| 1632 | // Get basic balance information |
| 1633 | currency, balanceCreatedAt, err := d.getBalanceInfo(ctx, tx, balanceID) |
| 1634 | if err != nil { |
| 1635 | return nil, err |
| 1636 | } |
| 1637 | |
| 1638 | var creditBalance, debitBalance *big.Int |
| 1639 | var startTime time.Time |
| 1640 | |
| 1641 | if fromSource { |
| 1642 | // Skip snapshots and start from zero |
| 1643 | logrus.WithField("balance_id", balanceID).Debug("skipping snapshots, calculating from genesis") |
| 1644 | creditBalance = new(big.Int).SetInt64(0) |
| 1645 | debitBalance = new(big.Int).SetInt64(0) |
| 1646 | startTime = time.Time{} // Use zero time to get all transactions |
| 1647 | } else { |
| 1648 | // Try to find the most recent snapshot |
| 1649 | creditBalance, debitBalance, startTime, err = d.getMostRecentSnapshot(ctx, tx, balanceID, targetTime) |
| 1650 | if err != nil { |
| 1651 | return nil, err |
| 1652 | } |
| 1653 | } |
| 1654 | |
| 1655 | // Calculate the balance by applying transactions since the snapshot (or from genesis) |
| 1656 | creditBalance, debitBalance, err = d.calculateBalanceFromTransactions( |
| 1657 | ctx, tx, balanceID, startTime, targetTime, creditBalance, debitBalance) |
| 1658 | if err != nil { |
| 1659 | return nil, err |
| 1660 | } |
| 1661 | |
| 1662 | // Calculate final balance |
| 1663 | balance := new(big.Int).Sub(creditBalance, debitBalance) |