Skip to content

Commit

Permalink
feat(core): update exporter metrics (#4617)
Browse files Browse the repository at this point in the history
* feat(core): update exporter metrics

* fix: avoid multiple queries to db for the same account

* fix: logger level for getRealAssetsVersusLiabilities

* fix: real assets vs liabilities calc
  • Loading branch information
dolcalmi authored Nov 1, 2024
1 parent 47a816e commit 748afc2
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 14 deletions.
138 changes: 128 additions & 10 deletions core/api/src/servers/exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
SECS_PER_5_MINS,
} from "@/config"

import { Lightning, OnChain } from "@/app"
import { Lightning, OnChain, Prices } from "@/app"

import { toSeconds } from "@/domain/primitives"

Expand All @@ -32,6 +32,7 @@ import { activateLndHealthCheck } from "@/services/lnd/health"
import { ledgerAdmin, setupMongoConnection } from "@/services/mongodb"

import { timeoutWithCancel } from "@/utils"
import { displayAmountFromNumber, UsdDisplayCurrency } from "@/domain/fiat"

const TIMEOUT_WALLET_BALANCE = 30000

Expand All @@ -40,12 +41,35 @@ const logger = baseLogger.child({ module: "exporter" })
const prefix = "galoy"

const main = async () => {
const { getLiabilitiesBalance, getLndBalance, getBitcoindBalance, getOnChainBalance } =
ledgerAdmin
const { getLndBalance, getBitcoindBalance, getOnChainBalance } = ledgerAdmin

createGauge({
name: "assets",
description: "how much money (BTC) is on books",
collect: async () => {
const getDealerBalance = async (getId: () => Promise<WalletId>) => {
const walletId = await getId()
return getWalletBalance(walletId)
}

const btcAssets = await ledgerAdmin.getAssetsBalance()
const dealerBtcLiabilities = await getDealerBalance(getDealerBtcWalletId)

// Dealer BTC liabilities must be deducted from assets because
// Stablesats deposits and withdrawals are processed directly through Bria.
return Math.abs(btcAssets) - dealerBtcLiabilities
},
})

createGauge({
name: "liabilities",
description: "how much money customers has",
collect: getLiabilitiesBalance,
description: "how much money (BTC) customers has",
collect: async () => {
const liabilities = await getUserLiabilities()
if (liabilities instanceof Error) return 0

return liabilities
},
})

createGauge({
Expand Down Expand Up @@ -109,6 +133,12 @@ const main = async () => {
},
})

createGauge({
name: "realAssetsVsLiabilities",
description: "do we have enough Bitcoin to cover users' liabilities",
collect: getRealAssetsVersusLiabilities,
})

createGauge({
name: "assetsEqLiabilities",
description: "do we have a balanced book",
Expand Down Expand Up @@ -271,14 +301,31 @@ const createWalletGauge = ({
})
}

const inProgressBalanceQueries = new Map<string, Promise<number>>()

const getWalletBalance = async (walletId: WalletId): Promise<number> => {
const walletBalance = await LedgerService().getWalletBalance(walletId)
if (walletBalance instanceof Error) {
logger.warn({ walletId, walletBalance }, "impossible to get balance")
return 0
const inProgressKey = `wallet-${walletId}`

const inProgress = inProgressBalanceQueries.get(inProgressKey)
if (inProgress) {
return inProgress
}

return walletBalance
const balancePromise = (async () => {
try {
const walletBalance = await LedgerService().getWalletBalance(walletId)
if (walletBalance instanceof Error) {
logger.warn({ walletId, walletBalance }, "impossible to get balance")
return 0
}
return walletBalance
} finally {
inProgressBalanceQueries.delete(inProgressKey)
}
})()

inProgressBalanceQueries.set(inProgressKey, balancePromise)
return balancePromise
}

const createColdStorageWalletGauge = () => {
Expand Down Expand Up @@ -311,6 +358,77 @@ const getAssetsLiabilitiesDifference = async () => {
return assets + liabilities
}

const getUserLiabilities = async () => {
const getDealerBalance = async (getId: () => Promise<WalletId>) => {
const walletId = await getId()
return getWalletBalance(walletId)
}

const btcLiabilities = await ledgerAdmin.getLiabilitiesBalance()
const dealerBtcLiabilities = await getDealerBalance(getDealerBtcWalletId)

// Dealer BTC liabilities must be deducted from liabilities because
// Stablesats deposits and withdrawals are processed directly through Bria.
const customerBtcLiabilities = btcLiabilities - dealerBtcLiabilities

const dealerUsdLiabilities = await getDealerBalance(getDealerUsdWalletId)
logger.info(
{
btcLiabilities,
dealerBtcLiabilities,
customerBtcLiabilities,
dealerUsdLiabilities,
},
"getUserLiabilities balances",
)
const dealerUsdLiabilitiesDisplay = displayAmountFromNumber({
amount: Math.abs(dealerUsdLiabilities),
currency: UsdDisplayCurrency,
})
if (dealerUsdLiabilitiesDisplay instanceof Error) return dealerUsdLiabilitiesDisplay

const dealerUsdLiabilitiesInSatsAmount = await Prices.estimateWalletsAmounts({
amount: Number(dealerUsdLiabilitiesDisplay.displayInMajor),
currency: UsdDisplayCurrency,
})
logger.info(
{
mayor: dealerUsdLiabilitiesDisplay.displayInMajor,
currency: UsdDisplayCurrency,
dealerUsdLiabilitiesInSatsAmount,
},
"getUserLiabilities usd balances",
)
if (dealerUsdLiabilitiesInSatsAmount instanceof Error)
return dealerUsdLiabilitiesInSatsAmount

return (
customerBtcLiabilities + Number(dealerUsdLiabilitiesInSatsAmount.btcSatAmount.amount)
)
}

const getRealAssetsVersusLiabilities = async () => {
const [liabilitiesBalance, lndBalance, coldStorage, hotBalance] = await Promise.all([
getUserLiabilities(),
Lightning.getTotalBalance(),
OnChain.getColdBalance(),
OnChain.getHotBalance(),
])

const liabilities = liabilitiesBalance instanceof Error ? 0 : liabilitiesBalance
const lnd = lndBalance instanceof Error ? 0 : lndBalance
const briaHot = hotBalance instanceof Error ? 0 : Number(hotBalance.amount)
const briaCold = coldStorage instanceof Error ? 0 : Number(coldStorage.amount)

logger.info(
{ liabilities, lnd, briaHot, briaCold },
"getRealAssetsVersusLiabilities balances",
)

// if it is a negative value then it must match with exchange stablesats balance
return lnd + briaCold + briaHot - liabilities
}

export const getBookingVersusRealWorldAssets = async () => {
const [lightning, bitcoin, onChain, lndBalance, coldStorage, hotBalance] =
await Promise.all([
Expand Down
26 changes: 22 additions & 4 deletions core/api/src/services/ledger/admin-legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,30 @@ import {
UnknownLedgerError,
} from "@/domain/ledger"

const inProgressQueries = new Map<string, Promise<number>>()

const getWalletBalance = async (account: string, query = {}) => {
const params = { account, currency: "BTC", ...query }
const { balance } = await MainBookAdmin.balance(params, {
readPreference: "secondaryPreferred",
})
return balance
const inProgressKey = `${account}-${JSON.stringify(params)}`

const inProgress = inProgressQueries.get(inProgressKey)
if (inProgress) {
return inProgress
}

const balancePromise = (async () => {
try {
const { balance } = await MainBookAdmin.balance(params, {
readPreference: "secondaryPreferred",
})
return balance
} finally {
inProgressQueries.delete(inProgressKey)
}
})()

inProgressQueries.set(inProgressKey, balancePromise)
return balancePromise
}

export const getAssetsBalance = (endDate?: Date) =>
Expand Down

0 comments on commit 748afc2

Please sign in to comment.