named wallet sync track txo stats

This commit is contained in:
Mononaut 2024-09-30 21:01:10 +00:00
parent 9c303e8c23
commit 756e4356a5
No known key found for this signature in database
GPG Key ID: A3F058E41374C04E
2 changed files with 37 additions and 12 deletions

View File

@ -305,7 +305,7 @@ class ElectrsApi implements AbstractBitcoinApi {
} }
$getAddress(address: string): Promise<IEsploraApi.Address> { $getAddress(address: string): Promise<IEsploraApi.Address> {
throw new Error('Method getAddress not implemented.'); return this.failoverRouter.$get<IEsploraApi.Address>('/address/' + address);
} }
$getAddressTransactions(address: string, txId?: string): Promise<IEsploraApi.Transaction[]> { $getAddressTransactions(address: string, txId?: string): Promise<IEsploraApi.Transaction[]> {

View File

@ -8,7 +8,14 @@ import { TransactionExtended } from '../../mempool.interfaces';
interface WalletAddress { interface WalletAddress {
address: string; address: string;
active: boolean; active: boolean;
transactions?: IEsploraApi.AddressTxSummary[]; stats: {
funded_txo_count: number;
funded_txo_sum: number;
spent_txo_count: number;
spent_txo_sum: number;
tx_count: number;
};
transactions: IEsploraApi.AddressTxSummary[];
lastSync: number; lastSync: number;
} }
@ -37,7 +44,7 @@ class WalletApi {
// resync wallet addresses from the services backend // resync wallet addresses from the services backend
async $syncWallets(): Promise<void> { async $syncWallets(): Promise<void> {
if (!config.WALLETS.ENABLED) { if (!config.WALLETS.ENABLED || this.syncing) {
return; return;
} }
this.syncing = true; this.syncing = true;
@ -74,10 +81,13 @@ class WalletApi {
const refreshTransactions = !wallet.addresses[address.address] || (address.active && (Date.now() - wallet.addresses[address.address].lastSync) > 60 * 60 * 1000); const refreshTransactions = !wallet.addresses[address.address] || (address.active && (Date.now() - wallet.addresses[address.address].lastSync) > 60 * 60 * 1000);
if (refreshTransactions) { if (refreshTransactions) {
try { try {
const summary = await bitcoinApi.$getAddressTransactionSummary(address.address);
const addressInfo = await bitcoinApi.$getAddress(address.address);
const walletAddress: WalletAddress = { const walletAddress: WalletAddress = {
address: address.address, address: address.address,
active: address.active, active: address.active,
transactions: await bitcoinApi.$getAddressTransactionSummary(address.address), transactions: summary,
stats: addressInfo.chain_stats,
lastSync: Date.now(), lastSync: Date.now(),
}; };
wallet.addresses[address.address] = walletAddress; wallet.addresses[address.address] = walletAddress;
@ -88,36 +98,51 @@ class WalletApi {
} }
// check a new block for transactions that affect wallet address balances, and add relevant transactions to wallets // check a new block for transactions that affect wallet address balances, and add relevant transactions to wallets
processBlock(block: IEsploraApi.Block, blockTxs: TransactionExtended[]): Record<string, Record<string, IEsploraApi.AddressTxSummary[]>> { processBlock(block: IEsploraApi.Block, blockTxs: TransactionExtended[]): Record<string, IEsploraApi.Transaction[]> {
const walletTransactions: Record<string, Record<string, IEsploraApi.AddressTxSummary[]>> = {}; const walletTransactions: Record<string, IEsploraApi.Transaction[]> = {};
for (const walletKey of Object.keys(this.wallets)) { for (const walletKey of Object.keys(this.wallets)) {
const wallet = this.wallets[walletKey]; const wallet = this.wallets[walletKey];
walletTransactions[walletKey] = {}; walletTransactions[walletKey] = [];
for (const tx of blockTxs) { for (const tx of blockTxs) {
const funded: Record<string, number> = {}; const funded: Record<string, number> = {};
const spent: Record<string, number> = {}; const spent: Record<string, number> = {};
const fundedCount: Record<string, number> = {};
const spentCount: Record<string, number> = {};
let anyMatch = false;
for (const vin of tx.vin) { for (const vin of tx.vin) {
const address = vin.prevout?.scriptpubkey_address; const address = vin.prevout?.scriptpubkey_address;
if (address && wallet.addresses[address]) { if (address && wallet.addresses[address]) {
anyMatch = true;
spent[address] = (spent[address] ?? 0) + (vin.prevout?.value ?? 0); spent[address] = (spent[address] ?? 0) + (vin.prevout?.value ?? 0);
spentCount[address] = (spentCount[address] ?? 0) + 1;
} }
} }
for (const vout of tx.vout) { for (const vout of tx.vout) {
const address = vout.scriptpubkey_address; const address = vout.scriptpubkey_address;
if (address && wallet.addresses[address]) { if (address && wallet.addresses[address]) {
anyMatch = true;
funded[address] = (funded[address] ?? 0) + (vout.value ?? 0); funded[address] = (funded[address] ?? 0) + (vout.value ?? 0);
fundedCount[address] = (fundedCount[address] ?? 0) + 1;
} }
} }
for (const address of Object.keys({ ...funded, ...spent })) { for (const address of Object.keys({ ...funded, ...spent })) {
if (!walletTransactions[walletKey][address]) { // update address stats
walletTransactions[walletKey][address] = []; wallet.addresses[address].stats.tx_count++;
} wallet.addresses[address].stats.funded_txo_count += fundedCount[address] || 0;
walletTransactions[walletKey][address].push({ wallet.addresses[address].stats.spent_txo_count += spentCount[address] || 0;
wallet.addresses[address].stats.funded_txo_sum += funded[address] || 0;
wallet.addresses[address].stats.spent_txo_sum += spent[address] || 0;
// add tx to summary
const txSummary: IEsploraApi.AddressTxSummary = {
txid: tx.txid, txid: tx.txid,
value: (funded[address] ?? 0) - (spent[address] ?? 0), value: (funded[address] ?? 0) - (spent[address] ?? 0),
height: block.height, height: block.height,
time: block.timestamp, time: block.timestamp,
}); };
wallet.addresses[address].transactions?.push(txSummary);
}
if (anyMatch) {
walletTransactions[walletKey].push(tx);
} }
} }
} }