query transaction status for deposit

This commit is contained in:
Sebastian 2023-01-15 17:48:41 -03:00
parent e034f1045c
commit fc38d0da95
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
3 changed files with 163 additions and 68 deletions

View File

@ -850,6 +850,13 @@ export enum RefreshOperationStatus {
FinishedWithError = 51 /* DORMANT_START + 1 */, FinishedWithError = 51 /* DORMANT_START + 1 */,
} }
export enum TransactionStatus {
Unknown = 10,
Accepted = 20,
KycRequired = 30,
Wired = 40,
}
/** /**
* Additional information about the reason of a refresh. * Additional information about the reason of a refresh.
*/ */
@ -1652,6 +1659,8 @@ export interface DepositGroupRecord {
timestampFinished: TalerProtocolTimestamp | undefined; timestampFinished: TalerProtocolTimestamp | undefined;
operationStatus: OperationStatus; operationStatus: OperationStatus;
transactionPerCoin: TransactionStatus[];
} }
/** /**
@ -2416,6 +2425,20 @@ export const walletDbFixups: FixupDescription[] = [
}); });
}, },
}, },
{
name: "DepositGroupRecord_transactionPerCoin",
async fn(tx): Promise<void> {
await tx.depositGroups.iter().forEachAsync(async (dg) => {
if (dg.transactionPerCoin) {
return;
}
dg.transactionPerCoin = dg.depositedPerCoin.map(
(c) => TransactionStatus.Unknown,
);
await tx.depositGroups.put(dg);
});
},
},
]; ];
const logger = new Logger("db.ts"); const logger = new Logger("db.ts");

View File

@ -24,7 +24,9 @@ import {
CancellationToken, CancellationToken,
canonicalJson, canonicalJson,
codecForDepositSuccess, codecForDepositSuccess,
MerchantContractTerms, codecForTackTransactionAccepted,
codecForTackTransactionWired,
CoinDepositPermission,
CreateDepositGroupRequest, CreateDepositGroupRequest,
CreateDepositGroupResponse, CreateDepositGroupResponse,
DepositGroupFees, DepositGroupFees,
@ -34,23 +36,27 @@ import {
GetFeeForDepositRequest, GetFeeForDepositRequest,
getRandomBytes, getRandomBytes,
hashWire, hashWire,
HttpStatusCode,
Logger, Logger,
MerchantContractTerms,
parsePaytoUri, parsePaytoUri,
PayCoinSelection, PayCoinSelection,
PrepareDepositRequest, PrepareDepositRequest,
PrepareDepositResponse, PrepareDepositResponse,
RefreshReason, RefreshReason,
TalerErrorCode,
TalerProtocolTimestamp, TalerProtocolTimestamp,
TrackDepositGroupRequest, TrackDepositGroupRequest,
TrackDepositGroupResponse, TrackDepositGroupResponse,
TrackTransaction,
TransactionType, TransactionType,
URL, URL,
TalerErrorCode,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
DenominationRecord, DenominationRecord,
DepositGroupRecord, DepositGroupRecord,
OperationStatus, OperationStatus,
TransactionStatus,
} from "../db.js"; } from "../db.js";
import { TalerError } from "../errors.js"; import { TalerError } from "../errors.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
@ -111,43 +117,60 @@ export async function processDepositGroup(
); );
for (let i = 0; i < depositPermissions.length; i++) { for (let i = 0; i < depositPermissions.length; i++) {
if (depositGroup.depositedPerCoin[i]) {
continue;
}
const perm = depositPermissions[i]; const perm = depositPermissions[i];
const requestBody: ExchangeDepositRequest = {
contribution: Amounts.stringify(perm.contribution), let updatedDeposit: boolean | undefined = undefined;
merchant_payto_uri: depositGroup.wire.payto_uri, let updatedTxStatus: TransactionStatus | undefined = undefined;
wire_salt: depositGroup.wire.salt,
h_contract_terms: depositGroup.contractTermsHash, if (!depositGroup.depositedPerCoin[i]) {
ub_sig: perm.ub_sig, const requestBody: ExchangeDepositRequest = {
timestamp: depositGroup.contractTermsRaw.timestamp, contribution: Amounts.stringify(perm.contribution),
wire_transfer_deadline: merchant_payto_uri: depositGroup.wire.payto_uri,
depositGroup.contractTermsRaw.wire_transfer_deadline, wire_salt: depositGroup.wire.salt,
refund_deadline: depositGroup.contractTermsRaw.refund_deadline, h_contract_terms: depositGroup.contractTermsHash,
coin_sig: perm.coin_sig, ub_sig: perm.ub_sig,
denom_pub_hash: perm.h_denom, timestamp: depositGroup.contractTermsRaw.timestamp,
merchant_pub: depositGroup.merchantPub, wire_transfer_deadline:
h_age_commitment: perm.h_age_commitment, depositGroup.contractTermsRaw.wire_transfer_deadline,
}; refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
// Check for cancellation before making network request. coin_sig: perm.coin_sig,
options.cancellationToken?.throwIfCancelled(); denom_pub_hash: perm.h_denom,
const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url); merchant_pub: depositGroup.merchantPub,
logger.info(`depositing to ${url}`); h_age_commitment: perm.h_age_commitment,
const httpResp = await ws.http.postJson(url.href, requestBody, { };
cancellationToken: options.cancellationToken, // Check for cancellation before making network request.
}); options.cancellationToken?.throwIfCancelled();
await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess()); const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
await ws.db logger.info(`depositing to ${url}`);
.mktx((x) => [x.depositGroups]) const httpResp = await ws.http.postJson(url.href, requestBody, {
.runReadWrite(async (tx) => { cancellationToken: options.cancellationToken,
const dg = await tx.depositGroups.get(depositGroupId);
if (!dg) {
return;
}
dg.depositedPerCoin[i] = true;
await tx.depositGroups.put(dg);
}); });
await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
updatedDeposit = true;
}
if (depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired) {
const track = await trackDepositPermission(ws, depositGroup, perm);
updatedTxStatus = txStatusFromTrack(track);
}
if (updatedTxStatus !== undefined || updatedDeposit !== undefined) {
await ws.db
.mktx((x) => [x.depositGroups])
.runReadWrite(async (tx) => {
const dg = await tx.depositGroups.get(depositGroupId);
if (!dg) {
return;
}
if (updatedDeposit !== undefined) {
dg.depositedPerCoin[i] = updatedDeposit;
}
if (updatedTxStatus !== undefined) {
dg.transactionPerCoin[i] = updatedTxStatus;
}
await tx.depositGroups.put(dg);
});
}
} }
await ws.db await ws.db
@ -157,13 +180,17 @@ export async function processDepositGroup(
if (!dg) { if (!dg) {
return; return;
} }
let allDeposited = true; let allDepositedAndWired = true;
for (const d of depositGroup.depositedPerCoin) { for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) {
if (!d) { if (
allDeposited = false; !depositGroup.depositedPerCoin[i] ||
depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired
) {
allDepositedAndWired = false;
break;
} }
} }
if (allDeposited) { if (allDepositedAndWired) {
dg.timestampFinished = TalerProtocolTimestamp.now(); dg.timestampFinished = TalerProtocolTimestamp.now();
dg.operationStatus = OperationStatus.Finished; dg.operationStatus = OperationStatus.Finished;
await tx.depositGroups.put(dg); await tx.depositGroups.put(dg);
@ -172,14 +199,24 @@ export async function processDepositGroup(
return OperationAttemptResult.finishedEmpty(); return OperationAttemptResult.finishedEmpty();
} }
function txStatusFromTrack(t: TrackTransaction): TransactionStatus {
if (t.type === "accepted") {
if (!t.kyc_ok && t.requirement_row !== undefined) {
return TransactionStatus.KycRequired;
}
return TransactionStatus.Accepted;
}
if (t.type === "wired") {
return TransactionStatus.Wired;
}
return TransactionStatus.Unknown;
}
export async function trackDepositGroup( export async function trackDepositGroup(
ws: InternalWalletState, ws: InternalWalletState,
req: TrackDepositGroupRequest, req: TrackDepositGroupRequest,
): Promise<TrackDepositGroupResponse> { ): Promise<TrackDepositGroupResponse> {
const responses: { const responses: TrackTransaction[] = [];
status: number;
body: any;
}[] = [];
const depositGroup = await ws.db const depositGroup = await ws.db
.mktx((x) => [x.depositGroups]) .mktx((x) => [x.depositGroups])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -200,31 +237,55 @@ export async function trackDepositGroup(
contractData, contractData,
); );
for (const dp of depositPermissions) {
const track = await trackDepositPermission(ws, depositGroup, dp);
responses.push(track);
}
return { responses };
}
async function trackDepositPermission(
ws: InternalWalletState,
depositGroup: DepositGroupRecord,
dp: CoinDepositPermission,
): Promise<TrackTransaction> {
const wireHash = depositGroup.contractTermsRaw.h_wire; const wireHash = depositGroup.contractTermsRaw.h_wire;
for (const dp of depositPermissions) { const url = new URL(
const url = new URL( `deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`,
`deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`, dp.exchange_url,
dp.exchange_url, );
); const sigResp = await ws.cryptoApi.signTrackTransaction({
const sigResp = await ws.cryptoApi.signTrackTransaction({ coinPub: dp.coin_pub,
coinPub: dp.coin_pub, contractTermsHash: depositGroup.contractTermsHash,
contractTermsHash: depositGroup.contractTermsHash, merchantPriv: depositGroup.merchantPriv,
merchantPriv: depositGroup.merchantPriv, merchantPub: depositGroup.merchantPub,
merchantPub: depositGroup.merchantPub, wireHash,
wireHash, });
}); url.searchParams.set("merchant_sig", sigResp.sig);
url.searchParams.set("merchant_sig", sigResp.sig); const httpResp = await ws.http.get(url.href);
const httpResp = await ws.http.get(url.href); switch (httpResp.status) {
const body = await httpResp.json(); case HttpStatusCode.Accepted: {
responses.push({ const accepted = await readSuccessResponseJsonOrThrow(
body, httpResp,
status: httpResp.status, codecForTackTransactionAccepted(),
}); );
return { type: "accepted", ...accepted };
}
case HttpStatusCode.Ok: {
const wired = await readSuccessResponseJsonOrThrow(
httpResp,
codecForTackTransactionWired(),
);
return { type: "wired", ...wired };
}
default: {
throw Error(
`unexpected response from track-transaction (${httpResp.status})`,
);
}
} }
return {
responses,
};
} }
export async function getFeeForDeposit( export async function getFeeForDeposit(
@ -491,6 +552,9 @@ export async function createDepositGroup(
noncePub: noncePair.pub, noncePub: noncePair.pub,
timestampCreated: AbsoluteTime.toTimestamp(now), timestampCreated: AbsoluteTime.toTimestamp(now),
timestampFinished: undefined, timestampFinished: undefined,
transactionPerCoin: payCoinSel.coinSel.coinPubs.map(
() => TransactionStatus.Unknown,
),
payCoinSelection: payCoinSel.coinSel, payCoinSelection: payCoinSel.coinSel,
payCoinSelectionUid: encodeCrock(getRandomBytes(32)), payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
depositedPerCoin: payCoinSel.coinSel.coinPubs.map(() => false), depositedPerCoin: payCoinSel.coinSel.coinPubs.map(() => false),

View File

@ -53,6 +53,7 @@ import {
WalletContractData, WalletContractData,
PeerPushPaymentInitiationStatus, PeerPushPaymentInitiationStatus,
PeerPullPaymentIncomingStatus, PeerPullPaymentIncomingStatus,
TransactionStatus,
} from "../db.js"; } from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { assertUnreachable } from "../util/assertUnreachable.js"; import { assertUnreachable } from "../util/assertUnreachable.js";
@ -552,6 +553,13 @@ function buildTransactionForDeposit(
TransactionType.Deposit, TransactionType.Deposit,
dg.depositGroupId, dg.depositGroupId,
), ),
wireTransferProgress:
(100 *
dg.transactionPerCoin.reduce(
(prev, cur) => prev + (cur === TransactionStatus.Wired ? 1 : 0),
0,
)) /
dg.transactionPerCoin.length,
depositGroupId: dg.depositGroupId, depositGroupId: dg.depositGroupId,
...(ort?.lastError ? { error: ort.lastError } : {}), ...(ort?.lastError ? { error: ort.lastError } : {}),
}; };