aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-09-21 12:40:02 +0200
committerFlorian Dold <florian@dold.me>2022-09-21 12:40:11 +0200
commit28b4489bead6cd88db1b91e0e0ae8b8e0d1d0007 (patch)
tree2ffbe9d0b63aef8cc91cba28f4c65dd1b5a94a02
parent859991a40c4a7757d874f9ae6e6db7b76145a3c3 (diff)
wallet-core: make basic backup work again
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts10
-rw-r--r--packages/taler-wallet-core/src/errors.ts13
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts216
-rw-r--r--packages/taler-wallet-core/src/operations/backup/index.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts17
-rw-r--r--packages/taler-wallet-core/src/wallet.ts3
6 files changed, 112 insertions, 149 deletions
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts b/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts
index ec1d6417b..f5c9af07e 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts
@@ -31,9 +31,6 @@ import {
} from "../harness/helpers.js";
import { SyncService } from "../harness/sync";
-/**
- * Run test for basic, bank-integrated withdrawal.
- */
export async function runWalletBackupDoublespendTest(t: GlobalTestState) {
// Set up test environment
@@ -131,6 +128,13 @@ export async function runWalletBackupDoublespendTest(t: GlobalTestState) {
// Make wallet pay for the order
+ {
+ console.log(
+ "wallet2 balance before preparePay:",
+ await wallet2.client.call(WalletApiOperation.GetBalances, {}),
+ );
+ }
+
const preparePayResult = await wallet2.client.call(
WalletApiOperation.PreparePayForUri,
{
diff --git a/packages/taler-wallet-core/src/errors.ts b/packages/taler-wallet-core/src/errors.ts
index 62bde667d..0f4c480cd 100644
--- a/packages/taler-wallet-core/src/errors.ts
+++ b/packages/taler-wallet-core/src/errors.ts
@@ -113,6 +113,19 @@ function getDefaultHint(code: number): string {
}
}
+export class TalerProtocolViolationError<T = any> extends Error {
+ constructor(hint?: string) {
+ let msg: string;
+ if (hint) {
+ msg = `Taler protocol violation error (${hint})`;
+ } else {
+ msg = `Taler protocol violation error`;
+ }
+ super(msg);
+ Object.setPrototypeOf(this, TalerProtocolViolationError.prototype);
+ }
+}
+
export class TalerError<T = any> extends Error {
errorDetail: TalerErrorDetail & T;
private constructor(d: TalerErrorDetail & T) {
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts
index 507a6cf10..20c7316c1 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -18,6 +18,7 @@ import {
AgeRestriction,
AmountJson,
Amounts,
+ BackupCoin,
BackupCoinSourceType,
BackupDenomSel,
BackupProposalStatus,
@@ -37,6 +38,7 @@ import {
} from "@gnu-taler/taler-util";
import {
AbortStatus,
+ CoinRecord,
CoinSource,
CoinSourceType,
CoinStatus,
@@ -65,6 +67,7 @@ import {
} from "../../util/invariants.js";
import { GetReadOnlyAccess, GetReadWriteAccess } from "../../util/query.js";
import { RetryInfo } from "../../util/retries.js";
+import { makeCoinAvailable } from "../../wallet.js";
import { getExchangeDetails } from "../exchanges.js";
import { makeEventId, TombstoneTag } from "../transactions.js";
import { provideBackupState } from "./state.js";
@@ -226,6 +229,71 @@ export interface BackupCryptoPrecomputedData {
reservePrivToPub: Record<string, string>;
}
+export async function importCoin(
+ ws: InternalWalletState,
+ tx: GetReadWriteAccess<{
+ coins: typeof WalletStoresV1.coins;
+ coinAvailability: typeof WalletStoresV1.coinAvailability;
+ denominations: typeof WalletStoresV1.denominations;
+ }>,
+ cryptoComp: BackupCryptoPrecomputedData,
+ args: {
+ backupCoin: BackupCoin;
+ exchangeBaseUrl: string;
+ denomPubHash: string;
+ },
+): Promise<void> {
+ const { backupCoin, exchangeBaseUrl, denomPubHash } = args;
+ const compCoin = cryptoComp.coinPrivToCompletedCoin[backupCoin.coin_priv];
+ checkLogicInvariant(!!compCoin);
+ const existingCoin = await tx.coins.get(compCoin.coinPub);
+ if (!existingCoin) {
+ let coinSource: CoinSource;
+ switch (backupCoin.coin_source.type) {
+ case BackupCoinSourceType.Refresh:
+ coinSource = {
+ type: CoinSourceType.Refresh,
+ oldCoinPub: backupCoin.coin_source.old_coin_pub,
+ };
+ break;
+ case BackupCoinSourceType.Tip:
+ coinSource = {
+ type: CoinSourceType.Tip,
+ coinIndex: backupCoin.coin_source.coin_index,
+ walletTipId: backupCoin.coin_source.wallet_tip_id,
+ };
+ break;
+ case BackupCoinSourceType.Withdraw:
+ coinSource = {
+ type: CoinSourceType.Withdraw,
+ coinIndex: backupCoin.coin_source.coin_index,
+ reservePub: backupCoin.coin_source.reserve_pub,
+ withdrawalGroupId: backupCoin.coin_source.withdrawal_group_id,
+ };
+ break;
+ }
+ const coinRecord: CoinRecord = {
+ blindingKey: backupCoin.blinding_key,
+ coinEvHash: compCoin.coinEvHash,
+ coinPriv: backupCoin.coin_priv,
+ currentAmount: Amounts.parseOrThrow(backupCoin.current_amount),
+ denomSig: backupCoin.denom_sig,
+ coinPub: compCoin.coinPub,
+ exchangeBaseUrl,
+ denomPubHash,
+ status: backupCoin.fresh ? CoinStatus.Fresh : CoinStatus.Dormant,
+ coinSource,
+ // FIXME!
+ maxAge: AgeRestriction.AGE_UNRESTRICTED,
+ };
+ if (coinRecord.status === CoinStatus.Fresh) {
+ await makeCoinAvailable(ws, tx, coinRecord);
+ } else {
+ await tx.coins.put(coinRecord);
+ }
+ }
+}
+
export async function importBackup(
ws: InternalWalletState,
backupBlobArg: any,
@@ -241,6 +309,7 @@ export async function importBackup(
x.exchangeDetails,
x.exchanges,
x.coins,
+ x.coinAvailability,
x.denominations,
x.purchases,
x.proposals,
@@ -360,10 +429,6 @@ export async function importBackup(
denomPubHash,
]);
if (!existingDenom) {
- logger.info(
- `importing backup denomination: ${j2s(backupDenomination)}`,
- );
-
const value = Amounts.parseOrThrow(backupDenomination.value);
await tx.denominations.put({
@@ -398,53 +463,11 @@ export async function importBackup(
});
}
for (const backupCoin of backupDenomination.coins) {
- const compCoin =
- cryptoComp.coinPrivToCompletedCoin[backupCoin.coin_priv];
- checkLogicInvariant(!!compCoin);
- const existingCoin = await tx.coins.get(compCoin.coinPub);
- if (!existingCoin) {
- let coinSource: CoinSource;
- switch (backupCoin.coin_source.type) {
- case BackupCoinSourceType.Refresh:
- coinSource = {
- type: CoinSourceType.Refresh,
- oldCoinPub: backupCoin.coin_source.old_coin_pub,
- };
- break;
- case BackupCoinSourceType.Tip:
- coinSource = {
- type: CoinSourceType.Tip,
- coinIndex: backupCoin.coin_source.coin_index,
- walletTipId: backupCoin.coin_source.wallet_tip_id,
- };
- break;
- case BackupCoinSourceType.Withdraw:
- coinSource = {
- type: CoinSourceType.Withdraw,
- coinIndex: backupCoin.coin_source.coin_index,
- reservePub: backupCoin.coin_source.reserve_pub,
- withdrawalGroupId:
- backupCoin.coin_source.withdrawal_group_id,
- };
- break;
- }
- await tx.coins.put({
- blindingKey: backupCoin.blinding_key,
- coinEvHash: compCoin.coinEvHash,
- coinPriv: backupCoin.coin_priv,
- currentAmount: Amounts.parseOrThrow(backupCoin.current_amount),
- denomSig: backupCoin.denom_sig,
- coinPub: compCoin.coinPub,
- exchangeBaseUrl: backupExchangeDetails.base_url,
- denomPubHash,
- status: backupCoin.fresh
- ? CoinStatus.Fresh
- : CoinStatus.Dormant,
- coinSource,
- // FIXME!
- maxAge: AgeRestriction.AGE_UNRESTRICTED,
- });
- }
+ await importCoin(ws, tx, cryptoComp, {
+ backupCoin,
+ denomPubHash,
+ exchangeBaseUrl: backupExchangeDetails.base_url,
+ });
}
}
@@ -532,97 +555,6 @@ export async function importBackup(
timestampFinish: backupWg.timestamp_finish,
});
}
-
- // FIXME: import reserves with new schema
-
- // for (const backupReserve of backupExchangeDetails.reserves) {
- // const reservePub =
- // cryptoComp.reservePrivToPub[backupReserve.reserve_priv];
- // const ts = makeEventId(TombstoneTag.DeleteReserve, reservePub);
- // if (tombstoneSet.has(ts)) {
- // continue;
- // }
- // checkLogicInvariant(!!reservePub);
- // const existingReserve = await tx.reserves.get(reservePub);
- // const instructedAmount = Amounts.parseOrThrow(
- // backupReserve.instructed_amount,
- // );
- // if (!existingReserve) {
- // let bankInfo: ReserveBankInfo | undefined;
- // if (backupReserve.bank_info) {
- // bankInfo = {
- // exchangePaytoUri: backupReserve.bank_info.exchange_payto_uri,
- // statusUrl: backupReserve.bank_info.status_url,
- // confirmUrl: backupReserve.bank_info.confirm_url,
- // };
- // }
- // await tx.reserves.put({
- // currency: instructedAmount.currency,
- // instructedAmount,
- // exchangeBaseUrl: backupExchangeDetails.base_url,
- // reservePub,
- // reservePriv: backupReserve.reserve_priv,
- // bankInfo,
- // timestampCreated: backupReserve.timestamp_created,
- // timestampBankConfirmed:
- // backupReserve.bank_info?.timestamp_bank_confirmed,
- // timestampReserveInfoPosted:
- // backupReserve.bank_info?.timestamp_reserve_info_posted,
- // senderWire: backupReserve.sender_wire,
- // retryInfo: RetryInfo.reset(),
- // lastError: undefined,
- // initialWithdrawalGroupId:
- // backupReserve.initial_withdrawal_group_id,
- // initialWithdrawalStarted:
- // backupReserve.withdrawal_groups.length > 0,
- // // FIXME!
- // reserveStatus: ReserveRecordStatus.QueryingStatus,
- // initialDenomSel: await getDenomSelStateFromBackup(
- // tx,
- // backupExchangeDetails.base_url,
- // backupReserve.initial_selected_denoms,
- // ),
- // // FIXME!
- // operationStatus: OperationStatus.Pending,
- // });
- // }
- // for (const backupWg of backupReserve.withdrawal_groups) {
- // const ts = makeEventId(
- // TombstoneTag.DeleteWithdrawalGroup,
- // backupWg.withdrawal_group_id,
- // );
- // if (tombstoneSet.has(ts)) {
- // continue;
- // }
- // const existingWg = await tx.withdrawalGroups.get(
- // backupWg.withdrawal_group_id,
- // );
- // if (!existingWg) {
- // await tx.withdrawalGroups.put({
- // denomsSel: await getDenomSelStateFromBackup(
- // tx,
- // backupExchangeDetails.base_url,
- // backupWg.selected_denoms,
- // ),
- // exchangeBaseUrl: backupExchangeDetails.base_url,
- // lastError: undefined,
- // rawWithdrawalAmount: Amounts.parseOrThrow(
- // backupWg.raw_withdrawal_amount,
- // ),
- // reservePub,
- // retryInfo: RetryInfo.reset(),
- // secretSeed: backupWg.secret_seed,
- // timestampStart: backupWg.timestamp_created,
- // timestampFinish: backupWg.timestamp_finish,
- // withdrawalGroupId: backupWg.withdrawal_group_id,
- // denomSelUid: backupWg.selected_denoms_id,
- // operationStatus: backupWg.timestamp_finish
- // ? OperationStatus.Finished
- // : OperationStatus.Pending,
- // });
- // }
- // }
- // }
}
for (const backupProposal of backupBlob.proposals) {
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts
index b69c0b7b7..fc84ce4ef 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -482,6 +482,8 @@ export async function processBackupForProvider(
throw Error("unknown backup provider");
}
+ logger.info(`running backup for provider ${backupProviderBaseUrl}`);
+
return await runBackupCycleForProvider(ws, {
backupProviderBaseUrl: provider.baseUrl,
retryAfterPayment: true,
diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts
index 415100160..6757b79b4 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -78,6 +78,7 @@ import {
makeErrorDetail,
makePendingOperationFailedError,
TalerError,
+ TalerProtocolViolationError,
} from "../errors.js";
import {
EXCHANGE_COINS_LOCK,
@@ -752,7 +753,7 @@ async function handleInsufficientFunds(
return;
}
- const brokenCoinPub = (err as any).coin_pub;
+ logger.trace(`got error details: ${j2s(err)}`);
const exchangeReply = (err as any).exchange_reply;
if (
@@ -766,7 +767,12 @@ async function handleInsufficientFunds(
throw Error(`unable to handle /pay error response (${exchangeReply.code})`);
}
- logger.trace(`got error details: ${j2s(err)}`);
+ const brokenCoinPub = (exchangeReply as any).coin_pub;
+ logger.trace(`excluded broken coin pub=${brokenCoinPub}`);
+
+ if (!brokenCoinPub) {
+ throw new TalerProtocolViolationError();
+ }
const { contractData } = proposal.download;
@@ -1146,6 +1152,8 @@ export async function selectPayCoinsNew(
req,
);
+ // logger.trace(`candidate denoms: ${j2s(candidateDenoms)}`);
+
const coinPubs: string[] = [];
const coinContributions: AmountJson[] = [];
const currency = contractTermsAmount.currency;
@@ -1201,6 +1209,9 @@ export async function selectPayCoinsNew(
const finalSel = selectedDenom;
+ logger.trace(`coin selection request ${j2s(req)}`);
+ logger.trace(`selected coins (via denoms) for payment: ${j2s(finalSel)}`);
+
await ws.db
.mktx((x) => [x.coins, x.denominations])
.runReadOnly(async (tx) => {
@@ -1301,7 +1312,7 @@ export async function checkPaymentByProposalId(
});
if (!res) {
- logger.info("not confirming payment, insufficient coins");
+ logger.info("not allowing payment, insufficient coins");
return {
status: PreparePayResultType.InsufficientBalance,
contractTerms: d.contractTermsRaw,
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 2e362da6e..c91a96841 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -221,7 +221,7 @@ import {
HttpRequestLibrary,
readSuccessResponseJsonOrThrow,
} from "./util/http.js";
-import { checkDbInvariant } from "./util/invariants.js";
+import { checkDbInvariant, checkLogicInvariant } from "./util/invariants.js";
import {
AsyncCondition,
OpenedPromise,
@@ -812,6 +812,7 @@ export async function makeCoinAvailable(
}>,
coinRecord: CoinRecord,
): Promise<void> {
+ checkLogicInvariant(coinRecord.status === CoinStatus.Fresh);
const existingCoin = await tx.coins.get(coinRecord.coinPub);
if (existingCoin) {
return;