wallet-core: CLI improvements, ToS fetching fixes

This commit is contained in:
Florian Dold 2022-10-16 20:15:55 +02:00
parent fbb7dd9e7e
commit 8d4a7d6103
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
4 changed files with 106 additions and 69 deletions

View File

@ -314,48 +314,42 @@ walletCli
logger.info("finished handling API request");
});
walletCli
.subcommand("", "pending", { help: "Show pending operations." })
.action(async (args) => {
await withWallet(args, async (wallet) => {
const pending = await wallet.client.call(
WalletApiOperation.GetPendingOperations,
{},
);
console.log(JSON.stringify(pending, undefined, 2));
});
});
walletCli
.subcommand("transactions", "transactions", { help: "Show transactions." })
const transactionsCli = walletCli
.subcommand("transactions", "transactions", { help: "Manage transactions." })
.maybeOption("currency", ["--currency"], clk.STRING)
.maybeOption("search", ["--search"], clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
const pending = await wallet.client.call(
WalletApiOperation.GetTransactions,
{
currency: args.transactions.currency,
search: args.transactions.search,
},
);
console.log(JSON.stringify(pending, undefined, 2));
});
});
.maybeOption("search", ["--search"], clk.STRING);
walletCli
.subcommand("runPendingOpt", "run-pending", {
help: "Run pending operations.",
// Default action
transactionsCli.action(async (args) => {
await withWallet(args, async (wallet) => {
const pending = await wallet.client.call(
WalletApiOperation.GetTransactions,
{
currency: args.transactions.currency,
search: args.transactions.search,
},
);
console.log(JSON.stringify(pending, undefined, 2));
});
});
transactionsCli
.subcommand("deleteTransaction", "delete", {
help: "Permanently delete a transaction from the transaction list.",
})
.requiredArgument("transactionId", clk.STRING, {
help: "Identifier of the transaction to delete",
})
.flag("forceNow", ["-f", "--force-now"])
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.ws.runPending(args.runPendingOpt.forceNow);
await wallet.client.call(WalletApiOperation.DeleteTransaction, {
transactionId: args.deleteTransaction.transactionId,
});
});
});
walletCli
.subcommand("retryTransaction", "retry-transaction", {
transactionsCli
.subcommand("retryTransaction", "retry", {
help: "Retry a transaction.",
})
.requiredArgument("transactionId", clk.STRING)
@ -387,21 +381,6 @@ walletCli
});
});
walletCli
.subcommand("deleteTransaction", "delete-transaction", {
help: "Permanently delete a transaction from the transaction list.",
})
.requiredArgument("transactionId", clk.STRING, {
help: "Identifier of the transaction to delete",
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.client.call(WalletApiOperation.DeleteTransaction, {
transactionId: args.deleteTransaction.transactionId,
});
});
});
walletCli
.subcommand("withdraw", "withdraw", {
help: "Withdraw with a taler://withdraw/ URI",
@ -604,17 +583,26 @@ exchangesCli
exchangesCli
.subcommand("exchangesTosCmd", "tos", {
help: "Show terms of service.",
help: "Show/request terms of service.",
})
.requiredArgument("url", clk.STRING, {
help: "Base URL of the exchange.",
})
.maybeOption("contentTypes", ["--content-type"], clk.STRING)
.action(async (args) => {
let acceptedFormat: string[] | undefined = undefined;
if (args.exchangesTosCmd.contentTypes) {
const split = args.exchangesTosCmd.contentTypes
.split(",")
.map((x) => x.trim());
acceptedFormat = split;
}
await withWallet(args, async (wallet) => {
const tosResult = await wallet.client.call(
WalletApiOperation.GetExchangeTos,
{
exchangeBaseUrl: args.exchangesTosCmd.url,
acceptedFormat,
},
);
console.log(JSON.stringify(tosResult, undefined, 2));
@ -764,6 +752,29 @@ advancedCli
await withWallet(args, async () => {});
});
advancedCli
.subcommand("runPendingOpt", "run-pending", {
help: "Run pending operations.",
})
.flag("forceNow", ["-f", "--force-now"])
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.ws.runPending(args.runPendingOpt.forceNow);
});
});
advancedCli
.subcommand("", "pending", { help: "Show pending operations." })
.action(async (args) => {
await withWallet(args, async (wallet) => {
const pending = await wallet.client.call(
WalletApiOperation.GetPendingOperations,
{},
);
console.log(JSON.stringify(pending, undefined, 2));
});
});
advancedCli
.subcommand("bench1", "bench1", {
help: "Run the 'bench1' benchmark",

View File

@ -521,7 +521,7 @@ export interface ExchangeRecord {
* Should usually not change. Only changes when the
* exchange advertises a different master public key and/or
* currency.
*
*
* FIXME: Use a rowId here?
*/
detailsPointer: ExchangeDetailsPointer | undefined;
@ -1364,7 +1364,7 @@ export interface WithdrawalGroupRecord {
/**
* Wire information (as payto URI) for the bank account that
* transferred funds for this reserve.
*
*
* FIXME: Doesn't this belong to the bankAccounts object store?
*/
senderWire?: string;

View File

@ -134,6 +134,7 @@ export async function downloadExchangeWithTermsOfService(
timeout: Duration,
contentType: string,
): Promise<ExchangeTosDownloadResult> {
logger.info(`downloading exchange tos (type ${contentType})`);
const reqUrl = new URL("terms", exchangeBaseUrl);
const headers = {
Accept: contentType,
@ -524,7 +525,9 @@ export async function downloadTosFromAcceptedFormat(
break;
}
}
if (tosFound !== undefined) return tosFound;
if (tosFound !== undefined) {
return tosFound;
}
// If none of the specified format was found try text/plain
return await downloadExchangeWithTermsOfService(
baseUrl,
@ -557,7 +560,7 @@ export async function updateExchangeFromUrl(
*/
export async function updateExchangeFromUrlHandler(
ws: InternalWalletState,
baseUrl: string,
exchangeBaseUrl: string,
options: {
forceNow?: boolean;
cancellationToken?: CancellationToken;
@ -569,19 +572,21 @@ export async function updateExchangeFromUrlHandler(
}>
> {
const forceNow = options.forceNow ?? false;
logger.info(`updating exchange info for ${baseUrl}, forced: ${forceNow}`);
logger.info(
`updating exchange info for ${exchangeBaseUrl}, forced: ${forceNow}`,
);
const now = AbsoluteTime.now();
baseUrl = canonicalizeBaseUrl(baseUrl);
exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl);
let isNewExchange = true;
const { exchange, exchangeDetails } = await ws.db
.mktx((x) => [x.exchanges, x.exchangeDetails])
.runReadWrite(async (tx) => {
let oldExch = await tx.exchanges.get(baseUrl);
let oldExch = await tx.exchanges.get(exchangeBaseUrl);
if (oldExch) {
isNewExchange = false;
}
return provideExchangeRecordInTx(ws, tx, baseUrl, now);
return provideExchangeRecordInTx(ws, tx, exchangeBaseUrl, now);
});
if (
@ -600,11 +605,15 @@ export async function updateExchangeFromUrlHandler(
const timeout = getExchangeRequestTimeout();
const keysInfo = await downloadExchangeKeysInfo(baseUrl, ws.http, timeout);
const keysInfo = await downloadExchangeKeysInfo(
exchangeBaseUrl,
ws.http,
timeout,
);
logger.info("updating exchange /wire info");
const wireInfoDownload = await downloadExchangeWireInfo(
baseUrl,
exchangeBaseUrl,
ws.http,
timeout,
);
@ -632,15 +641,15 @@ export async function updateExchangeFromUrlHandler(
logger.info("finished validating exchange /wire info");
// We download the text/plain version here,
// because that one needs to exist, and we
// will get the current etag from the response.
const tosDownload = await downloadTosFromAcceptedFormat(
ws,
baseUrl,
exchangeBaseUrl,
timeout,
["text/plain"],
);
const tosHasBeenAccepted =
exchangeDetails?.tosAccepted &&
exchangeDetails.tosAccepted.etag === tosDownload.tosEtag;
let recoupGroupId: string | undefined;
@ -651,6 +660,7 @@ export async function updateExchangeFromUrlHandler(
const updated = await ws.db
.mktx((x) => [
x.exchanges,
x.exchangeTos,
x.exchangeDetails,
x.exchangeSignkeys,
x.denominations,
@ -659,13 +669,13 @@ export async function updateExchangeFromUrlHandler(
x.recoupGroups,
])
.runReadWrite(async (tx) => {
const r = await tx.exchanges.get(baseUrl);
const r = await tx.exchanges.get(exchangeBaseUrl);
if (!r) {
logger.warn(`exchange ${baseUrl} no longer present`);
logger.warn(`exchange ${exchangeBaseUrl} no longer present`);
return;
}
let existingDetails = await getExchangeDetails(tx, r.baseUrl);
let acceptedTosEtag = undefined;
let acceptedTosEtag: string | undefined = undefined;
if (!existingDetails) {
detailsPointerChanged = true;
}
@ -708,6 +718,21 @@ export async function updateExchangeFromUrlHandler(
const drRowId = await tx.exchangeDetails.put(newDetails);
checkDbInvariant(typeof drRowId.key === "number");
let tosRecord = await tx.exchangeTos.get([
exchangeBaseUrl,
tosDownload.tosEtag,
]);
if (!tosRecord || tosRecord.etag !== existingTosAccepted?.etag) {
tosRecord = {
etag: tosDownload.tosEtag,
exchangeBaseUrl,
termsOfServiceContentType: tosDownload.tosContentType,
termsOfServiceText: tosDownload.tosText,
};
await tx.exchangeTos.put(tosRecord);
}
for (const sk of keysInfo.signingKeys) {
// FIXME: validate signing keys before inserting them
await tx.exchangeSignKeys.put({
@ -726,7 +751,7 @@ export async function updateExchangeFromUrlHandler(
);
for (const currentDenom of keysInfo.currentDenominations) {
const oldDenom = await tx.denominations.get([
baseUrl,
exchangeBaseUrl,
currentDenom.denomPubHash,
]);
if (oldDenom) {
@ -802,6 +827,7 @@ export async function updateExchangeFromUrlHandler(
newlyRevokedCoinPubs,
);
}
return {
exchange: r,
exchangeDetails: newDetails,

View File

@ -631,7 +631,7 @@ async function getExchangeTosStatusDetails(
return {
acceptedVersion: exchangeDetails.tosAccepted?.etag,
content: exchangeTos.termsOfServiceContentType,
content: exchangeTos.termsOfServiceText,
contentType: exchangeTos.termsOfServiceContentType,
currentVersion: exchangeTos.etag,
};