aboutsummaryrefslogtreecommitdiff
path: root/src/operations
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-03-12 00:44:28 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-03-12 00:44:28 +0530
commit2c52046f0bf358a5e07c53394b3b72d091356cce (patch)
tree8993c992b9c8240ee865671cdfbab380e61af96c /src/operations
parent6e2881fabf74a3c1da8e94dcbe3e68fce6080d9e (diff)
downloadwallet-core-2c52046f0bf358a5e07c53394b3b72d091356cce.tar.xz
full recoup, untested/unfinished first attempt
Diffstat (limited to 'src/operations')
-rw-r--r--src/operations/exchanges.ts43
-rw-r--r--src/operations/history.ts11
-rw-r--r--src/operations/pending.ts28
-rw-r--r--src/operations/recoup.ts372
-rw-r--r--src/operations/refresh.ts8
-rw-r--r--src/operations/reserves.ts1
-rw-r--r--src/operations/state.ts1
-rw-r--r--src/operations/withdraw.ts26
8 files changed, 431 insertions, 59 deletions
diff --git a/src/operations/exchanges.ts b/src/operations/exchanges.ts
index cf6b06868..ed13a1e5b 100644
--- a/src/operations/exchanges.ts
+++ b/src/operations/exchanges.ts
@@ -31,6 +31,7 @@ import {
WireFee,
ExchangeUpdateReason,
ExchangeUpdatedEventRecord,
+ CoinStatus,
} from "../types/dbTypes";
import { canonicalizeBaseUrl } from "../util/helpers";
import * as Amounts from "../util/amounts";
@@ -45,6 +46,7 @@ import {
} from "./versions";
import { getTimestampNow } from "../util/time";
import { compare } from "../util/libtoolVersion";
+import { createRecoupGroup, processRecoupGroup } from "./recoup";
async function denominationRecordFromKeys(
ws: InternalWalletState,
@@ -61,6 +63,7 @@ async function denominationRecordFromKeys(
feeRefund: Amounts.parseOrThrow(denomIn.fee_refund),
feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw),
isOffered: true,
+ isRevoked: false,
masterSig: denomIn.master_sig,
stampExpireDeposit: denomIn.stamp_expire_deposit,
stampExpireLegal: denomIn.stamp_expire_legal,
@@ -189,6 +192,8 @@ async function updateExchangeWithKeys(
),
);
+ let recoupGroupId: string | undefined = undefined;
+
await ws.db.runWithWriteTransaction(
[Stores.exchanges, Stores.denominations],
async tx => {
@@ -222,8 +227,46 @@ async function updateExchangeWithKeys(
await tx.put(Stores.denominations, newDenom);
}
}
+
+ // Handle recoup
+ const recoupDenomList = exchangeKeysJson.recoup ?? [];
+ const newlyRevokedCoinPubs: string[] = [];
+ for (const recoupDenomPubHash of recoupDenomList) {
+ const oldDenom = await tx.getIndexed(
+ Stores.denominations.denomPubHashIndex,
+ recoupDenomPubHash,
+ );
+ if (!oldDenom) {
+ // We never even knew about the revoked denomination, all good.
+ continue;
+ }
+ if (oldDenom.isRevoked) {
+ // We already marked the denomination as revoked,
+ // this implies we revoked all coins
+ continue;
+ }
+ oldDenom.isRevoked = true;
+ await tx.put(Stores.denominations, oldDenom);
+ const affectedCoins = await tx
+ .iterIndexed(Stores.coins.denomPubIndex)
+ .toArray();
+ for (const ac of affectedCoins) {
+ newlyRevokedCoinPubs.push(ac.coinPub);
+ }
+ }
+ if (newlyRevokedCoinPubs.length != 0) {
+ await createRecoupGroup(ws, tx, newlyRevokedCoinPubs);
+ }
},
);
+
+ if (recoupGroupId) {
+ // Asynchronously start recoup. This doesn't need to finish
+ // for the exchange update to be considered finished.
+ processRecoupGroup(ws, recoupGroupId).catch((e) => {
+ console.log("error while recouping coins:", e);
+ });
+ }
}
async function updateExchangeFinalize(
diff --git a/src/operations/history.ts b/src/operations/history.ts
index 2fb7854d2..2cf215a5a 100644
--- a/src/operations/history.ts
+++ b/src/operations/history.ts
@@ -181,6 +181,7 @@ export async function getHistory(
Stores.payEvents,
Stores.refundEvents,
Stores.reserveUpdatedEvents,
+ Stores.recoupGroups,
],
async tx => {
tx.iter(Stores.exchanges).forEach(exchange => {
@@ -485,6 +486,16 @@ export async function getHistory(
amountRefundedInvalid: Amounts.toString(amountRefundedInvalid),
});
});
+
+ tx.iter(Stores.recoupGroups).forEach(rg => {
+ if (rg.timestampFinished) {
+ history.push({
+ type: HistoryEventType.FundsRecouped,
+ timestamp: rg.timestampFinished,
+ eventId: makeEventId(HistoryEventType.FundsRecouped, rg.recoupGroupId),
+ });
+ }
+ });
},
);
diff --git a/src/operations/pending.ts b/src/operations/pending.ts
index fce9a3bfb..08ec3fc9e 100644
--- a/src/operations/pending.ts
+++ b/src/operations/pending.ts
@@ -405,6 +405,32 @@ async function gatherPurchasePending(
});
}
+async function gatherRecoupPending(
+ tx: TransactionHandle,
+ now: Timestamp,
+ resp: PendingOperationsResponse,
+ onlyDue: boolean = false,
+): Promise<void> {
+ await tx.iter(Stores.recoupGroups).forEach(rg => {
+ if (rg.timestampFinished) {
+ return;
+ }
+ resp.nextRetryDelay = updateRetryDelay(
+ resp.nextRetryDelay,
+ now,
+ rg.retryInfo.nextRetry,
+ );
+ if (onlyDue && rg.retryInfo.nextRetry.t_ms > now.t_ms) {
+ return;
+ }
+ resp.pendingOperations.push({
+ type: PendingOperationType.Recoup,
+ givesLifeness: true,
+ recoupGroupId: rg.recoupGroupId,
+ });
+ });
+}
+
export async function getPendingOperations(
ws: InternalWalletState,
{ onlyDue = false } = {},
@@ -420,6 +446,7 @@ export async function getPendingOperations(
Stores.proposals,
Stores.tips,
Stores.purchases,
+ Stores.recoupGroups,
],
async tx => {
const walletBalance = await getBalancesInsideTransaction(ws, tx);
@@ -436,6 +463,7 @@ export async function getPendingOperations(
await gatherProposalPending(tx, now, resp, onlyDue);
await gatherTipPending(tx, now, resp, onlyDue);
await gatherPurchasePending(tx, now, resp, onlyDue);
+ await gatherRecoupPending(tx, now, resp, onlyDue);
return resp;
},
);
diff --git a/src/operations/recoup.ts b/src/operations/recoup.ts
index 2b646a4d8..842a67b87 100644
--- a/src/operations/recoup.ts
+++ b/src/operations/recoup.ts
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
+ (C) 2019-2010 Taler Systems SA
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -15,75 +15,357 @@
*/
/**
+ * Implementation of the recoup operation, which allows to recover the
+ * value of coins held in a revoked denomination.
+ *
+ * @author Florian Dold <dold@taler.net>
+ */
+
+/**
* Imports.
*/
-import {
- Database
-} from "../util/query";
import { InternalWalletState } from "./state";
-import { Stores, TipRecord, CoinStatus } from "../types/dbTypes";
+import {
+ Stores,
+ CoinStatus,
+ CoinSourceType,
+ CoinRecord,
+ WithdrawCoinSource,
+ RefreshCoinSource,
+ ReserveRecordStatus,
+ RecoupGroupRecord,
+ initRetryInfo,
+ updateRetryInfoTimeout,
+} from "../types/dbTypes";
-import { Logger } from "../util/logging";
-import { RecoupConfirmation, codecForRecoupConfirmation } from "../types/talerTypes";
-import { updateExchangeFromUrl } from "./exchanges";
+import { codecForRecoupConfirmation } from "../types/talerTypes";
import { NotificationType } from "../types/notifications";
+import { processReserve } from "./reserves";
-const logger = new Logger("payback.ts");
+import * as Amounts from "../util/amounts";
+import { createRefreshGroup, processRefreshGroup } from "./refresh";
+import { RefreshReason, OperationError } from "../types/walletTypes";
+import { TransactionHandle } from "../util/query";
+import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto";
+import { getTimestampNow } from "../util/time";
+import { guardOperationException } from "./errors";
-export async function recoup(
+async function incrementRecoupRetry(
ws: InternalWalletState,
- coinPub: string,
+ recoupGroupId: string,
+ err: OperationError | undefined,
): Promise<void> {
- let coin = await ws.db.get(Stores.coins, coinPub);
- if (!coin) {
- throw Error(`Coin ${coinPub} not found, can't request payback`);
+ await ws.db.runWithWriteTransaction([Stores.recoupGroups], async tx => {
+ const r = await tx.get(Stores.recoupGroups, recoupGroupId);
+ if (!r) {
+ return;
+ }
+ if (!r.retryInfo) {
+ return;
+ }
+ r.retryInfo.retryCounter++;
+ updateRetryInfoTimeout(r.retryInfo);
+ r.lastError = err;
+ await tx.put(Stores.recoupGroups, r);
+ });
+ ws.notify({ type: NotificationType.RecoupOperationError });
+}
+
+async function putGroupAsFinished(
+ tx: TransactionHandle,
+ recoupGroup: RecoupGroupRecord,
+ coinIdx: number,
+): Promise<void> {
+ recoupGroup.recoupFinishedPerCoin[coinIdx] = true;
+ let allFinished = true;
+ for (const b of recoupGroup.recoupFinishedPerCoin) {
+ if (!b) {
+ allFinished = false;
+ }
}
- const reservePub = coin.reservePub;
- if (!reservePub) {
- throw Error(`Can't request payback for a refreshed coin`);
+ if (allFinished) {
+ recoupGroup.timestampFinished = getTimestampNow();
+ recoupGroup.retryInfo = initRetryInfo(false);
+ recoupGroup.lastError = undefined;
}
+ await tx.put(Stores.recoupGroups, recoupGroup);
+}
+
+async function recoupTipCoin(
+ ws: InternalWalletState,
+ recoupGroupId: string,
+ coinIdx: number,
+ coin: CoinRecord,
+): Promise<void> {
+ // We can't really recoup a coin we got via tipping.
+ // Thus we just put the coin to sleep.
+ // FIXME: somehow report this to the user
+ await ws.db.runWithWriteTransaction([Stores.recoupGroups], async tx => {
+ const recoupGroup = await tx.get(Stores.recoupGroups, recoupGroupId);
+ if (!recoupGroup) {
+ return;
+ }
+ if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+ return;
+ }
+ await putGroupAsFinished(tx, recoupGroup, coinIdx);
+ });
+}
+
+async function recoupWithdrawCoin(
+ ws: InternalWalletState,
+ recoupGroupId: string,
+ coinIdx: number,
+ coin: CoinRecord,
+ cs: WithdrawCoinSource,
+): Promise<void> {
+ const reservePub = cs.reservePub;
const reserve = await ws.db.get(Stores.reserves, reservePub);
if (!reserve) {
- throw Error(`Reserve of coin ${coinPub} not found`);
+ // FIXME: We should at least emit some pending operation / warning for this?
+ return;
+ }
+
+ ws.notify({
+ type: NotificationType.RecoupStarted,
+ });
+
+ const recoupRequest = await ws.cryptoApi.createRecoupRequest(coin);
+ const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl);
+ const resp = await ws.http.postJson(reqUrl.href, recoupRequest);
+ if (resp.status !== 200) {
+ throw Error("recoup request failed");
}
- switch (coin.status) {
- case CoinStatus.Dormant:
- throw Error(`Can't do payback for coin ${coinPub} since it's dormant`);
+ const recoupConfirmation = codecForRecoupConfirmation().decode(
+ await resp.json(),
+ );
+
+ if (recoupConfirmation.reserve_pub !== reservePub) {
+ throw Error(`Coin's reserve doesn't match reserve on recoup`);
}
- coin.status = CoinStatus.Dormant;
- // Even if we didn't get the payback yet, we suspend withdrawal, since
- // technically we might update reserve status before we get the response
- // from the reserve for the payback request.
- reserve.hasPayback = true;
+
+ // FIXME: verify that our expectations about the amount match
+
await ws.db.runWithWriteTransaction(
- [Stores.coins, Stores.reserves],
+ [Stores.coins, Stores.reserves, Stores.recoupGroups],
async tx => {
- await tx.put(Stores.coins, coin!!);
- await tx.put(Stores.reserves, reserve);
+ const recoupGroup = await tx.get(Stores.recoupGroups, recoupGroupId);
+ if (!recoupGroup) {
+ return;
+ }
+ if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+ return;
+ }
+ const updatedCoin = await tx.get(Stores.coins, coin.coinPub);
+ if (!updatedCoin) {
+ return;
+ }
+ const updatedReserve = await tx.get(Stores.reserves, reserve.reservePub);
+ if (!updatedReserve) {
+ return;
+ }
+ updatedCoin.status = CoinStatus.Dormant;
+ const currency = updatedCoin.currentAmount.currency;
+ updatedCoin.currentAmount = Amounts.getZero(currency);
+ updatedReserve.reserveStatus = ReserveRecordStatus.QUERYING_STATUS;
+ await tx.put(Stores.coins, updatedCoin);
+ await tx.put(Stores.reserves, updatedReserve);
+ await putGroupAsFinished(tx, recoupGroup, coinIdx);
},
);
+
ws.notify({
- type: NotificationType.PaybackStarted,
+ type: NotificationType.RecoupFinished,
});
- const paybackRequest = await ws.cryptoApi.createPaybackRequest(coin);
- const reqUrl = new URL("payback", coin.exchangeBaseUrl);
- const resp = await ws.http.postJson(reqUrl.href, paybackRequest);
+ processReserve(ws, reserve.reservePub).catch(e => {
+ console.log("processing reserve after recoup failed:", e);
+ });
+}
+
+async function recoupRefreshCoin(
+ ws: InternalWalletState,
+ recoupGroupId: string,
+ coinIdx: number,
+ coin: CoinRecord,
+ cs: RefreshCoinSource,
+): Promise<void> {
+ ws.notify({
+ type: NotificationType.RecoupStarted,
+ });
+
+ const recoupRequest = await ws.cryptoApi.createRecoupRequest(coin);
+ const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl);
+ const resp = await ws.http.postJson(reqUrl.href, recoupRequest);
if (resp.status !== 200) {
- throw Error();
+ throw Error("recoup request failed");
}
- const paybackConfirmation = codecForRecoupConfirmation().decode(await resp.json());
- if (paybackConfirmation.reserve_pub !== coin.reservePub) {
- throw Error(`Coin's reserve doesn't match reserve on payback`);
+ const recoupConfirmation = codecForRecoupConfirmation().decode(
+ await resp.json(),
+ );
+
+ if (recoupConfirmation.old_coin_pub != cs.oldCoinPub) {
+ throw Error(`Coin's oldCoinPub doesn't match reserve on recoup`);
}
- coin = await ws.db.get(Stores.coins, coinPub);
- if (!coin) {
- throw Error(`Coin ${coinPub} not found, can't confirm payback`);
+
+ const refreshGroupId = await ws.db.runWithWriteTransaction(
+ [Stores.coins, Stores.reserves],
+ async tx => {
+ const recoupGroup = await tx.get(Stores.recoupGroups, recoupGroupId);
+ if (!recoupGroup) {
+ return;
+ }
+ if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+ return;
+ }
+ const oldCoin = await tx.get(Stores.coins, cs.oldCoinPub);
+ const updatedCoin = await tx.get(Stores.coins, coin.coinPub);
+ if (!updatedCoin) {
+ return;
+ }
+ if (!oldCoin) {
+ return;
+ }
+ updatedCoin.status = CoinStatus.Dormant;
+ oldCoin.currentAmount = Amounts.add(
+ oldCoin.currentAmount,
+ updatedCoin.currentAmount,
+ ).amount;
+ await tx.put(Stores.coins, updatedCoin);
+ await putGroupAsFinished(tx, recoupGroup, coinIdx);
+ return await createRefreshGroup(
+ tx,
+ [{ coinPub: oldCoin.coinPub }],
+ RefreshReason.Recoup,
+ );
+ },
+ );
+
+ if (refreshGroupId) {
+ processRefreshGroup(ws, refreshGroupId.refreshGroupId).then(e => {
+ console.error("error while refreshing after recoup", e);
+ });
}
- coin.status = CoinStatus.Dormant;
- await ws.db.put(Stores.coins, coin);
- ws.notify({
- type: NotificationType.PaybackFinished,
+}
+
+async function resetRecoupGroupRetry(
+ ws: InternalWalletState,
+ recoupGroupId: string,
+) {
+ await ws.db.mutate(Stores.recoupGroups, recoupGroupId, x => {
+ if (x.retryInfo.active) {
+ x.retryInfo = initRetryInfo();
+ }
+ return x;
});
- await updateExchangeFromUrl(ws, coin.exchangeBaseUrl, true);
+}
+
+export async function processRecoupGroup(
+ ws: InternalWalletState,
+ recoupGroupId: string,
+ forceNow: boolean = false,
+): Promise<void> {
+ await ws.memoProcessRecoup.memo(recoupGroupId, async () => {
+ const onOpErr = (e: OperationError) =>
+ incrementRecoupRetry(ws, recoupGroupId, e);
+ return await guardOperationException(
+ async () => await processRecoupGroupImpl(ws, recoupGroupId, forceNow),
+ onOpErr,
+ );
+ });
+}
+
+async function processRecoupGroupImpl(
+ ws: InternalWalletState,
+ recoupGroupId: string,
+ forceNow: boolean = false,
+): Promise<void> {
+ if (forceNow) {
+ await resetRecoupGroupRetry(ws, recoupGroupId);
+ }
+ const recoupGroup = await ws.db.get(Stores.recoupGroups, recoupGroupId);
+ if (!recoupGroup) {
+ return;
+ }
+ if (recoupGroup.timestampFinished) {
+ return;
+ }
+ const ps = recoupGroup.coinPubs.map((x, i) =>
+ processRecoup(ws, recoupGroupId, i),
+ );
+ await Promise.all(ps);
+}
+
+export async function createRecoupGroup(
+ ws: InternalWalletState,
+ tx: TransactionHandle,
+ coinPubs: string[],
+): Promise<string> {
+ const recoupGroupId = encodeCrock(getRandomBytes(32));
+
+ const recoupGroup: RecoupGroupRecord = {
+ recoupGroupId,
+ coinPubs: coinPubs,
+ lastError: undefined,
+ timestampFinished: undefined,
+ timestampStarted: getTimestampNow(),
+ retryInfo: initRetryInfo(),
+ recoupFinishedPerCoin: coinPubs.map(() => false),
+ };
+
+ for (let coinIdx = 0; coinIdx < coinPubs.length; coinIdx++) {
+ const coinPub = coinPubs[coinIdx];
+ const coin = await tx.get(Stores.coins, coinPub);
+ if (!coin) {
+ recoupGroup.recoupFinishedPerCoin[coinIdx] = true;
+ continue;
+ }
+ if (Amounts.isZero(coin.currentAmount)) {
+ recoupGroup.recoupFinishedPerCoin[coinIdx] = true;
+ continue;
+ }
+ coin.currentAmount = Amounts.getZero(coin.currentAmount.currency);
+ await tx.put(Stores.coins, coin);
+ }
+
+ await tx.put(Stores.recoupGroups, recoupGroup);
+
+ return recoupGroupId;
+}
+
+async function processRecoup(
+ ws: InternalWalletState,
+ recoupGroupId: string,
+ coinIdx: number,
+): Promise<void> {
+ const recoupGroup = await ws.db.get(Stores.recoupGroups, recoupGroupId);
+ if (!recoupGroup) {
+ return;
+ }
+ if (recoupGroup.timestampFinished) {
+ return;
+ }
+ if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+ return;
+ }
+
+ const coinPub = recoupGroup.coinPubs[coinIdx];
+
+ let coin = await ws.db.get(Stores.coins, coinPub);
+ if (!coin) {
+ throw Error(`Coin ${coinPub} not found, can't request payback`);
+ }
+
+ const cs = coin.coinSource;
+
+ switch (cs.type) {
+ case CoinSourceType.Tip:
+ return recoupTipCoin(ws, recoupGroupId, coinIdx, coin);
+ case CoinSourceType.Refresh:
+ return recoupRefreshCoin(ws, recoupGroupId, coinIdx, coin, cs);
+ case CoinSourceType.Withdraw:
+ return recoupWithdrawCoin(ws, recoupGroupId, coinIdx, coin, cs);
+ default:
+ throw Error("unknown coin source type");
+ }
}
diff --git a/src/operations/refresh.ts b/src/operations/refresh.ts
index 6dd16d61a..092d9f154 100644
--- a/src/operations/refresh.ts
+++ b/src/operations/refresh.ts
@@ -26,6 +26,7 @@ import {
initRetryInfo,
updateRetryInfoTimeout,
RefreshGroupRecord,
+ CoinSourceType,
} from "../types/dbTypes";
import { amountToPretty } from "../util/helpers";
import { Database, TransactionHandle } from "../util/query";
@@ -407,10 +408,11 @@ async function refreshReveal(
denomPubHash: denom.denomPubHash,
denomSig,
exchangeBaseUrl: refreshSession.exchangeBaseUrl,
- reservePub: undefined,
status: CoinStatus.Fresh,
- coinIndex: -1,
- withdrawSessionId: "",
+ coinSource: {
+ type: CoinSourceType.Refresh,
+ oldCoinPub: refreshSession.meltCoinPub,
+ }
};
coins.push(coin);
diff --git a/src/operations/reserves.ts b/src/operations/reserves.ts
index 1f9cc3053..c909555fe 100644
--- a/src/operations/reserves.ts
+++ b/src/operations/reserves.ts
@@ -103,7 +103,6 @@ export async function createReserve(
amountWithdrawCompleted: Amounts.getZero(currency),
amountWithdrawRemaining: Amounts.getZero(currency),
exchangeBaseUrl: canonExchange,
- hasPayback: false,
amountInitiallyRequested: req.amount,
reservePriv: keypair.priv,
reservePub: keypair.pub,
diff --git a/src/operations/state.ts b/src/operations/state.ts
index 3e4936c98..ae32db2b3 100644
--- a/src/operations/state.ts
+++ b/src/operations/state.ts
@@ -39,6 +39,7 @@ export class InternalWalletState {
> = new AsyncOpMemoSingle();
memoGetBalance: AsyncOpMemoSingle<WalletBalance> = new AsyncOpMemoSingle();
memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
+ memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
cryptoApi: CryptoApi;
listeners: NotificationListener[] = [];
diff --git a/src/operations/withdraw.ts b/src/operations/withdraw.ts
index 0c58f5f2f..478aa4ceb 100644
--- a/src/operations/withdraw.ts
+++ b/src/operations/withdraw.ts
@@ -24,6 +24,7 @@ import {
PlanchetRecord,
initRetryInfo,
updateRetryInfoTimeout,
+ CoinSourceType,
} from "../types/dbTypes";
import * as Amounts from "../util/amounts";
import {
@@ -48,6 +49,7 @@ import {
timestampCmp,
timestampSubtractDuraction,
} from "../util/time";
+import { Store } from "../util/query";
const logger = new Logger("withdraw.ts");
@@ -229,10 +231,13 @@ async function processPlanchet(
denomPubHash: planchet.denomPubHash,
denomSig,
exchangeBaseUrl: withdrawalSession.exchangeBaseUrl,
- reservePub: planchet.reservePub,
status: CoinStatus.Fresh,
- coinIndex: coinIdx,
- withdrawSessionId: withdrawalSessionId,
+ coinSource: {
+ type: CoinSourceType.Withdraw,
+ coinIndex: coinIdx,
+ reservePub: planchet.reservePub,
+ withdrawSessionId: withdrawalSessionId
+ }
};
let withdrawSessionFinished = false;
@@ -449,14 +454,15 @@ async function processWithdrawCoin(
return;
}
- const coin = await ws.db.getIndexed(Stores.coins.byWithdrawalWithIdx, [
- withdrawalSessionId,
- coinIndex,
- ]);
+ const planchet = withdrawalSession.planchets[coinIndex];
- if (coin) {
- console.log("coin already exists");
- return;
+ if (planchet) {
+ const coin = await ws.db.get(Stores.coins, planchet.coinPub);
+
+ if (coin) {
+ console.log("coin already exists");
+ return;
+ }
}
if (!withdrawalSession.planchets[coinIndex]) {