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 */,
}
export enum TransactionStatus {
Unknown = 10,
Accepted = 20,
KycRequired = 30,
Wired = 40,
}
/**
* Additional information about the reason of a refresh.
*/
@ -1652,6 +1659,8 @@ export interface DepositGroupRecord {
timestampFinished: TalerProtocolTimestamp | undefined;
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");

View File

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

View File

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