fix tipping and adjust DB
This commit is contained in:
parent
b063382d25
commit
b9e43e652e
@ -94,13 +94,19 @@ runTest(async (t: GlobalTestState) => {
|
|||||||
|
|
||||||
console.log(ptr);
|
console.log(ptr);
|
||||||
|
|
||||||
|
t.assertAmountEquals(ptr.tipAmountRaw, "TESTKUDOS:5");
|
||||||
|
t.assertAmountEquals(ptr.tipAmountEffective, "TESTKUDOS:4.85");
|
||||||
|
|
||||||
await wallet.acceptTip({
|
await wallet.acceptTip({
|
||||||
walletTipId: ptr.walletTipId,
|
walletTipId: ptr.walletTipId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
await wallet.runUntilDone();
|
await wallet.runUntilDone();
|
||||||
|
|
||||||
const bal = await wallet.getBalances();
|
const bal = await wallet.getBalances();
|
||||||
|
|
||||||
console.log(bal);
|
console.log(bal);
|
||||||
|
|
||||||
|
t.assertAmountEquals(bal.balances[0].available, "TESTKUDOS:4.85");
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export enum TalerErrorCode {
|
export enum TalerErrorCode {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special code to indicate no error (or no "code" present).
|
* Special code to indicate no error (or no "code" present).
|
||||||
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
||||||
@ -3270,10 +3272,18 @@ export enum TalerErrorCode {
|
|||||||
*/
|
*/
|
||||||
WALLET_WITHDRAWAL_GROUP_INCOMPLETE = 7015,
|
WALLET_WITHDRAWAL_GROUP_INCOMPLETE = 7015,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The signature on a coin by the exchange's denomination key (obtained through the merchant via tipping) is invalid after unblinding it.
|
||||||
|
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
||||||
|
* (A value of 0 indicates that the error is generated client-side).
|
||||||
|
*/
|
||||||
|
WALLET_TIPPING_COIN_SIGNATURE_INVALID = 7016,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* End of error code range.
|
* End of error code range.
|
||||||
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
||||||
* (A value of 0 indicates that the error is generated client-side).
|
* (A value of 0 indicates that the error is generated client-side).
|
||||||
*/
|
*/
|
||||||
END = 9999,
|
END = 9999,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,6 @@ async function gatherWithdrawalPending(
|
|||||||
givesLifeness: true,
|
givesLifeness: true,
|
||||||
numCoinsTotal,
|
numCoinsTotal,
|
||||||
numCoinsWithdrawn,
|
numCoinsWithdrawn,
|
||||||
source: wsr.source,
|
|
||||||
withdrawalGroupId: wsr.withdrawalGroupId,
|
withdrawalGroupId: wsr.withdrawalGroupId,
|
||||||
lastError: wsr.lastError,
|
lastError: wsr.lastError,
|
||||||
retryInfo: wsr.retryInfo,
|
retryInfo: wsr.retryInfo,
|
||||||
|
@ -818,10 +818,7 @@ async function depleteReserve(
|
|||||||
const withdrawalRecord: WithdrawalGroupRecord = {
|
const withdrawalRecord: WithdrawalGroupRecord = {
|
||||||
withdrawalGroupId: withdrawalGroupId,
|
withdrawalGroupId: withdrawalGroupId,
|
||||||
exchangeBaseUrl: newReserve.exchangeBaseUrl,
|
exchangeBaseUrl: newReserve.exchangeBaseUrl,
|
||||||
source: {
|
reservePub: newReserve.reservePub,
|
||||||
type: WithdrawalSourceType.Reserve,
|
|
||||||
reservePub: newReserve.reservePub,
|
|
||||||
},
|
|
||||||
rawWithdrawalAmount: withdrawAmount,
|
rawWithdrawalAmount: withdrawAmount,
|
||||||
timestampStart: getTimestampNow(),
|
timestampStart: getTimestampNow(),
|
||||||
retryInfo: initRetryInfo(),
|
retryInfo: initRetryInfo(),
|
||||||
|
@ -31,6 +31,9 @@ import {
|
|||||||
updateRetryInfoTimeout,
|
updateRetryInfoTimeout,
|
||||||
WithdrawalSourceType,
|
WithdrawalSourceType,
|
||||||
TipPlanchet,
|
TipPlanchet,
|
||||||
|
CoinRecord,
|
||||||
|
CoinSourceType,
|
||||||
|
CoinStatus,
|
||||||
} from "../types/dbTypes";
|
} from "../types/dbTypes";
|
||||||
import {
|
import {
|
||||||
getExchangeWithdrawalInfo,
|
getExchangeWithdrawalInfo,
|
||||||
@ -40,13 +43,14 @@ import {
|
|||||||
} from "./withdraw";
|
} from "./withdraw";
|
||||||
import { updateExchangeFromUrl } from "./exchanges";
|
import { updateExchangeFromUrl } from "./exchanges";
|
||||||
import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto";
|
import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto";
|
||||||
import { guardOperationException } from "./errors";
|
import { guardOperationException, makeErrorDetails } from "./errors";
|
||||||
import { NotificationType } from "../types/notifications";
|
import { NotificationType } from "../types/notifications";
|
||||||
import { getTimestampNow } from "../util/time";
|
import { getTimestampNow } from "../util/time";
|
||||||
import { readSuccessResponseJsonOrThrow } from "../util/http";
|
import { readSuccessResponseJsonOrThrow } from "../util/http";
|
||||||
import { URL } from "../util/url";
|
import { URL } from "../util/url";
|
||||||
import { Logger } from "../util/logging";
|
import { Logger } from "../util/logging";
|
||||||
import { checkDbInvariant } from "../util/invariants";
|
import { checkDbInvariant } from "../util/invariants";
|
||||||
|
import { TalerErrorCode } from "../TalerErrorCode";
|
||||||
|
|
||||||
const logger = new Logger("operations/tip.ts");
|
const logger = new Logger("operations/tip.ts");
|
||||||
|
|
||||||
@ -99,7 +103,7 @@ export async function prepareTip(
|
|||||||
walletTipId: walletTipId,
|
walletTipId: walletTipId,
|
||||||
acceptedTimestamp: undefined,
|
acceptedTimestamp: undefined,
|
||||||
rejectedTimestamp: undefined,
|
rejectedTimestamp: undefined,
|
||||||
amount,
|
tipAmountRaw: amount,
|
||||||
deadline: tipPickupStatus.expiration,
|
deadline: tipPickupStatus.expiration,
|
||||||
exchangeUrl: tipPickupStatus.exchange_url,
|
exchangeUrl: tipPickupStatus.exchange_url,
|
||||||
merchantBaseUrl: res.merchantBaseUrl,
|
merchantBaseUrl: res.merchantBaseUrl,
|
||||||
@ -109,10 +113,10 @@ export async function prepareTip(
|
|||||||
response: undefined,
|
response: undefined,
|
||||||
createdTimestamp: getTimestampNow(),
|
createdTimestamp: getTimestampNow(),
|
||||||
merchantTipId: res.merchantTipId,
|
merchantTipId: res.merchantTipId,
|
||||||
totalFees: Amounts.add(
|
tipAmountEffective: Amounts.sub(amount, Amounts.add(
|
||||||
withdrawDetails.overhead,
|
withdrawDetails.overhead,
|
||||||
withdrawDetails.withdrawFee,
|
withdrawDetails.withdrawFee,
|
||||||
).amount,
|
).amount).amount,
|
||||||
retryInfo: initRetryInfo(),
|
retryInfo: initRetryInfo(),
|
||||||
lastError: undefined,
|
lastError: undefined,
|
||||||
denomsSel: denomSelectionInfoToState(selectedDenoms),
|
denomsSel: denomSelectionInfoToState(selectedDenoms),
|
||||||
@ -122,10 +126,10 @@ export async function prepareTip(
|
|||||||
|
|
||||||
const tipStatus: PrepareTipResult = {
|
const tipStatus: PrepareTipResult = {
|
||||||
accepted: !!tipRecord && !!tipRecord.acceptedTimestamp,
|
accepted: !!tipRecord && !!tipRecord.acceptedTimestamp,
|
||||||
amount: Amounts.stringify(tipPickupStatus.tip_amount),
|
tipAmountRaw: Amounts.stringify(tipPickupStatus.tip_amount),
|
||||||
exchangeBaseUrl: tipPickupStatus.exchange_url,
|
exchangeBaseUrl: tipPickupStatus.exchange_url,
|
||||||
expirationTimestamp: tipPickupStatus.expiration,
|
expirationTimestamp: tipPickupStatus.expiration,
|
||||||
totalFees: Amounts.stringify(tipRecord.totalFees),
|
tipAmountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
|
||||||
walletTipId: tipRecord.walletTipId,
|
walletTipId: tipRecord.walletTipId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -182,13 +186,13 @@ async function resetTipRetry(
|
|||||||
|
|
||||||
async function processTipImpl(
|
async function processTipImpl(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
tipId: string,
|
walletTipId: string,
|
||||||
forceNow: boolean,
|
forceNow: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (forceNow) {
|
if (forceNow) {
|
||||||
await resetTipRetry(ws, tipId);
|
await resetTipRetry(ws, walletTipId);
|
||||||
}
|
}
|
||||||
let tipRecord = await ws.db.get(Stores.tips, tipId);
|
let tipRecord = await ws.db.get(Stores.tips, walletTipId);
|
||||||
if (!tipRecord) {
|
if (!tipRecord) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -216,7 +220,7 @@ async function processTipImpl(
|
|||||||
planchets.push(r);
|
planchets.push(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await ws.db.mutate(Stores.tips, tipId, (r) => {
|
await ws.db.mutate(Stores.tips, walletTipId, (r) => {
|
||||||
if (!r.planchets) {
|
if (!r.planchets) {
|
||||||
r.planchets = planchets;
|
r.planchets = planchets;
|
||||||
}
|
}
|
||||||
@ -224,7 +228,7 @@ async function processTipImpl(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tipRecord = await ws.db.get(Stores.tips, tipId);
|
tipRecord = await ws.db.get(Stores.tips, walletTipId);
|
||||||
checkDbInvariant(!!tipRecord, "tip record should be in database");
|
checkDbInvariant(!!tipRecord, "tip record should be in database");
|
||||||
checkDbInvariant(!!tipRecord.planchets, "tip record should have planchets");
|
checkDbInvariant(!!tipRecord.planchets, "tip record should have planchets");
|
||||||
|
|
||||||
@ -246,55 +250,68 @@ async function processTipImpl(
|
|||||||
codecForTipResponse(),
|
codecForTipResponse(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.reserve_sigs.length !== tipRecord.planchets.length) {
|
if (response.blind_sigs.length !== tipRecord.planchets.length) {
|
||||||
throw Error("number of tip responses does not match requested planchets");
|
throw Error("number of tip responses does not match requested planchets");
|
||||||
}
|
}
|
||||||
|
|
||||||
const withdrawalGroupId = encodeCrock(getRandomBytes(32));
|
const newCoinRecords: CoinRecord[] = [];
|
||||||
const planchets: PlanchetRecord[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < tipRecord.planchets.length; i++) {
|
for (let i = 0; i < response.blind_sigs.length; i++) {
|
||||||
const tipPlanchet = tipRecord.planchets[i];
|
const blindedSig = response.blind_sigs[i].blind_sig;
|
||||||
const coinEvHash = await ws.cryptoApi.hashEncoded(tipPlanchet.coinEv);
|
|
||||||
const planchet: PlanchetRecord = {
|
const planchet = tipRecord.planchets[i];
|
||||||
blindingKey: tipPlanchet.blindingKey,
|
|
||||||
coinEv: tipPlanchet.coinEv,
|
const denomSig = await ws.cryptoApi.rsaUnblind(
|
||||||
coinPriv: tipPlanchet.coinPriv,
|
blindedSig,
|
||||||
coinPub: tipPlanchet.coinPub,
|
planchet.blindingKey,
|
||||||
coinValue: tipPlanchet.coinValue,
|
planchet.denomPub,
|
||||||
denomPub: tipPlanchet.denomPub,
|
);
|
||||||
denomPubHash: tipPlanchet.denomPubHash,
|
|
||||||
reservePub: response.reserve_pub,
|
const isValid = await ws.cryptoApi.rsaVerify(
|
||||||
withdrawSig: response.reserve_sigs[i].reserve_sig,
|
planchet.coinPub,
|
||||||
isFromTip: true,
|
denomSig,
|
||||||
coinEvHash,
|
planchet.denomPub,
|
||||||
coinIdx: i,
|
);
|
||||||
withdrawalDone: false,
|
|
||||||
withdrawalGroupId: withdrawalGroupId,
|
if (!isValid) {
|
||||||
lastError: undefined,
|
await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => {
|
||||||
};
|
const tipRecord = await tx.get(Stores.tips, walletTipId);
|
||||||
planchets.push(planchet);
|
if (!tipRecord) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tipRecord.lastError = makeErrorDetails(
|
||||||
|
TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID,
|
||||||
|
"invalid signature from the exchange (via merchant tip) after unblinding",
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
await tx.put(Stores.tips, tipRecord);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newCoinRecords.push({
|
||||||
|
blindingKey: planchet.blindingKey,
|
||||||
|
coinPriv: planchet.coinPriv,
|
||||||
|
coinPub: planchet.coinPub,
|
||||||
|
coinSource: {
|
||||||
|
type: CoinSourceType.Tip,
|
||||||
|
coinIndex: i,
|
||||||
|
walletTipId: walletTipId,
|
||||||
|
},
|
||||||
|
currentAmount: planchet.coinValue,
|
||||||
|
denomPub: planchet.denomPub,
|
||||||
|
denomPubHash: planchet.denomPubHash,
|
||||||
|
denomSig: denomSig,
|
||||||
|
exchangeBaseUrl: tipRecord.exchangeUrl,
|
||||||
|
status: CoinStatus.Fresh,
|
||||||
|
suspended: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const withdrawalGroup: WithdrawalGroupRecord = {
|
|
||||||
exchangeBaseUrl: tipRecord.exchangeUrl,
|
|
||||||
source: {
|
|
||||||
type: WithdrawalSourceType.Tip,
|
|
||||||
tipId: tipRecord.walletTipId,
|
|
||||||
},
|
|
||||||
timestampStart: getTimestampNow(),
|
|
||||||
withdrawalGroupId: withdrawalGroupId,
|
|
||||||
rawWithdrawalAmount: tipRecord.amount,
|
|
||||||
retryInfo: initRetryInfo(),
|
|
||||||
timestampFinish: undefined,
|
|
||||||
lastError: undefined,
|
|
||||||
denomsSel: tipRecord.denomsSel,
|
|
||||||
};
|
|
||||||
|
|
||||||
await ws.db.runWithWriteTransaction(
|
await ws.db.runWithWriteTransaction(
|
||||||
[Stores.tips, Stores.withdrawalGroups],
|
[Stores.coins, Stores.tips, Stores.withdrawalGroups],
|
||||||
async (tx) => {
|
async (tx) => {
|
||||||
const tr = await tx.get(Stores.tips, tipId);
|
const tr = await tx.get(Stores.tips, walletTipId);
|
||||||
if (!tr) {
|
if (!tr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -303,16 +320,12 @@ async function processTipImpl(
|
|||||||
}
|
}
|
||||||
tr.pickedUp = true;
|
tr.pickedUp = true;
|
||||||
tr.retryInfo = initRetryInfo(false);
|
tr.retryInfo = initRetryInfo(false);
|
||||||
|
|
||||||
await tx.put(Stores.tips, tr);
|
await tx.put(Stores.tips, tr);
|
||||||
await tx.put(Stores.withdrawalGroups, withdrawalGroup);
|
for (const cr of newCoinRecords) {
|
||||||
for (const p of planchets) {
|
await tx.put(Stores.coins, cr);
|
||||||
await tx.put(Stores.planchets, p);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await processWithdrawGroup(ws, withdrawalGroupId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function acceptTip(
|
export async function acceptTip(
|
||||||
|
@ -116,63 +116,49 @@ export async function getTransactions(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (wsr.source.type) {
|
const r = await tx.get(Stores.reserves, wsr.reservePub);
|
||||||
case WithdrawalSourceType.Reserve:
|
if (!r) {
|
||||||
{
|
return;
|
||||||
const r = await tx.get(Stores.reserves, wsr.source.reservePub);
|
|
||||||
if (!r) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let amountRaw: AmountJson | undefined = undefined;
|
|
||||||
if (wsr.withdrawalGroupId === r.initialWithdrawalGroupId) {
|
|
||||||
amountRaw = r.instructedAmount;
|
|
||||||
} else {
|
|
||||||
amountRaw = wsr.denomsSel.totalWithdrawCost;
|
|
||||||
}
|
|
||||||
let withdrawalDetails: WithdrawalDetails;
|
|
||||||
if (r.bankInfo) {
|
|
||||||
withdrawalDetails = {
|
|
||||||
type: WithdrawalType.TalerBankIntegrationApi,
|
|
||||||
confirmed: true,
|
|
||||||
bankConfirmationUrl: r.bankInfo.confirmUrl,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const exchange = await tx.get(
|
|
||||||
Stores.exchanges,
|
|
||||||
r.exchangeBaseUrl,
|
|
||||||
);
|
|
||||||
if (!exchange) {
|
|
||||||
// FIXME: report somehow
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
withdrawalDetails = {
|
|
||||||
type: WithdrawalType.ManualTransfer,
|
|
||||||
exchangePaytoUris:
|
|
||||||
exchange.wireInfo?.accounts.map((x) => x.payto_uri) ?? [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
transactions.push({
|
|
||||||
type: TransactionType.Withdrawal,
|
|
||||||
amountEffective: Amounts.stringify(
|
|
||||||
wsr.denomsSel.totalCoinValue,
|
|
||||||
),
|
|
||||||
amountRaw: Amounts.stringify(amountRaw),
|
|
||||||
withdrawalDetails,
|
|
||||||
exchangeBaseUrl: wsr.exchangeBaseUrl,
|
|
||||||
pending: !wsr.timestampFinish,
|
|
||||||
timestamp: wsr.timestampStart,
|
|
||||||
transactionId: makeEventId(
|
|
||||||
TransactionType.Withdrawal,
|
|
||||||
wsr.withdrawalGroupId,
|
|
||||||
),
|
|
||||||
...(wsr.lastError ? { error: wsr.lastError } : {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Tips are reported via their own event
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
let amountRaw: AmountJson | undefined = undefined;
|
||||||
|
if (wsr.withdrawalGroupId === r.initialWithdrawalGroupId) {
|
||||||
|
amountRaw = r.instructedAmount;
|
||||||
|
} else {
|
||||||
|
amountRaw = wsr.denomsSel.totalWithdrawCost;
|
||||||
|
}
|
||||||
|
let withdrawalDetails: WithdrawalDetails;
|
||||||
|
if (r.bankInfo) {
|
||||||
|
withdrawalDetails = {
|
||||||
|
type: WithdrawalType.TalerBankIntegrationApi,
|
||||||
|
confirmed: true,
|
||||||
|
bankConfirmationUrl: r.bankInfo.confirmUrl,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const exchange = await tx.get(Stores.exchanges, r.exchangeBaseUrl);
|
||||||
|
if (!exchange) {
|
||||||
|
// FIXME: report somehow
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
withdrawalDetails = {
|
||||||
|
type: WithdrawalType.ManualTransfer,
|
||||||
|
exchangePaytoUris:
|
||||||
|
exchange.wireInfo?.accounts.map((x) => x.payto_uri) ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
transactions.push({
|
||||||
|
type: TransactionType.Withdrawal,
|
||||||
|
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
|
||||||
|
amountRaw: Amounts.stringify(amountRaw),
|
||||||
|
withdrawalDetails,
|
||||||
|
exchangeBaseUrl: wsr.exchangeBaseUrl,
|
||||||
|
pending: !wsr.timestampFinish,
|
||||||
|
timestamp: wsr.timestampStart,
|
||||||
|
transactionId: makeEventId(
|
||||||
|
TransactionType.Withdrawal,
|
||||||
|
wsr.withdrawalGroupId,
|
||||||
|
),
|
||||||
|
...(wsr.lastError ? { error: wsr.lastError } : {}),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Report pending withdrawals based on reserves that
|
// Report pending withdrawals based on reserves that
|
||||||
|
@ -242,12 +242,9 @@ async function processPlanchetGenerate(
|
|||||||
if (!denom) {
|
if (!denom) {
|
||||||
throw Error("invariant violated");
|
throw Error("invariant violated");
|
||||||
}
|
}
|
||||||
if (withdrawalGroup.source.type != WithdrawalSourceType.Reserve) {
|
|
||||||
throw Error("invariant violated");
|
|
||||||
}
|
|
||||||
const reserve = await ws.db.get(
|
const reserve = await ws.db.get(
|
||||||
Stores.reserves,
|
Stores.reserves,
|
||||||
withdrawalGroup.source.reservePub,
|
withdrawalGroup.reservePub,
|
||||||
);
|
);
|
||||||
if (!reserve) {
|
if (!reserve) {
|
||||||
throw Error("invariant violated");
|
throw Error("invariant violated");
|
||||||
@ -420,7 +417,7 @@ async function processPlanchetVerifyAndStoreCoin(
|
|||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => {
|
await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => {
|
||||||
let planchet = await ws.db.getIndexed(Stores.planchets.byGroupAndIndex, [
|
let planchet = await tx.getIndexed(Stores.planchets.byGroupAndIndex, [
|
||||||
withdrawalGroupId,
|
withdrawalGroupId,
|
||||||
coinIdx,
|
coinIdx,
|
||||||
]);
|
]);
|
||||||
@ -700,7 +697,7 @@ async function processWithdrawGroupImpl(
|
|||||||
if (finishedForFirstTime) {
|
if (finishedForFirstTime) {
|
||||||
ws.notify({
|
ws.notify({
|
||||||
type: NotificationType.WithdrawGroupFinished,
|
type: NotificationType.WithdrawGroupFinished,
|
||||||
withdrawalSource: withdrawalGroup.source,
|
reservePub: withdrawalGroup.reservePub,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -694,17 +694,28 @@ export interface PlanchetRecord {
|
|||||||
lastError: TalerErrorDetails | undefined;
|
lastError: TalerErrorDetails | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public key of the reserve, this might be a reserve not
|
* Public key of the reserve that this planchet
|
||||||
* known to the wallet if the planchet is from a tip.
|
* is being withdrawn from.
|
||||||
|
*
|
||||||
|
* Can be the empty string (non-null/undefined for DB indexing)
|
||||||
|
* if this is a tipping reserve.
|
||||||
*/
|
*/
|
||||||
reservePub: string;
|
reservePub: string;
|
||||||
|
|
||||||
denomPubHash: string;
|
denomPubHash: string;
|
||||||
|
|
||||||
denomPub: string;
|
denomPub: string;
|
||||||
|
|
||||||
blindingKey: string;
|
blindingKey: string;
|
||||||
|
|
||||||
withdrawSig: string;
|
withdrawSig: string;
|
||||||
|
|
||||||
coinEv: string;
|
coinEv: string;
|
||||||
|
|
||||||
coinEvHash: string;
|
coinEvHash: string;
|
||||||
|
|
||||||
coinValue: AmountJson;
|
coinValue: AmountJson;
|
||||||
|
|
||||||
isFromTip: boolean;
|
isFromTip: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -772,6 +783,8 @@ export interface RefreshCoinSource {
|
|||||||
|
|
||||||
export interface TipCoinSource {
|
export interface TipCoinSource {
|
||||||
type: CoinSourceType.Tip;
|
type: CoinSourceType.Tip;
|
||||||
|
walletTipId: string;
|
||||||
|
coinIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CoinSource = WithdrawCoinSource | RefreshCoinSource | TipCoinSource;
|
export type CoinSource = WithdrawCoinSource | RefreshCoinSource | TipCoinSource;
|
||||||
@ -950,9 +963,9 @@ export interface TipRecord {
|
|||||||
/**
|
/**
|
||||||
* The tipped amount.
|
* The tipped amount.
|
||||||
*/
|
*/
|
||||||
amount: AmountJson;
|
tipAmountRaw: AmountJson;
|
||||||
|
|
||||||
totalFees: AmountJson;
|
tipAmountEffective: AmountJson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp, the tip can't be picked up anymore after this deadline.
|
* Timestamp, the tip can't be picked up anymore after this deadline.
|
||||||
@ -1481,18 +1494,6 @@ export enum WithdrawalSourceType {
|
|||||||
Reserve = "reserve",
|
Reserve = "reserve",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WithdrawalSourceTip {
|
|
||||||
type: WithdrawalSourceType.Tip;
|
|
||||||
tipId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WithdrawalSourceReserve {
|
|
||||||
type: WithdrawalSourceType.Reserve;
|
|
||||||
reservePub: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WithdrawalSource = WithdrawalSourceTip | WithdrawalSourceReserve;
|
|
||||||
|
|
||||||
export interface DenominationSelectionInfo {
|
export interface DenominationSelectionInfo {
|
||||||
totalCoinValue: AmountJson;
|
totalCoinValue: AmountJson;
|
||||||
totalWithdrawCost: AmountJson;
|
totalWithdrawCost: AmountJson;
|
||||||
@ -1524,12 +1525,7 @@ export interface DenomSelectionState {
|
|||||||
export interface WithdrawalGroupRecord {
|
export interface WithdrawalGroupRecord {
|
||||||
withdrawalGroupId: string;
|
withdrawalGroupId: string;
|
||||||
|
|
||||||
/**
|
reservePub: string;
|
||||||
* Withdrawal source. Fields that don't apply to the respective
|
|
||||||
* withdrawal source type must be null (i.e. can't be absent),
|
|
||||||
* otherwise the IndexedDB indexing won't like us.
|
|
||||||
*/
|
|
||||||
source: WithdrawalSource;
|
|
||||||
|
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { TalerErrorDetails } from "./walletTypes";
|
import { TalerErrorDetails } from "./walletTypes";
|
||||||
import { WithdrawalSource } from "./dbTypes";
|
|
||||||
import { ReserveHistorySummary } from "../util/reserveHistoryUtil";
|
import { ReserveHistorySummary } from "../util/reserveHistoryUtil";
|
||||||
|
|
||||||
export enum NotificationType {
|
export enum NotificationType {
|
||||||
@ -141,7 +140,7 @@ export interface WithdrawalGroupCreatedNotification {
|
|||||||
|
|
||||||
export interface WithdrawalGroupFinishedNotification {
|
export interface WithdrawalGroupFinishedNotification {
|
||||||
type: NotificationType.WithdrawGroupFinished;
|
type: NotificationType.WithdrawGroupFinished;
|
||||||
withdrawalSource: WithdrawalSource;
|
reservePub: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WaitingForRetryNotification {
|
export interface WaitingForRetryNotification {
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { TalerErrorDetails, BalancesResponse } from "./walletTypes";
|
import { TalerErrorDetails, BalancesResponse } from "./walletTypes";
|
||||||
import { WithdrawalSource, RetryInfo, ReserveRecordStatus } from "./dbTypes";
|
import { RetryInfo, ReserveRecordStatus } from "./dbTypes";
|
||||||
import { Timestamp, Duration } from "../util/time";
|
import { Timestamp, Duration } from "../util/time";
|
||||||
|
|
||||||
export enum PendingOperationType {
|
export enum PendingOperationType {
|
||||||
@ -219,7 +219,6 @@ export interface PendingRecoupOperation {
|
|||||||
*/
|
*/
|
||||||
export interface PendingWithdrawOperation {
|
export interface PendingWithdrawOperation {
|
||||||
type: PendingOperationType.Withdraw;
|
type: PendingOperationType.Withdraw;
|
||||||
source: WithdrawalSource;
|
|
||||||
lastError: TalerErrorDetails | undefined;
|
lastError: TalerErrorDetails | undefined;
|
||||||
retryInfo: RetryInfo;
|
retryInfo: RetryInfo;
|
||||||
withdrawalGroupId: string;
|
withdrawalGroupId: string;
|
||||||
|
@ -593,11 +593,11 @@ export interface TipPickupRequest {
|
|||||||
* Reserve signature, defined as separate class to facilitate
|
* Reserve signature, defined as separate class to facilitate
|
||||||
* schema validation with "@Checkable".
|
* schema validation with "@Checkable".
|
||||||
*/
|
*/
|
||||||
export class ReserveSigSingleton {
|
export class BlindSigWrapper {
|
||||||
/**
|
/**
|
||||||
* Reserve signature.
|
* Reserve signature.
|
||||||
*/
|
*/
|
||||||
reserve_sig: string;
|
blind_sig: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -605,15 +605,10 @@ export class ReserveSigSingleton {
|
|||||||
* to the TipPickupRequest.
|
* to the TipPickupRequest.
|
||||||
*/
|
*/
|
||||||
export class TipResponse {
|
export class TipResponse {
|
||||||
/**
|
|
||||||
* Public key of the reserve
|
|
||||||
*/
|
|
||||||
reserve_pub: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The order of the signatures matches the planchets list.
|
* The order of the signatures matches the planchets list.
|
||||||
*/
|
*/
|
||||||
reserve_sigs: ReserveSigSingleton[];
|
blind_sigs: BlindSigWrapper[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1166,15 +1161,14 @@ export const codecForMerchantRefundResponse = (): Codec<
|
|||||||
.property("refunds", codecForList(codecForMerchantRefundPermission()))
|
.property("refunds", codecForList(codecForMerchantRefundPermission()))
|
||||||
.build("MerchantRefundResponse");
|
.build("MerchantRefundResponse");
|
||||||
|
|
||||||
export const codecForReserveSigSingleton = (): Codec<ReserveSigSingleton> =>
|
export const codecForBlindSigWrapper = (): Codec<BlindSigWrapper> =>
|
||||||
buildCodecForObject<ReserveSigSingleton>()
|
buildCodecForObject<BlindSigWrapper>()
|
||||||
.property("reserve_sig", codecForString())
|
.property("blind_sig", codecForString())
|
||||||
.build("ReserveSigSingleton");
|
.build("BlindSigWrapper");
|
||||||
|
|
||||||
export const codecForTipResponse = (): Codec<TipResponse> =>
|
export const codecForTipResponse = (): Codec<TipResponse> =>
|
||||||
buildCodecForObject<TipResponse>()
|
buildCodecForObject<TipResponse>()
|
||||||
.property("reserve_pub", codecForString())
|
.property("blind_sigs", codecForList(codecForBlindSigWrapper()))
|
||||||
.property("reserve_sigs", codecForList(codecForReserveSigSingleton()))
|
|
||||||
.build("TipResponse");
|
.build("TipResponse");
|
||||||
|
|
||||||
export const codecForRecoup = (): Codec<Recoup> =>
|
export const codecForRecoup = (): Codec<Recoup> =>
|
||||||
|
@ -359,8 +359,8 @@ export interface PrepareTipResult {
|
|||||||
* Has the tip already been accepted?
|
* Has the tip already been accepted?
|
||||||
*/
|
*/
|
||||||
accepted: boolean;
|
accepted: boolean;
|
||||||
amount: AmountString;
|
tipAmountRaw: AmountString;
|
||||||
totalFees: AmountString;
|
tipAmountEffective: AmountString;
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
expirationTimestamp: Timestamp;
|
expirationTimestamp: Timestamp;
|
||||||
}
|
}
|
||||||
@ -368,8 +368,8 @@ export interface PrepareTipResult {
|
|||||||
export const codecForPrepareTipResult = (): Codec<PrepareTipResult> =>
|
export const codecForPrepareTipResult = (): Codec<PrepareTipResult> =>
|
||||||
buildCodecForObject<PrepareTipResult>()
|
buildCodecForObject<PrepareTipResult>()
|
||||||
.property("accepted", codecForBoolean())
|
.property("accepted", codecForBoolean())
|
||||||
.property("amount", codecForAmountString())
|
.property("tipAmountRaw", codecForAmountString())
|
||||||
.property("totalFees", codecForAmountString())
|
.property("tipAmountEffective", codecForAmountString())
|
||||||
.property("exchangeBaseUrl", codecForString())
|
.property("exchangeBaseUrl", codecForString())
|
||||||
.property("expirationTimestamp", codecForTimestamp)
|
.property("expirationTimestamp", codecForTimestamp)
|
||||||
.property("walletTipId", codecForString())
|
.property("walletTipId", codecForString())
|
||||||
|
@ -86,7 +86,6 @@ import {
|
|||||||
codecForPreparePayRequest,
|
codecForPreparePayRequest,
|
||||||
codecForIntegrationTestArgs,
|
codecForIntegrationTestArgs,
|
||||||
WithdrawTestBalanceRequest,
|
WithdrawTestBalanceRequest,
|
||||||
withdrawTestBalanceDefaults,
|
|
||||||
codecForWithdrawTestBalance,
|
codecForWithdrawTestBalance,
|
||||||
codecForTestPayArgs,
|
codecForTestPayArgs,
|
||||||
codecForSetCoinSuspendedRequest,
|
codecForSetCoinSuspendedRequest,
|
||||||
@ -916,9 +915,7 @@ export class Wallet {
|
|||||||
console.error("no withdrawal session found for coin");
|
console.error("no withdrawal session found for coin");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ws.source.type == "reserve") {
|
withdrawalReservePub = ws.reservePub;
|
||||||
withdrawalReservePub = ws.source.reservePub;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
coinsJson.coins.push({
|
coinsJson.coins.push({
|
||||||
coin_pub: c.coinPub,
|
coin_pub: c.coinPub,
|
||||||
|
Loading…
Reference in New Issue
Block a user