diff options
author | Florian Dold <florian@dold.me> | 2024-06-17 13:50:43 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-06-17 13:50:43 +0200 |
commit | abbab3b029002eb5d603af611db6f411daf5d490 (patch) | |
tree | c48a79a4dc28649ac274b3dafdbb849e3bf517b0 /packages | |
parent | 05535fdc226f39666ed0a692871f54dea904af7b (diff) | |
download | wallet-core-abbab3b029002eb5d603af611db6f411daf5d490.tar.xz |
wallet-core: coin history
Diffstat (limited to 'packages')
17 files changed, 175 insertions, 66 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-revocation.ts b/packages/taler-harness/src/integrationtests/test-revocation.ts index 65aa86f98..8714a3769 100644 --- a/packages/taler-harness/src/integrationtests/test-revocation.ts +++ b/packages/taler-harness/src/integrationtests/test-revocation.ts @@ -51,7 +51,7 @@ async function revokeAllWalletCoins(req: { console.log(coinDump); const usedDenomHashes = new Set<string>(); for (const coin of coinDump.coins) { - usedDenomHashes.add(coin.denom_pub_hash); + usedDenomHashes.add(coin.denomPubHash); } for (const x of usedDenomHashes.values()) { await exchange.revokeDenomination(x); @@ -239,7 +239,7 @@ export async function runRevocationTest(t: GlobalTestState) { const coinDump = await walletClient.call(WalletApiOperation.DumpCoins, {}); console.log(coinDump); - const coinPubList = coinDump.coins.map((x) => x.coin_pub); + const coinPubList = coinDump.coins.map((x) => x.coinPub); await walletClient.call(WalletApiOperation.ForceRefresh, { refreshCoinSpecs: coinPubList.map((x) => ({ coinPub: x })), }); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-refresh-errors.ts b/packages/taler-harness/src/integrationtests/test-wallet-refresh-errors.ts index 0f1efd35e..7b101bc18 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-refresh-errors.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-refresh-errors.ts @@ -80,7 +80,7 @@ export async function runWalletRefreshErrorsTest(t: GlobalTestState) { await walletClient.call(WalletApiOperation.ForceRefresh, { refreshCoinSpecs: [ { - coinPub: coinDump.coins[0].coin_pub, + coinPub: coinDump.coins[0].coinPub, amount: "TESTKUDOS:3" as AmountString, }, ], @@ -95,7 +95,7 @@ export async function runWalletRefreshErrorsTest(t: GlobalTestState) { await walletClient.call(WalletApiOperation.ForceRefresh, { refreshCoinSpecs: [ { - coinPub: coinDump.coins[0].coin_pub, + coinPub: coinDump.coins[0].coinPub, amount: "TESTKUDOS:3" as AmountString, }, ], diff --git a/packages/taler-harness/src/integrationtests/test-wallettesting.ts b/packages/taler-harness/src/integrationtests/test-wallettesting.ts index 001081532..bc5ed1004 100644 --- a/packages/taler-harness/src/integrationtests/test-wallettesting.ts +++ b/packages/taler-harness/src/integrationtests/test-wallettesting.ts @@ -191,10 +191,10 @@ export async function runWallettestingTest(t: GlobalTestState) { { for (const c of coinDump.coins) { if ( - c.coin_status === CoinStatus.Fresh && - 0 === Amounts.cmp(c.denom_value, "TESTKUDOS:8") + c.coinStatus === CoinStatus.Fresh && + 0 === Amounts.cmp(c.denomValue, "TESTKUDOS:8") ) { - susp = c.coin_pub; + susp = c.coinPub; } } } diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 099e5c060..360d8b183 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -627,6 +627,32 @@ export enum CoinStatus { Dormant = "dormant", } +export type WalletCoinHistoryItem = + | { + type: "withdraw"; + transactionId: TransactionIdStr; + } + | { + type: "spend"; + transactionId: TransactionIdStr; + amount: AmountString; + } + | { + type: "refresh"; + transactionId: TransactionIdStr; + amount: AmountString; + } + | { + type: "recoup"; + transactionId: TransactionIdStr; + amount: AmountString; + } + | { + type: "refund"; + transactionId: TransactionIdStr; + amount: AmountString; + }; + /** * Easy to process format for the public data of coins * managed by the wallet. @@ -636,38 +662,42 @@ export interface CoinDumpJson { /** * The coin's denomination's public key. */ - denom_pub: DenominationPubKey; + denomPub: DenominationPubKey; /** * Hash of denom_pub. */ - denom_pub_hash: string; + denomPubHash: string; /** * Value of the denomination (without any fees). */ - denom_value: string; + denomValue: string; /** * Public key of the coin. */ - coin_pub: string; + coinPub: string; /** * Base URL of the exchange for the coin. */ - exchange_base_url: string; + exchangeBaseUrl: string; /** * Public key of the parent coin. * Only present if this coin was obtained via refreshing. */ - refresh_parent_coin_pub: string | undefined; + refreshParentCoinPub: string | undefined; /** * Public key of the reserve for this coin. * Only present if this coin was obtained via refreshing. */ - withdrawal_reserve_pub: string | undefined; - coin_status: CoinStatus; + withdrawalReservePub: string | undefined; + /** + * Status of the coin. + */ + coinStatus: CoinStatus; /** * Information about the age restriction */ ageCommitmentProof: AgeCommitmentProof | undefined; + history: WalletCoinHistoryItem[]; }>; } diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index 5bde7db01..252390733 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -1695,10 +1695,25 @@ advancedCli await withWallet(args, { lazyTaskLoop: true }, async (wallet) => { const coins = await wallet.client.call(WalletApiOperation.DumpCoins, {}); for (const coin of coins.coins) { - console.log(`coin ${coin.coin_pub}`); - console.log(` exchange ${coin.exchange_base_url}`); - console.log(` denomPubHash ${coin.denom_pub_hash}`); - console.log(` status ${coin.coin_status}`); + console.log(`coin ${coin.coinPub}`); + console.log(` exchange ${coin.exchangeBaseUrl}`); + console.log(` denomPubHash ${coin.denomPubHash}`); + console.log(` status ${coin.coinStatus}`); + if (coin.history.length > 0) { + console.log(` history`); + for (const hi of coin.history) { + switch (hi.type) { + case "spend": + console.log(` spend ${hi.transactionId} ${hi.amount}`); + break; + case "refresh": + console.log(` refresh ${hi.transactionId} ${hi.amount}`); + break; + default: + console.log(` unknown (${hi.type})`); + } + } + } } }); }); diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index 9f63c74d5..83f50c8e3 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -46,6 +46,7 @@ import { } from "@gnu-taler/taler-util"; import { BackupProviderRecord, + CoinHistoryRecord, CoinRecord, DbPreciseTimestamp, DepositGroupRecord, @@ -221,6 +222,21 @@ export async function spendCoins( coinAvailability.visibleCoinCount--; } } + let histEntry: CoinHistoryRecord | undefined = await tx.coinHistory.get( + coin.coinPub, + ); + if (!histEntry) { + histEntry = { + coinPub: coin.coinPub, + history: [], + }; + } + histEntry.history.push({ + type: "spend", + transactionId: csi.transactionId, + amount: Amounts.stringify(contrib), + }); + await tx.coinHistory.put(histEntry); await tx.coins.put(coin); await tx.coinAvailability.put(coinAvailability); } diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 7c2380e2d..3438cbdc7 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -905,7 +905,12 @@ export interface CoinRecord { ageCommitmentProof: AgeCommitmentProof | undefined; } -export type WalletCoinHistoryItem = +/** + * History item for a coin. + * + * DB-specific format, + */ +export type DbWalletCoinHistoryItem = | { type: "withdraw"; transactionId: TransactionIdStr; @@ -944,7 +949,7 @@ export interface CoinHistoryRecord { * We store this as an array in the object store, as the coin history * is pretty much always very small. */ - history: WalletCoinHistoryItem[]; + history: DbWalletCoinHistoryItem[]; } export enum RefreshCoinStatus { diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts index e3d0b997e..6394fdc78 100644 --- a/packages/taler-wallet-core/src/deposits.ts +++ b/packages/taler-wallet-core/src/deposits.ts @@ -518,12 +518,13 @@ async function refundDepositGroup( const res = await wex.db.runReadWriteTx( { storeNames: [ + "coinAvailability", + "coinHistory", + "coins", + "denominations", "depositGroups", "refreshGroups", "refreshSessions", - "coins", - "denominations", - "coinAvailability", ], }, async (tx) => { diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index 3bec30587..4043d3498 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -1693,12 +1693,13 @@ export async function updateExchangeFromUrlHandler( await wex.db.runReadWriteTx( { storeNames: [ + "coinAvailability", + "coinHistory", "coins", "denominations", - "coinAvailability", + "exchanges", "refreshGroups", "refreshSessions", - "exchanges", ], }, async (tx) => { diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index 118a338cc..28fb204dd 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -282,13 +282,14 @@ export class PayMerchantTransactionContext implements TransactionContext { const transitionInfo = await wex.db.runReadWriteTx( { storeNames: [ - "purchases", - "refreshGroups", - "refreshSessions", - "denominations", "coinAvailability", + "coinHistory", "coins", + "denominations", "operationRetries", + "purchases", + "refreshGroups", + "refreshSessions", ], }, async (tx) => { @@ -3407,16 +3408,17 @@ async function storeRefunds( const result = await wex.db.runReadWriteTx( { storeNames: [ + "coinAvailability", + "coinHistory", + "coins", "coins", "denominations", - "purchases", - "refundItems", - "refundGroups", "denominations", - "coins", - "coinAvailability", + "purchases", "refreshGroups", "refreshSessions", + "refundGroups", + "refundItems", ], }, async (tx) => { diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts index bc154a45d..a1c6b46ce 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -237,11 +237,12 @@ export class PeerPullDebitTransactionContext implements TransactionContext { { extraStores: [ "coinAvailability", + "coinAvailability", + "coinHistory", + "coins", "denominations", "refreshGroups", "refreshSessions", - "coins", - "coinAvailability", ], }, async (pi, tx) => { diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts index 92d4b98ba..6603cc4f3 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -733,12 +733,13 @@ async function processPeerPushDebitAbortingDeletePurse( const transitionInfo = await wex.db.runReadWriteTx( { storeNames: [ + "coinAvailability", + "coinHistory", + "coins", + "denominations", "peerPushDebit", "refreshGroups", "refreshSessions", - "denominations", - "coinAvailability", - "coins", ], }, async (tx) => { @@ -974,12 +975,13 @@ async function processPeerPushDebitReady( const transitionInfo = await wex.db.runReadWriteTx( { storeNames: [ + "coinAvailability", + "coinHistory", + "coins", + "denominations", "peerPushDebit", "refreshGroups", "refreshSessions", - "denominations", - "coinAvailability", - "coins", ], }, async (tx) => { diff --git a/packages/taler-wallet-core/src/recoup.ts b/packages/taler-wallet-core/src/recoup.ts index 9adc5e2ad..56c819bf8 100644 --- a/packages/taler-wallet-core/src/recoup.ts +++ b/packages/taler-wallet-core/src/recoup.ts @@ -392,12 +392,13 @@ export async function processRecoupGroup( await wex.db.runReadWriteTx( { storeNames: [ - "recoupGroups", "coinAvailability", + "coinHistory", + "coins", "denominations", + "recoupGroups", "refreshGroups", "refreshSessions", - "coins", ], }, async (tx) => { diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index 01970f8bc..9a5058a4d 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -92,6 +92,7 @@ import { import { CryptoApiStoppedError } from "./crypto/workers/crypto-dispatcher.js"; import { CoinAvailabilityRecord, + CoinHistoryRecord, CoinRecord, CoinSourceType, DenominationRecord, @@ -1546,7 +1547,13 @@ export async function calculateRefreshOutput( async function applyRefreshToOldCoins( wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction< - ["denominations", "coins", "refreshGroups", "coinAvailability"] + [ + "denominations", + "coins", + "coinHistory", + "refreshGroups", + "coinAvailability", + ] >, oldCoinPubs: CoinRefreshRequest[], refreshGroupId: string, @@ -1604,6 +1611,24 @@ async function applyRefreshToOldCoins( default: assertUnreachable(coin.status); } + let histEntry: CoinHistoryRecord | undefined = await tx.coinHistory.get( + coin.coinPub, + ); + if (!histEntry) { + histEntry = { + coinPub: coin.coinPub, + history: [], + }; + } + histEntry.history.push({ + type: "refresh", + transactionId: constructTransactionIdentifier({ + tag: TransactionType.Refresh, + refreshGroupId, + }), + amount: Amounts.stringify(ocp.amount), + }); + await tx.coinHistory.put(histEntry); await tx.coins.put(coin); } } @@ -1628,6 +1653,7 @@ export async function createRefreshGroup( [ "denominations", "coins", + "coinHistory", "refreshGroups", "refreshSessions", "coinAvailability", @@ -1785,6 +1811,7 @@ export async function forceRefresh( "refreshSessions", "denominations", "coins", + "coinHistory", ], }, async (tx) => { diff --git a/packages/taler-wallet-core/src/testing.ts b/packages/taler-wallet-core/src/testing.ts index 057ac50cd..6435595cb 100644 --- a/packages/taler-wallet-core/src/testing.ts +++ b/packages/taler-wallet-core/src/testing.ts @@ -58,7 +58,10 @@ import { import { getBalances } from "./balance.js"; import { genericWaitForState } from "./common.js"; import { createDepositGroup } from "./deposits.js"; -import { fetchFreshExchange } from "./exchanges.js"; +import { + acceptExchangeTermsOfService, + fetchFreshExchange, +} from "./exchanges.js"; import { confirmPay, preparePayForUri, @@ -122,6 +125,9 @@ export async function withdrawTestBalance( amount, ); + await fetchFreshExchange(wex, req.exchangeBaseUrl); + await acceptExchangeTermsOfService(wex, req.exchangeBaseUrl); + const acceptResp = await acceptWithdrawalFromUri(wex, { talerWithdrawUri: wresp.taler_withdraw_uri, selectedExchange: exchangeBaseUrl, diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index bc2011883..a58e3aff0 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -516,7 +516,7 @@ async function dumpCoins(wex: WalletExecutionContext): Promise<CoinDumpJson> { const coinsJson: CoinDumpJson = { coins: [] }; logger.info("dumping coins"); await wex.db.runReadOnlyTx( - { storeNames: ["coins", "denominations"] }, + { storeNames: ["coins", "coinHistory", "denominations"] }, async (tx) => { const coins = await tx.coins.iter().toArray(); for (const c of coins) { @@ -547,16 +547,18 @@ async function dumpCoins(wex: WalletExecutionContext): Promise<CoinDumpJson> { logger.warn("no denomination found for coin"); continue; } + const historyRec = await tx.coinHistory.get(c.coinPub); coinsJson.coins.push({ - coin_pub: c.coinPub, - denom_pub: denomInfo.denomPub, - denom_pub_hash: c.denomPubHash, - denom_value: denom.value, - exchange_base_url: c.exchangeBaseUrl, - refresh_parent_coin_pub: refreshParentCoinPub, - withdrawal_reserve_pub: withdrawalReservePub, - coin_status: c.status, + coinPub: c.coinPub, + denomPub: denomInfo.denomPub, + denomPubHash: c.denomPubHash, + denomValue: denom.value, + exchangeBaseUrl: c.exchangeBaseUrl, + refreshParentCoinPub: refreshParentCoinPub, + withdrawalReservePub: withdrawalReservePub, + coinStatus: c.status, ageCommitmentProof: c.ageCommitmentProof, + history: historyRec ? historyRec.history : [], }); } }, diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx index 8f23c0685..9feb03714 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -22,7 +22,7 @@ import { LogLevel, NotificationType, ScopeType, - stringifyWithdrawExchange + stringifyWithdrawExchange, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -112,21 +112,21 @@ export function DeveloperPage(): VNode { const currencies: { [ex: string]: string } = {}; const money_by_exchange = coins.reduce( (prev, cur) => { - const denom = Amounts.parseOrThrow(cur.denom_value); - if (!prev[cur.exchange_base_url]) { - prev[cur.exchange_base_url] = []; - currencies[cur.exchange_base_url] = denom.currency; + const denom = Amounts.parseOrThrow(cur.denomValue); + if (!prev[cur.exchangeBaseUrl]) { + prev[cur.exchangeBaseUrl] = []; + currencies[cur.exchangeBaseUrl] = denom.currency; } - prev[cur.exchange_base_url].push({ + prev[cur.exchangeBaseUrl].push({ // ageKeysCount: cur.ageCommitmentProof?.proof.privateKeys.length, denom_value: denom.value, denom_fraction: denom.fraction, // remain_value: parseFloat( // Amounts.stringifyValue(Amounts.parseOrThrow(cur.remaining_value)), // ), - status: cur.coin_status, - from_refresh: cur.refresh_parent_coin_pub !== undefined, - id: cur.coin_pub, + status: cur.coinStatus, + from_refresh: cur.refreshParentCoinPub !== undefined, + id: cur.coinPub, }); return prev; }, @@ -351,7 +351,7 @@ export function DeveloperPage(): VNode { <a href={new URL(`/keys`, e.exchangeBaseUrl).href} target="_blank" - rel="noreferrer" + rel="noreferrer" > {e.exchangeBaseUrl} </a> |