diff options
author | Florian Dold <florian@dold.me> | 2023-12-18 19:25:26 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2023-12-18 19:25:26 +0100 |
commit | 12a9b08c6f8c31f684239a30fc39acc9189c6571 (patch) | |
tree | 3755eb1a6b7659d44059ddb4cf85c832a8f88a8e | |
parent | a488ce70d6dbfe08845eccaeb2375b367f7c307a (diff) | |
download | wallet-core-12a9b08c6f8c31f684239a30fc39acc9189c6571.tar.xz |
wallet-core: towards properly handling peer-pull-debit expiry
10 files changed, 286 insertions, 46 deletions
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index a6ed59032..27c54b8b4 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -582,7 +582,7 @@ class BankServiceBase { protected globalTestState: GlobalTestState, protected bankConfig: BankConfig, protected configFile: string, - ) { } + ) {} } export interface HarnessExchangeBankAccount { @@ -602,7 +602,8 @@ export interface HarnessExchangeBankAccount { */ export class FakebankService extends BankServiceBase - implements BankServiceHandle { + implements BankServiceHandle +{ proc: ProcessWrapper | undefined; http = createPlatformHttpLib({ enableThrottling: false }); @@ -732,7 +733,8 @@ export class FakebankService */ export class LibeufinBankService extends BankServiceBase - implements BankServiceHandle { + implements BankServiceHandle +{ proc: ProcessWrapper | undefined; http = createPlatformHttpLib({ enableThrottling: false }); @@ -928,11 +930,11 @@ export class ExchangeService implements ExchangeServiceInterface { private exchangeBankAccounts: HarnessExchangeBankAccount[] = []; - setTimetravel(t: number | undefined): void { + setTimetravel(tMs: number | undefined): void { if (this.isRunning()) { throw Error("can't set time travel while the exchange is running"); } - this.currentTimetravelOffsetMs = t; + this.currentTimetravelOffsetMs = tMs; } private get timetravelArg(): string | undefined { @@ -1184,7 +1186,7 @@ export class ExchangeService implements ExchangeServiceInterface { private exchangeConfig: ExchangeConfig, private configFilename: string, private keyPair: EddsaKeyPair, - ) { } + ) {} get name() { return this.exchangeConfig.name; @@ -1540,7 +1542,7 @@ export class MerchantService implements MerchantServiceInterface { private globalState: GlobalTestState, private merchantConfig: MerchantConfig, private configFilename: string, - ) { } + ) {} private currentTimetravelOffsetMs: number | undefined; @@ -1878,7 +1880,7 @@ export class WalletService { constructor( private globalState: GlobalTestState, private opts: WalletServiceOptions, - ) { } + ) {} get socketPath() { const unixPath = path.join( @@ -1987,7 +1989,7 @@ export class WalletClient { return client.call(operation, payload); } - constructor(private args: WalletClientArgs) { } + constructor(private args: WalletClientArgs) {} async connect(): Promise<void> { const waiter = this.waiter; @@ -2054,9 +2056,11 @@ export class WalletCli { ? `--crypto-worker=${cliOpts.cryptoWorkerType}` : ""; const logName = `wallet-${self.name}`; - const command = `taler-wallet-cli ${self.timetravelArg ?? "" - } ${cryptoWorkerArg} --no-throttle -LTRACE --skip-defaults --wallet-db '${self.dbfile - }' api '${op}' ${shellWrap(JSON.stringify(payload))}`; + const command = `taler-wallet-cli ${ + self.timetravelArg ?? "" + } ${cryptoWorkerArg} --no-throttle -LTRACE --skip-defaults --wallet-db '${ + self.dbfile + }' api '${op}' ${shellWrap(JSON.stringify(payload))}`; const resp = await sh(self.globalTestState, logName, command); logger.info("--- wallet core response ---"); logger.info(resp); diff --git a/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts b/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts index a98ea89bb..583dba28d 100644 --- a/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts +++ b/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts @@ -22,17 +22,16 @@ import { AmountString, Duration, NotificationType, - TalerUriAction, TransactionMajorState, TransactionMinorState, TransactionType, WalletNotification, j2s, - stringifyTalerUri, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { GlobalTestState } from "../harness/harness.js"; import { + applyTimeTravelV2, createSimpleTestkudosEnvironmentV2, createWalletDaemonWithClient, withdrawViaBankV2, @@ -139,7 +138,7 @@ export async function runPeerToPeerPushTest(t: GlobalTestState) { const acceptResp = await w2.walletClient.call( WalletApiOperation.ConfirmPeerPushCredit, { - peerPushCreditId: checkResp.peerPushCreditId, + transactionId: checkResp.transactionId, }, ); @@ -157,6 +156,7 @@ export async function runPeerToPeerPushTest(t: GlobalTestState) { console.log(`txn1: ${j2s(txn1)}`); console.log(`txn2: ${j2s(txn2)}`); + // We expect insufficient balance here! const ex1 = await t.assertThrowsTalerErrorAsync(async () => { await w1.walletClient.call(WalletApiOperation.InitiatePeerPushDebit, { partialContractTerms: { @@ -168,6 +168,54 @@ export async function runPeerToPeerPushTest(t: GlobalTestState) { }); console.log("got expected exception detail", j2s(ex1.errorDetail)); + + const initiateResp2 = await w1.walletClient.call( + WalletApiOperation.InitiatePeerPushDebit, + { + partialContractTerms: { + summary: "second tx, will expire", + amount: "TESTKUDOS:5" as AmountString, + purse_expiration, + }, + }, + ); + + const peerPushReadyCond2 = w1.walletClient.waitForNotificationCond( + (x) => + x.type === NotificationType.TransactionStateTransition && + x.newTxState.major === TransactionMajorState.Pending && + x.newTxState.minor === TransactionMinorState.Ready && + x.transactionId === initiateResp2.transactionId, + ); + + await peerPushReadyCond2; + + const timetravelOffsetMs = Duration.toMilliseconds( + Duration.fromSpec({ days: 5 }), + ); + + await exchange.stop(); + exchange.setTimetravel(timetravelOffsetMs); + await exchange.start(); + await exchange.pingUntilAvailable(); + + await w1.walletClient.call(WalletApiOperation.TestingSetTimetravel, { + offsetMs: timetravelOffsetMs, + }); + + await w1.walletClient.call( + WalletApiOperation.TestingWaitTransactionsFinal, + {}, + ); + + const txDetails2 = await w1.walletClient.call( + WalletApiOperation.GetTransactionById, + { + transactionId: initiateResp2.transactionId, + }, + ); + + console.log(`tx details 2: ${j2s(txDetails2)}`); } runPeerToPeerPushTest.suites = ["wallet"]; diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts index de870955b..8f8b2ceac 100644 --- a/packages/taler-util/src/taler-crypto.ts +++ b/packages/taler-util/src/taler-crypto.ts @@ -844,7 +844,8 @@ export function hashDenomPub(pub: DenominationPubKey): Uint8Array { return hash(uint8ArrayBuf); } else { throw Error( - `unsupported cipher (${(pub as DenominationPubKey).cipher + `unsupported cipher (${ + (pub as DenominationPubKey).cipher }), unable to hash`, ); } @@ -993,6 +994,7 @@ export enum TalerSignaturePurpose { WALLET_ACCOUNT_MERGE = 1214, WALLET_PURSE_ECONTRACT = 1216, WALLET_PURSE_DELETE = 1220, + WALLET_COIN_HISTORY = 1209, EXCHANGE_CONFIRM_RECOUP = 1039, EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041, TALER_SIGNATURE_AML_DECISION = 1350, @@ -1022,7 +1024,7 @@ export enum WalletAccountMergeFlags { export class SignaturePurposeBuilder { private chunks: Uint8Array[] = []; - constructor(private purposeNum: number) { } + constructor(private purposeNum: number) {} put(bytes: Uint8Array): SignaturePurposeBuilder { this.chunks.push(Uint8Array.from(bytes)); diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts index b3b197891..740478fb0 100644 --- a/packages/taler-util/src/transactions-types.ts +++ b/packages/taler-util/src/transactions-types.ts @@ -121,6 +121,7 @@ export enum TransactionMinorState { CheckRefund = "check-refund", CreatePurse = "create-purse", DeletePurse = "delete-purse", + RefreshExpired = "refresh-expired", Ready = "ready", Merge = "merge", Repurchase = "repurchase", 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( |