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,23 +314,13 @@ walletCli
logger.info("finished handling API request"); logger.info("finished handling API request");
}); });
walletCli const transactionsCli = walletCli
.subcommand("", "pending", { help: "Show pending operations." }) .subcommand("transactions", "transactions", { help: "Manage transactions." })
.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." })
.maybeOption("currency", ["--currency"], clk.STRING) .maybeOption("currency", ["--currency"], clk.STRING)
.maybeOption("search", ["--search"], clk.STRING) .maybeOption("search", ["--search"], clk.STRING);
.action(async (args) => {
// Default action
transactionsCli.action(async (args) => {
await withWallet(args, async (wallet) => { await withWallet(args, async (wallet) => {
const pending = await wallet.client.call( const pending = await wallet.client.call(
WalletApiOperation.GetTransactions, WalletApiOperation.GetTransactions,
@ -341,21 +331,25 @@ walletCli
); );
console.log(JSON.stringify(pending, undefined, 2)); console.log(JSON.stringify(pending, undefined, 2));
}); });
}); });
walletCli transactionsCli
.subcommand("runPendingOpt", "run-pending", { .subcommand("deleteTransaction", "delete", {
help: "Run pending operations.", 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) => { .action(async (args) => {
await withWallet(args, async (wallet) => { await withWallet(args, async (wallet) => {
await wallet.ws.runPending(args.runPendingOpt.forceNow); await wallet.client.call(WalletApiOperation.DeleteTransaction, {
transactionId: args.deleteTransaction.transactionId,
});
}); });
}); });
walletCli transactionsCli
.subcommand("retryTransaction", "retry-transaction", { .subcommand("retryTransaction", "retry", {
help: "Retry a transaction.", help: "Retry a transaction.",
}) })
.requiredArgument("transactionId", clk.STRING) .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 walletCli
.subcommand("withdraw", "withdraw", { .subcommand("withdraw", "withdraw", {
help: "Withdraw with a taler://withdraw/ URI", help: "Withdraw with a taler://withdraw/ URI",
@ -604,17 +583,26 @@ exchangesCli
exchangesCli exchangesCli
.subcommand("exchangesTosCmd", "tos", { .subcommand("exchangesTosCmd", "tos", {
help: "Show terms of service.", help: "Show/request terms of service.",
}) })
.requiredArgument("url", clk.STRING, { .requiredArgument("url", clk.STRING, {
help: "Base URL of the exchange.", help: "Base URL of the exchange.",
}) })
.maybeOption("contentTypes", ["--content-type"], clk.STRING)
.action(async (args) => { .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) => { await withWallet(args, async (wallet) => {
const tosResult = await wallet.client.call( const tosResult = await wallet.client.call(
WalletApiOperation.GetExchangeTos, WalletApiOperation.GetExchangeTos,
{ {
exchangeBaseUrl: args.exchangesTosCmd.url, exchangeBaseUrl: args.exchangesTosCmd.url,
acceptedFormat,
}, },
); );
console.log(JSON.stringify(tosResult, undefined, 2)); console.log(JSON.stringify(tosResult, undefined, 2));
@ -764,6 +752,29 @@ advancedCli
await withWallet(args, async () => {}); 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 advancedCli
.subcommand("bench1", "bench1", { .subcommand("bench1", "bench1", {
help: "Run the 'bench1' benchmark", help: "Run the 'bench1' benchmark",

View File

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

View File

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