taler-harness: add exchange-purse test
This commit is contained in:
parent
b13bd85215
commit
55bdc161b5
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2023 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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
AbsoluteTime,
|
||||||
|
ContractTermsUtil,
|
||||||
|
decodeCrock,
|
||||||
|
Duration,
|
||||||
|
encodeCrock,
|
||||||
|
getRandomBytes,
|
||||||
|
hash,
|
||||||
|
j2s,
|
||||||
|
PeerContractTerms,
|
||||||
|
TalerError,
|
||||||
|
TalerPreciseTimestamp,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import {
|
||||||
|
checkReserve,
|
||||||
|
CryptoDispatcher,
|
||||||
|
downloadExchangeInfo,
|
||||||
|
EncryptContractRequest,
|
||||||
|
findDenomOrThrow,
|
||||||
|
SpendCoinDetails,
|
||||||
|
SynchronousCryptoWorkerFactoryPlain,
|
||||||
|
topupReserveWithDemobank,
|
||||||
|
Wallet,
|
||||||
|
withdrawCoin,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
|
||||||
|
import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the exchange's purse API.
|
||||||
|
*/
|
||||||
|
export async function runExchangePurseTest(t: GlobalTestState) {
|
||||||
|
// Set up test environment
|
||||||
|
|
||||||
|
const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);
|
||||||
|
|
||||||
|
const http = harnessHttpLib;
|
||||||
|
const cryptoDisp = new CryptoDispatcher(
|
||||||
|
new SynchronousCryptoWorkerFactoryPlain(),
|
||||||
|
);
|
||||||
|
const cryptoApi = cryptoDisp.cryptoApi;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
|
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 topupReserveWithDemobank({
|
||||||
|
amount: "TESTKUDOS:10",
|
||||||
|
http,
|
||||||
|
reservePub: reserveKeyPair.pub,
|
||||||
|
bankAccessApiBaseUrl: bank.bankAccessApiBaseUrl,
|
||||||
|
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 d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8", {
|
||||||
|
denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const coin = await withdrawCoin({
|
||||||
|
http,
|
||||||
|
cryptoApi,
|
||||||
|
reserveKeyPair: {
|
||||||
|
reservePriv: reserveKeyPair.priv,
|
||||||
|
reservePub: reserveKeyPair.pub,
|
||||||
|
},
|
||||||
|
denom: d1,
|
||||||
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const amount = "TESTKUDOS:5";
|
||||||
|
const purseFee = "TESTKUDOS:0";
|
||||||
|
|
||||||
|
const mergeTimestamp = TalerPreciseTimestamp.now();
|
||||||
|
|
||||||
|
const contractTerms: PeerContractTerms = {
|
||||||
|
amount,
|
||||||
|
summary: "Hello",
|
||||||
|
purse_expiration: AbsoluteTime.toProtocolTimestamp(
|
||||||
|
AbsoluteTime.addDuration(
|
||||||
|
AbsoluteTime.now(),
|
||||||
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeReservePair = await cryptoApi.createEddsaKeypair({});
|
||||||
|
const pursePair = await cryptoApi.createEddsaKeypair({});
|
||||||
|
const mergePair = await cryptoApi.createEddsaKeypair({});
|
||||||
|
const contractPair = await cryptoApi.createEddsaKeypair({});
|
||||||
|
const contractEncNonce = encodeCrock(getRandomBytes(24));
|
||||||
|
|
||||||
|
const pursePub = pursePair.pub;
|
||||||
|
|
||||||
|
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
|
||||||
|
|
||||||
|
const purseSigResp = await cryptoApi.signPurseCreation({
|
||||||
|
hContractTerms,
|
||||||
|
mergePub: mergePair.pub,
|
||||||
|
minAge: 0,
|
||||||
|
purseAmount: amount,
|
||||||
|
purseExpiration: contractTerms.purse_expiration,
|
||||||
|
pursePriv: pursePair.priv,
|
||||||
|
});
|
||||||
|
|
||||||
|
const coinSpend: SpendCoinDetails = {
|
||||||
|
ageCommitmentProof: undefined,
|
||||||
|
coinPriv: coin.coinPriv,
|
||||||
|
coinPub: coin.coinPub,
|
||||||
|
contribution: amount,
|
||||||
|
denomPubHash: coin.denomPubHash,
|
||||||
|
denomSig: coin.denomSig,
|
||||||
|
};
|
||||||
|
|
||||||
|
const depositSigsResp = await cryptoApi.signPurseDeposits({
|
||||||
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
|
pursePub: pursePair.pub,
|
||||||
|
coins: [coinSpend],
|
||||||
|
});
|
||||||
|
|
||||||
|
const encryptContractRequest: EncryptContractRequest = {
|
||||||
|
contractTerms: contractTerms,
|
||||||
|
mergePriv: mergePair.priv,
|
||||||
|
pursePriv: pursePair.priv,
|
||||||
|
pursePub: pursePair.pub,
|
||||||
|
contractPriv: contractPair.priv,
|
||||||
|
contractPub: contractPair.pub,
|
||||||
|
nonce: contractEncNonce,
|
||||||
|
};
|
||||||
|
|
||||||
|
const econtractResp = await cryptoApi.encryptContractForMerge(
|
||||||
|
encryptContractRequest,
|
||||||
|
);
|
||||||
|
|
||||||
|
const econtractHash = encodeCrock(
|
||||||
|
hash(decodeCrock(econtractResp.econtract.econtract)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const createPurseUrl = new URL(
|
||||||
|
`purses/${pursePair.pub}/create`,
|
||||||
|
exchange.baseUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
const reqBody = {
|
||||||
|
amount: amount,
|
||||||
|
merge_pub: mergePair.pub,
|
||||||
|
purse_sig: purseSigResp.sig,
|
||||||
|
h_contract_terms: hContractTerms,
|
||||||
|
purse_expiration: contractTerms.purse_expiration,
|
||||||
|
deposits: depositSigsResp.deposits,
|
||||||
|
min_age: 0,
|
||||||
|
econtract: econtractResp.econtract,
|
||||||
|
};
|
||||||
|
|
||||||
|
const httpResp = await http.fetch(createPurseUrl.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: reqBody,
|
||||||
|
});
|
||||||
|
|
||||||
|
const respBody = await httpResp.json();
|
||||||
|
|
||||||
|
console.log("status", httpResp.status);
|
||||||
|
|
||||||
|
console.log(j2s(respBody));
|
||||||
|
|
||||||
|
const mergeUrl = new URL(`purses/${pursePub}/merge`, exchange.baseUrl);
|
||||||
|
mergeUrl.searchParams.set("timeout_ms", "300");
|
||||||
|
const statusResp = await http.fetch(mergeUrl.href, {});
|
||||||
|
|
||||||
|
const statusRespBody = await statusResp.json();
|
||||||
|
|
||||||
|
console.log(j2s(statusRespBody));
|
||||||
|
|
||||||
|
t.assertTrue(statusRespBody.merge_timestamp === undefined);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TalerError) {
|
||||||
|
console.log(e);
|
||||||
|
console.log(j2s(e.errorDetail));
|
||||||
|
} else {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runExchangePurseTest.suites = ["wallet"];
|
@ -14,7 +14,12 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CancellationToken, Logger, minimatch, setGlobalLogLevelFromString } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
CancellationToken,
|
||||||
|
Logger,
|
||||||
|
minimatch,
|
||||||
|
setGlobalLogLevelFromString,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
@ -105,6 +110,7 @@ import { runPeerRepairTest } from "./test-peer-repair.js";
|
|||||||
import { runPaymentShareTest } from "./test-payment-share.js";
|
import { runPaymentShareTest } from "./test-payment-share.js";
|
||||||
import { runSimplePaymentTest } from "./test-simple-payment.js";
|
import { runSimplePaymentTest } from "./test-simple-payment.js";
|
||||||
import { runTermOfServiceFormatTest } from "./test-tos-format.js";
|
import { runTermOfServiceFormatTest } from "./test-tos-format.js";
|
||||||
|
import { runExchangePurseTest } from "./test-exchange-purse.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test runner.
|
* Test runner.
|
||||||
@ -137,6 +143,7 @@ const allTests: TestMainFunction[] = [
|
|||||||
runFeeRegressionTest,
|
runFeeRegressionTest,
|
||||||
runForcedSelectionTest,
|
runForcedSelectionTest,
|
||||||
runKycTest,
|
runKycTest,
|
||||||
|
runExchangePurseTest,
|
||||||
runExchangeDepositTest,
|
runExchangeDepositTest,
|
||||||
runLibeufinAnastasisFacadeTest,
|
runLibeufinAnastasisFacadeTest,
|
||||||
runLibeufinApiBankaccountTest,
|
runLibeufinApiBankaccountTest,
|
||||||
|
@ -239,3 +239,25 @@ export function parsePaytoUri(s: string): PaytoUri | undefined {
|
|||||||
isKnown: false,
|
isKnown: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function talerPaytoFromExchangeReserve(
|
||||||
|
exchangeBaseUrl: string,
|
||||||
|
reservePub: string,
|
||||||
|
): string {
|
||||||
|
const url = new URL(exchangeBaseUrl);
|
||||||
|
let proto: string;
|
||||||
|
if (url.protocol === "http:") {
|
||||||
|
proto = "taler-reserve-http";
|
||||||
|
} else if (url.protocol === "https:") {
|
||||||
|
proto = "taler-reserve";
|
||||||
|
} else {
|
||||||
|
throw Error(`unsupported exchange base URL protocol (${url.protocol})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = url.pathname;
|
||||||
|
if (!path.endsWith("/")) {
|
||||||
|
path = path + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `payto://${proto}/${url.host}${url.pathname}${reservePub}`;
|
||||||
|
}
|
||||||
|
@ -1004,7 +1004,7 @@ export enum TalerSignaturePurpose {
|
|||||||
SYNC_BACKUP_UPLOAD = 1450,
|
SYNC_BACKUP_UPLOAD = 1450,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum WalletAccountMergeFlags {
|
export enum WalletAccountMergeFlags {
|
||||||
/**
|
/**
|
||||||
* Not a legal mode!
|
* Not a legal mode!
|
||||||
*/
|
*/
|
||||||
@ -1281,7 +1281,8 @@ export namespace AgeRestriction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PublishedAgeRestrictionBaseKey: Edx25519PublicKey = decodeCrock(
|
const PublishedAgeRestrictionBaseKey: Edx25519PublicKey = decodeCrock(
|
||||||
"CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG");
|
"CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG",
|
||||||
|
);
|
||||||
|
|
||||||
export async function restrictionCommitSeeded(
|
export async function restrictionCommitSeeded(
|
||||||
ageMask: number,
|
ageMask: number,
|
||||||
|
@ -51,12 +51,8 @@ export * from "./operations/refresh.js";
|
|||||||
|
|
||||||
export * from "./dbless.js";
|
export * from "./dbless.js";
|
||||||
|
|
||||||
export {
|
export * from "./crypto/cryptoTypes.js";
|
||||||
nativeCryptoR,
|
export * from "./crypto/cryptoImplementation.js";
|
||||||
nativeCrypto,
|
|
||||||
nullCrypto,
|
|
||||||
TalerCryptoInterface,
|
|
||||||
} from "./crypto/cryptoImplementation.js";
|
|
||||||
|
|
||||||
export * from "./util/timer.js";
|
export * from "./util/timer.js";
|
||||||
export * from "./util/denominations.js";
|
export * from "./util/denominations.js";
|
||||||
|
@ -420,28 +420,6 @@ export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> =>
|
|||||||
.property("merge_timestamp", codecOptional(codecForTimestamp))
|
.property("merge_timestamp", codecOptional(codecForTimestamp))
|
||||||
.build("ExchangePurseStatus");
|
.build("ExchangePurseStatus");
|
||||||
|
|
||||||
export function talerPaytoFromExchangeReserve(
|
|
||||||
exchangeBaseUrl: string,
|
|
||||||
reservePub: string,
|
|
||||||
): string {
|
|
||||||
const url = new URL(exchangeBaseUrl);
|
|
||||||
let proto: string;
|
|
||||||
if (url.protocol === "http:") {
|
|
||||||
proto = "taler-reserve-http";
|
|
||||||
} else if (url.protocol === "https:") {
|
|
||||||
proto = "taler-reserve";
|
|
||||||
} else {
|
|
||||||
throw Error(`unsupported exchange base URL protocol (${url.protocol})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = url.pathname;
|
|
||||||
if (!path.endsWith("/")) {
|
|
||||||
path = path + "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `payto://${proto}/${url.host}${url.pathname}${reservePub}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMergeReserveInfo(
|
export async function getMergeReserveInfo(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
req: {
|
req: {
|
||||||
|
@ -45,6 +45,7 @@ import {
|
|||||||
j2s,
|
j2s,
|
||||||
makeErrorDetail,
|
makeErrorDetail,
|
||||||
stringifyTalerUri,
|
stringifyTalerUri,
|
||||||
|
talerPaytoFromExchangeReserve,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
readSuccessResponseJsonOrErrorCode,
|
readSuccessResponseJsonOrErrorCode,
|
||||||
@ -74,7 +75,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
codecForExchangePurseStatus,
|
codecForExchangePurseStatus,
|
||||||
getMergeReserveInfo,
|
getMergeReserveInfo,
|
||||||
talerPaytoFromExchangeReserve,
|
|
||||||
} from "./pay-peer-common.js";
|
} from "./pay-peer-common.js";
|
||||||
import {
|
import {
|
||||||
constructTransactionIdentifier,
|
constructTransactionIdentifier,
|
||||||
|
@ -47,6 +47,7 @@ import {
|
|||||||
j2s,
|
j2s,
|
||||||
makeErrorDetail,
|
makeErrorDetail,
|
||||||
parsePayPushUri,
|
parsePayPushUri,
|
||||||
|
talerPaytoFromExchangeReserve,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
||||||
import {
|
import {
|
||||||
@ -71,7 +72,6 @@ import { updateExchangeFromUrl } from "./exchanges.js";
|
|||||||
import {
|
import {
|
||||||
codecForExchangePurseStatus,
|
codecForExchangePurseStatus,
|
||||||
getMergeReserveInfo,
|
getMergeReserveInfo,
|
||||||
talerPaytoFromExchangeReserve,
|
|
||||||
} from "./pay-peer-common.js";
|
} from "./pay-peer-common.js";
|
||||||
import {
|
import {
|
||||||
TransitionInfo,
|
TransitionInfo,
|
||||||
|
Loading…
Reference in New Issue
Block a user