/*
This file is part of GNU Taler
(C) 2020 Taler Systems S.A.
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
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see
*/
/**
* Imports.
*/
import {
AgeRestriction,
Amounts,
AmountString,
codecForExchangeWithdrawBatchResponse,
encodeCrock,
ExchangeBatchWithdrawRequest,
getRandomBytes,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
readSuccessResponseJsonOrThrow,
} from "@gnu-taler/taler-util/http";
import {
CryptoDispatcher,
SynchronousCryptoWorkerFactoryPlain,
TalerCryptoInterface,
} from "@gnu-taler/taler-wallet-core";
import {
checkReserve,
CoinInfo,
downloadExchangeInfo,
findDenomOrThrow,
ReserveKeypair,
topupReserveWithBank,
} from "@gnu-taler/taler-wallet-core/dbless";
import { DenominationRecord } from "../../../taler-wallet-core/src/db.js";
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";
/**
* Run test for basic, bank-integrated withdrawal and payment.
*/
export async function runWithdrawalIdempotentTest(t: GlobalTestState) {
// Set up test environment
const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);
const http = harnessHttpLib;
const cryptiDisp = new CryptoDispatcher(
new SynchronousCryptoWorkerFactoryPlain(),
);
const cryptoApi = cryptiDisp.cryptoApi;
const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http);
const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
let reserveUrl = new URL(`reserves/${reserveKeyPair.pub}`, exchange.baseUrl);
reserveUrl.searchParams.set("timeout_ms", "30000");
const longpollReq = http.fetch(reserveUrl.href, {
method: "GET",
});
await topupReserveWithBank({
amount: "TESTKUDOS:10" as AmountString,
http,
reservePub: reserveKeyPair.pub,
corebankApiBaseUrl: bank.corebankApiBaseUrl,
exchangeInfo,
});
console.log("waiting for longpoll request");
const resp = await longpollReq;
console.log(`got response, status ${resp.status}`);
console.log(exchangeInfo);
await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub);
const denomselAllowLate = false;
const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8" as AmountString, {
denomselAllowLate,
});
await myWithdrawCoin({
http,
cryptoApi,
reserveKeyPair: {
reservePriv: reserveKeyPair.priv,
reservePub: reserveKeyPair.pub,
},
denom: d1,
exchangeBaseUrl: exchange.baseUrl,
});
}
async function myWithdrawCoin(args: {
http: HttpRequestLibrary;
cryptoApi: TalerCryptoInterface;
reserveKeyPair: ReserveKeypair;
denom: DenominationRecord;
exchangeBaseUrl: string;
}): Promise {
const { http, cryptoApi, reserveKeyPair, denom, exchangeBaseUrl } = args;
const planchet = await cryptoApi.createPlanchet({
coinIndex: 0,
denomPub: denom.denomPub,
feeWithdraw: Amounts.parseOrThrow(denom.fees.feeWithdraw),
reservePriv: reserveKeyPair.reservePriv,
reservePub: reserveKeyPair.reservePub,
secretSeed: encodeCrock(getRandomBytes(32)),
value: Amounts.parseOrThrow(denom.value),
});
const reqBody: ExchangeBatchWithdrawRequest = {
planchets: [
{
denom_pub_hash: planchet.denomPubHash,
reserve_sig: planchet.withdrawSig,
coin_ev: planchet.coinEv,
},
],
};
const reqUrl = new URL(
`reserves/${planchet.reservePub}/batch-withdraw`,
exchangeBaseUrl,
).href;
const resp = await http.fetch(reqUrl, { method: "POST", body: reqBody });
const rBatch = await readSuccessResponseJsonOrThrow(
resp,
codecForExchangeWithdrawBatchResponse(),
);
{
// Check for idempotency!
const resp2 = await http.fetch(reqUrl, { method: "POST", body: reqBody });
await readSuccessResponseJsonOrThrow(
resp2,
codecForExchangeWithdrawBatchResponse(),
);
}
const ubSig = await cryptoApi.unblindDenominationSignature({
planchet,
evSig: rBatch.ev_sigs[0].ev_sig,
});
return {
coinPriv: planchet.coinPriv,
coinPub: planchet.coinPub,
denomSig: ubSig,
denomPub: denom.denomPub,
denomPubHash: denom.denomPubHash,
feeDeposit: Amounts.stringify(denom.fees.feeDeposit),
feeRefresh: Amounts.stringify(denom.fees.feeRefresh),
exchangeBaseUrl: args.exchangeBaseUrl,
maxAge: AgeRestriction.AGE_UNRESTRICTED,
};
}
runWithdrawalIdempotentTest.suites = ["wallet"];