aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core')
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoImplementation.ts28
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoTypes.ts10
-rw-r--r--packages/taler-wallet-core/src/db.ts12
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts168
-rw-r--r--packages/taler-wallet-core/src/operations/pending.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts23
6 files changed, 214 insertions, 29 deletions
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index 36ca128ae..7c6b142fb 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -103,6 +103,8 @@ import {
EncryptContractForDepositResponse,
EncryptContractRequest,
EncryptContractResponse,
+ SignCoinHistoryRequest,
+ SignCoinHistoryResponse,
SignDeletePurseRequest,
SignDeletePurseResponse,
SignPurseMergeRequest,
@@ -243,6 +245,10 @@ export interface TalerCryptoInterface {
signDeletePurse(
req: SignDeletePurseRequest,
): Promise<SignDeletePurseResponse>;
+
+ signCoinHistoryRequest(
+ req: SignCoinHistoryRequest,
+ ): Promise<SignCoinHistoryResponse>;
}
/**
@@ -427,6 +433,11 @@ export const nullCrypto: TalerCryptoInterface = {
): Promise<SignDeletePurseResponse> {
throw new Error("Function not implemented.");
},
+ signCoinHistoryRequest: function (
+ req: SignCoinHistoryRequest,
+ ): Promise<SignCoinHistoryResponse> {
+ throw new Error("Function not implemented.");
+ },
};
export type WithArg<X> = X extends (req: infer T) => infer R
@@ -1705,6 +1716,23 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
sig: sigResp.sig,
};
},
+ async signCoinHistoryRequest(
+ tci: TalerCryptoInterfaceR,
+ req: SignCoinHistoryRequest,
+ ): Promise<SignCoinHistoryResponse> {
+ const coinHistorySigBlob = buildSigPS(
+ TalerSignaturePurpose.WALLET_COIN_HISTORY,
+ )
+ .put(bufferForUint64(req.startOffset))
+ .build();
+ const sigResp = await tci.eddsaSign(tci, {
+ msg: encodeCrock(coinHistorySigBlob),
+ priv: req.coinPriv,
+ });
+ return {
+ sig: sigResp.sig,
+ };
+ },
};
export interface EddsaSignRequest {
diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
index 2204fac71..df25b87e4 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
@@ -279,6 +279,16 @@ export interface SignDeletePurseResponse {
sig: EddsaSignatureString;
}
+export interface SignCoinHistoryRequest {
+ coinPub: string;
+ coinPriv: string;
+ startOffset: number;
+}
+
+export interface SignCoinHistoryResponse {
+ sig: EddsaSignatureString;
+}
+
export interface SignReservePurseCreateRequest {
mergeTimestamp: TalerProtocolTimestamp;
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index d2c6b8368..6f6aad256 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1799,12 +1799,20 @@ export enum PeerPushDebitStatus {
PendingCreatePurse = 0x0100_0000 /* ACTIVE_START */,
PendingReady = 0x0100_0001,
AbortingDeletePurse = 0x0103_0000,
- AbortingRefresh = 0x0103_0001,
+ /**
+ * Refresh after the purse got deleted by the wallet.
+ */
+ AbortingRefreshDeleted = 0x0103_0001,
+ /**
+ * Refresh after the purse expired.
+ */
+ AbortingRefreshExpired = 0x0103_0002,
SuspendedCreatePurse = 0x0110_0000,
SuspendedReady = 0x0110_0001,
SuspendedAbortingDeletePurse = 0x0113_0000,
- SuspendedAbortingRefresh = 0x0113_0001,
+ SuspendedAbortingRefreshDeleted = 0x0113_0001,
+ SuspendedAbortingRefreshExpired = 0x0113_0002,
Done = 0x0500_0000,
Aborted = 0x0503_0000,
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
index da779a07d..c79aca1ad 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
@@ -365,7 +365,7 @@ async function processPeerPushDebitAbortingDeletePurse(
coinPubs,
RefreshReason.AbortPeerPushDebit,
);
- ppiRec.status = PeerPushDebitStatus.AbortingRefresh;
+ ppiRec.status = PeerPushDebitStatus.AbortingRefreshDeleted;
ppiRec.abortRefreshGroupId = refresh.refreshGroupId;
await tx.peerPushDebit.put(ppiRec);
const newTxState = computePeerPushDebitTransactionState(ppiRec);
@@ -415,7 +415,7 @@ async function transitionPeerPushDebitTransaction(
notifyTransition(ws, transactionId, transitionInfo);
}
-async function processPeerPushDebitAbortingRefresh(
+async function processPeerPushDebitAbortingRefreshDeleted(
ws: InternalWalletState,
peerPushInitiation: PeerPushDebitRecord,
): Promise<TaskRunResult> {
@@ -463,6 +463,54 @@ async function processPeerPushDebitAbortingRefresh(
return TaskRunResult.pending();
}
+async function processPeerPushDebitAbortingRefreshExpired(
+ ws: InternalWalletState,
+ peerPushInitiation: PeerPushDebitRecord,
+): Promise<TaskRunResult> {
+ const pursePub = peerPushInitiation.pursePub;
+ const abortRefreshGroupId = peerPushInitiation.abortRefreshGroupId;
+ checkLogicInvariant(!!abortRefreshGroupId);
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub: peerPushInitiation.pursePub,
+ });
+ const transitionInfo = await ws.db
+ .mktx((x) => [x.refreshGroups, x.peerPushDebit])
+ .runReadWrite(async (tx) => {
+ const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
+ let newOpState: PeerPushDebitStatus | undefined;
+ if (!refreshGroup) {
+ // Maybe it got manually deleted? Means that we should
+ // just go into failed.
+ logger.warn("no aborting refresh group found for deposit group");
+ newOpState = PeerPushDebitStatus.Failed;
+ } else {
+ if (refreshGroup.operationStatus === RefreshOperationStatus.Finished) {
+ newOpState = PeerPushDebitStatus.Expired;
+ } else if (
+ refreshGroup.operationStatus === RefreshOperationStatus.Failed
+ ) {
+ newOpState = PeerPushDebitStatus.Failed;
+ }
+ }
+ if (newOpState) {
+ const newDg = await tx.peerPushDebit.get(pursePub);
+ if (!newDg) {
+ return;
+ }
+ const oldTxState = computePeerPushDebitTransactionState(newDg);
+ newDg.status = newOpState;
+ const newTxState = computePeerPushDebitTransactionState(newDg);
+ await tx.peerPushDebit.put(newDg);
+ return { oldTxState, newTxState };
+ }
+ return undefined;
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+ // FIXME: Shouldn't this be finished in some cases?!
+ return TaskRunResult.pending();
+}
+
/**
* Process the "pending(ready)" state of a peer-push-debit transaction.
*/
@@ -476,6 +524,10 @@ async function processPeerPushDebitReady(
tag: PendingTaskType.PeerPushDebit,
pursePub,
});
+ const transactionId = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPushDebit,
+ pursePub,
+ });
runLongpollAsync(ws, retryTag, async (ct) => {
const mergeUrl = new URL(
`purses/${pursePub}/merge`,
@@ -510,14 +562,50 @@ async function processPeerPushDebitReady(
};
}
} else if (resp.status === HttpStatusCode.Gone) {
- await transitionPeerPushDebitTransaction(
- ws,
- peerPushInitiation.pursePub,
- {
- stFrom: PeerPushDebitStatus.PendingReady,
- stTo: PeerPushDebitStatus.Expired,
- },
- );
+ const transitionInfo = await ws.db
+ .mktx((x) => [
+ x.peerPushDebit,
+ x.refreshGroups,
+ x.denominations,
+ x.coinAvailability,
+ x.coins,
+ ])
+ .runReadWrite(async (tx) => {
+ const ppiRec = await tx.peerPushDebit.get(pursePub);
+ if (!ppiRec) {
+ return undefined;
+ }
+ if (ppiRec.status !== PeerPushDebitStatus.PendingReady) {
+ return undefined;
+ }
+ const currency = Amounts.currencyOf(ppiRec.amount);
+ const oldTxState = computePeerPushDebitTransactionState(ppiRec);
+ const coinPubs: CoinRefreshRequest[] = [];
+
+ for (let i = 0; i < ppiRec.coinSel.coinPubs.length; i++) {
+ coinPubs.push({
+ amount: ppiRec.coinSel.contributions[i],
+ coinPub: ppiRec.coinSel.coinPubs[i],
+ });
+ }
+
+ const refresh = await createRefreshGroup(
+ ws,
+ tx,
+ currency,
+ coinPubs,
+ RefreshReason.AbortPeerPushDebit,
+ );
+ ppiRec.status = PeerPushDebitStatus.AbortingRefreshExpired;
+ ppiRec.abortRefreshGroupId = refresh.refreshGroupId;
+ await tx.peerPushDebit.put(ppiRec);
+ const newTxState = computePeerPushDebitTransactionState(ppiRec);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
return {
ready: true,
};
@@ -569,8 +657,10 @@ export async function processPeerPushDebit(
return processPeerPushDebitReady(ws, peerPushInitiation);
case PeerPushDebitStatus.AbortingDeletePurse:
return processPeerPushDebitAbortingDeletePurse(ws, peerPushInitiation);
- case PeerPushDebitStatus.AbortingRefresh:
- return processPeerPushDebitAbortingRefresh(ws, peerPushInitiation);
+ case PeerPushDebitStatus.AbortingRefreshDeleted:
+ return processPeerPushDebitAbortingRefreshDeleted(ws, peerPushInitiation);
+ case PeerPushDebitStatus.AbortingRefreshExpired:
+ return processPeerPushDebitAbortingRefreshExpired(ws, peerPushInitiation);
default: {
const txState = computePeerPushDebitTransactionState(peerPushInitiation);
logger.warn(
@@ -722,11 +812,15 @@ export function computePeerPushDebitTransactionActions(
return [TransactionAction.Delete];
case PeerPushDebitStatus.AbortingDeletePurse:
return [TransactionAction.Suspend, TransactionAction.Fail];
- case PeerPushDebitStatus.AbortingRefresh:
+ case PeerPushDebitStatus.AbortingRefreshDeleted:
return [TransactionAction.Suspend, TransactionAction.Fail];
+ case PeerPushDebitStatus.AbortingRefreshExpired:
+ return [TransactionAction.Resume, TransactionAction.Fail];
+ case PeerPushDebitStatus.SuspendedAbortingRefreshExpired:
+ return [TransactionAction.Resume, TransactionAction.Fail];
case PeerPushDebitStatus.SuspendedAbortingDeletePurse:
return [TransactionAction.Resume, TransactionAction.Fail];
- case PeerPushDebitStatus.SuspendedAbortingRefresh:
+ case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted:
return [TransactionAction.Resume, TransactionAction.Fail];
case PeerPushDebitStatus.SuspendedCreatePurse:
return [TransactionAction.Resume, TransactionAction.Abort];
@@ -773,9 +867,11 @@ export async function abortPeerPushDebitTransaction(
// Network request might already be in-flight!
newStatus = PeerPushDebitStatus.AbortingDeletePurse;
break;
- case PeerPushDebitStatus.SuspendedAbortingRefresh:
+ case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted:
case PeerPushDebitStatus.SuspendedAbortingDeletePurse:
- case PeerPushDebitStatus.AbortingRefresh:
+ case PeerPushDebitStatus.SuspendedAbortingRefreshExpired:
+ case PeerPushDebitStatus.AbortingRefreshDeleted:
+ case PeerPushDebitStatus.AbortingRefreshExpired:
case PeerPushDebitStatus.Done:
case PeerPushDebitStatus.AbortingDeletePurse:
case PeerPushDebitStatus.Aborted:
@@ -824,13 +920,15 @@ export async function failPeerPushDebitTransaction(
}
let newStatus: PeerPushDebitStatus | undefined = undefined;
switch (pushDebitRec.status) {
- case PeerPushDebitStatus.AbortingRefresh:
- case PeerPushDebitStatus.SuspendedAbortingRefresh:
+ case PeerPushDebitStatus.AbortingRefreshDeleted:
+ case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted:
// FIXME: What to do about the refresh group?
newStatus = PeerPushDebitStatus.Failed;
break;
case PeerPushDebitStatus.AbortingDeletePurse:
case PeerPushDebitStatus.SuspendedAbortingDeletePurse:
+ case PeerPushDebitStatus.AbortingRefreshExpired:
+ case PeerPushDebitStatus.SuspendedAbortingRefreshExpired:
case PeerPushDebitStatus.PendingReady:
case PeerPushDebitStatus.SuspendedReady:
case PeerPushDebitStatus.SuspendedCreatePurse:
@@ -887,8 +985,11 @@ export async function suspendPeerPushDebitTransaction(
case PeerPushDebitStatus.PendingCreatePurse:
newStatus = PeerPushDebitStatus.SuspendedCreatePurse;
break;
- case PeerPushDebitStatus.AbortingRefresh:
- newStatus = PeerPushDebitStatus.SuspendedAbortingRefresh;
+ case PeerPushDebitStatus.AbortingRefreshDeleted:
+ newStatus = PeerPushDebitStatus.SuspendedAbortingRefreshDeleted;
+ break;
+ case PeerPushDebitStatus.AbortingRefreshExpired:
+ newStatus = PeerPushDebitStatus.SuspendedAbortingRefreshExpired;
break;
case PeerPushDebitStatus.AbortingDeletePurse:
newStatus = PeerPushDebitStatus.SuspendedAbortingDeletePurse;
@@ -897,7 +998,8 @@ export async function suspendPeerPushDebitTransaction(
newStatus = PeerPushDebitStatus.SuspendedReady;
break;
case PeerPushDebitStatus.SuspendedAbortingDeletePurse:
- case PeerPushDebitStatus.SuspendedAbortingRefresh:
+ case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted:
+ case PeerPushDebitStatus.SuspendedAbortingRefreshExpired:
case PeerPushDebitStatus.SuspendedReady:
case PeerPushDebitStatus.SuspendedCreatePurse:
case PeerPushDebitStatus.Done:
@@ -950,8 +1052,11 @@ export async function resumePeerPushDebitTransaction(
case PeerPushDebitStatus.SuspendedAbortingDeletePurse:
newStatus = PeerPushDebitStatus.AbortingDeletePurse;
break;
- case PeerPushDebitStatus.SuspendedAbortingRefresh:
- newStatus = PeerPushDebitStatus.AbortingRefresh;
+ case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted:
+ newStatus = PeerPushDebitStatus.AbortingRefreshDeleted;
+ break;
+ case PeerPushDebitStatus.SuspendedAbortingRefreshExpired:
+ newStatus = PeerPushDebitStatus.AbortingRefreshExpired;
break;
case PeerPushDebitStatus.SuspendedReady:
newStatus = PeerPushDebitStatus.PendingReady;
@@ -960,7 +1065,8 @@ export async function resumePeerPushDebitTransaction(
newStatus = PeerPushDebitStatus.PendingCreatePurse;
break;
case PeerPushDebitStatus.PendingCreatePurse:
- case PeerPushDebitStatus.AbortingRefresh:
+ case PeerPushDebitStatus.AbortingRefreshDeleted:
+ case PeerPushDebitStatus.AbortingRefreshExpired:
case PeerPushDebitStatus.AbortingDeletePurse:
case PeerPushDebitStatus.PendingReady:
case PeerPushDebitStatus.Done:
@@ -1011,17 +1117,27 @@ export function computePeerPushDebitTransactionState(
major: TransactionMajorState.Aborting,
minor: TransactionMinorState.DeletePurse,
};
- case PeerPushDebitStatus.AbortingRefresh:
+ case PeerPushDebitStatus.AbortingRefreshDeleted:
return {
major: TransactionMajorState.Aborting,
minor: TransactionMinorState.Refresh,
};
+ case PeerPushDebitStatus.AbortingRefreshExpired:
+ return {
+ major: TransactionMajorState.Aborting,
+ minor: TransactionMinorState.RefreshExpired,
+ };
case PeerPushDebitStatus.SuspendedAbortingDeletePurse:
return {
major: TransactionMajorState.SuspendedAborting,
minor: TransactionMinorState.DeletePurse,
};
- case PeerPushDebitStatus.SuspendedAbortingRefresh:
+ case PeerPushDebitStatus.SuspendedAbortingRefreshExpired:
+ return {
+ major: TransactionMajorState.SuspendedAborting,
+ minor: TransactionMinorState.RefreshExpired,
+ };
+ case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted:
return {
major: TransactionMajorState.SuspendedAborting,
minor: TransactionMinorState.Refresh,
diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts
index a9d6c5595..233ca3fa4 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -640,7 +640,7 @@ export async function iterRecordsForPeerPushInitiation(
if (filter.onlyState === "nonfinal") {
const keyRange = GlobalIDB.KeyRange.bound(
PeerPushDebitStatus.PendingCreatePurse,
- PeerPushDebitStatus.AbortingRefresh,
+ PeerPushDebitStatus.AbortingRefreshDeleted,
);
await tx.peerPushDebit.indexes.byStatus.iter(keyRange).forEachAsync(f);
} else {
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index c58f03e05..3bbbc2a4b 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -528,6 +528,29 @@ async function refreshMelt(
derived.meltValueWithFee,
)} failed in refresh group ${refreshGroupId} due to conflict`,
);
+
+ const historySig = await ws.cryptoApi.signCoinHistoryRequest({
+ coinPriv: oldCoin.coinPriv,
+ coinPub: oldCoin.coinPub,
+ startOffset: 0,
+ });
+
+ const historyUrl = new URL(
+ `coins/${oldCoin.coinPub}/history`,
+ oldCoin.exchangeBaseUrl,
+ );
+
+ const historyResp = await ws.http.fetch(historyUrl.href, {
+ method: "GET",
+ headers: {
+ "Taler-Coin-History-Signature": historySig.sig,
+ },
+ });
+
+ const historyJson = await historyResp.json();
+ logger.info(`coin history: ${j2s(historyJson)}`);
+
+ // FIXME: Before failing and re-trying, analyse response and adjust amount
}
const meltResponse = await readSuccessResponseJsonOrThrow(