implement latest recoup protocol

This commit is contained in:
Florian Dold 2022-01-12 15:51:56 +01:00
parent dbdad96b27
commit dc596f1f4d
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 31 additions and 48 deletions

View File

@ -173,11 +173,6 @@ export interface RecoupRequest {
* Signature of TALER_RecoupRequestPS created with the coin's private key. * Signature of TALER_RecoupRequestPS created with the coin's private key.
*/ */
coin_sig: string; coin_sig: string;
/**
* Amount being recouped.
*/
amount: AmountString;
} }
export interface RecoupRefreshRequest { export interface RecoupRefreshRequest {
@ -204,11 +199,6 @@ export interface RecoupRefreshRequest {
* the coin's private key. * the coin's private key.
*/ */
coin_sig: string; coin_sig: string;
/**
* Amount being recouped.
*/
amount: AmountString;
} }
/** /**

View File

@ -157,7 +157,6 @@ export interface CreateRecoupReqRequest {
denomPub: DenominationPubKey; denomPub: DenominationPubKey;
denomPubHash: string; denomPubHash: string;
denomSig: UnblindedSignature; denomSig: UnblindedSignature;
recoupAmount: AmountJson;
} }
/** /**
@ -170,5 +169,4 @@ export interface CreateRecoupRefreshReqRequest {
denomPub: DenominationPubKey; denomPub: DenominationPubKey;
denomPubHash: string; denomPubHash: string;
denomSig: UnblindedSignature; denomSig: UnblindedSignature;
recoupAmount: AmountJson;
} }

View File

@ -263,7 +263,6 @@ export class CryptoImplementation {
const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP) const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP)
.put(decodeCrock(req.denomPubHash)) .put(decodeCrock(req.denomPubHash))
.put(decodeCrock(req.blindingKey)) .put(decodeCrock(req.blindingKey))
.put(amountToBuffer(Amounts.jsonifyAmount(req.recoupAmount)))
.build(); .build();
const coinPriv = decodeCrock(req.coinPriv); const coinPriv = decodeCrock(req.coinPriv);
@ -274,7 +273,6 @@ export class CryptoImplementation {
coin_sig: encodeCrock(coinSig), coin_sig: encodeCrock(coinSig),
denom_pub_hash: req.denomPubHash, denom_pub_hash: req.denomPubHash,
denom_sig: req.denomSig.rsa_signature, denom_sig: req.denomSig.rsa_signature,
amount: Amounts.stringify(req.recoupAmount),
}; };
return paybackRequest; return paybackRequest;
} else { } else {
@ -283,7 +281,6 @@ export class CryptoImplementation {
coin_sig: encodeCrock(coinSig), coin_sig: encodeCrock(coinSig),
denom_pub_hash: req.denomPubHash, denom_pub_hash: req.denomPubHash,
denom_sig: req.denomSig, denom_sig: req.denomSig,
amount: Amounts.stringify(req.recoupAmount),
}; };
return paybackRequest; return paybackRequest;
} }
@ -292,33 +289,32 @@ export class CryptoImplementation {
/** /**
* Create and sign a message to recoup a coin. * Create and sign a message to recoup a coin.
*/ */
createRecoupRefreshRequest(req: CreateRecoupRefreshReqRequest): RecoupRefreshRequest { createRecoupRefreshRequest(
req: CreateRecoupRefreshReqRequest,
): RecoupRefreshRequest {
const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP_REFRESH) const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP_REFRESH)
.put(decodeCrock(req.denomPubHash)) .put(decodeCrock(req.denomPubHash))
.put(decodeCrock(req.blindingKey)) .put(decodeCrock(req.blindingKey))
.put(amountToBuffer(Amounts.jsonifyAmount(req.recoupAmount)))
.build(); .build();
const coinPriv = decodeCrock(req.coinPriv); const coinPriv = decodeCrock(req.coinPriv);
const coinSig = eddsaSign(p, coinPriv); const coinSig = eddsaSign(p, coinPriv);
if (req.denomPub.cipher === DenomKeyType.LegacyRsa) { if (req.denomPub.cipher === DenomKeyType.LegacyRsa) {
const paybackRequest: RecoupRefreshRequest = { const recoupRequest: RecoupRefreshRequest = {
coin_blind_key_secret: req.blindingKey, coin_blind_key_secret: req.blindingKey,
coin_sig: encodeCrock(coinSig), coin_sig: encodeCrock(coinSig),
denom_pub_hash: req.denomPubHash, denom_pub_hash: req.denomPubHash,
denom_sig: req.denomSig.rsa_signature, denom_sig: req.denomSig.rsa_signature,
amount: Amounts.stringify(req.recoupAmount),
}; };
return paybackRequest; return recoupRequest;
} else { } else {
const paybackRequest: RecoupRefreshRequest = { const recoupRequest: RecoupRefreshRequest = {
coin_blind_key_secret: req.blindingKey, coin_blind_key_secret: req.blindingKey,
coin_sig: encodeCrock(coinSig), coin_sig: encodeCrock(coinSig),
denom_pub_hash: req.denomPubHash, denom_pub_hash: req.denomPubHash,
denom_sig: req.denomSig, denom_sig: req.denomSig,
amount: Amounts.stringify(req.recoupAmount),
}; };
return paybackRequest; return recoupRequest;
} }
} }

View File

@ -42,7 +42,6 @@ import {
import { RetryInfo } from "./util/retries.js"; import { RetryInfo } from "./util/retries.js";
import { PayCoinSelection } from "./util/coinSelection.js"; import { PayCoinSelection } from "./util/coinSelection.js";
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge"; import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
import { PendingTaskInfo } from "./pending-types.js";
/** /**
* Name of the Taler database. This is effectively the major * Name of the Taler database. This is effectively the major
@ -140,7 +139,7 @@ export interface ReserveRecord {
reservePriv: string; reservePriv: string;
/** /**
* The exchange base URL. * The exchange base URL for the reserve.
*/ */
exchangeBaseUrl: string; exchangeBaseUrl: string;
@ -154,8 +153,6 @@ export interface ReserveRecord {
*/ */
timestampCreated: Timestamp; timestampCreated: Timestamp;
operationStatus: OperationStatus;
/** /**
* Time when the information about this reserve was posted to the bank. * Time when the information about this reserve was posted to the bank.
* *
@ -206,13 +203,17 @@ export interface ReserveRecord {
*/ */
initialDenomSel: DenomSelectionState; initialDenomSel: DenomSelectionState;
/**
* Current status of the reserve.
*/
reserveStatus: ReserveRecordStatus; reserveStatus: ReserveRecordStatus;
/** /**
* Was a reserve query requested? If so, query again instead * Is there any work to be done for this reserve?
* of going into dormant status. *
* FIXME: Technically redundant, since the reserveStatus would indicate this.
*/ */
requestedQuery: boolean; operationStatus: OperationStatus;
/** /**
* Time of the last successful status query. * Time of the last successful status query.

View File

@ -457,7 +457,6 @@ export async function importBackup(
exchangeBaseUrl: backupExchangeDetails.base_url, exchangeBaseUrl: backupExchangeDetails.base_url,
reservePub, reservePub,
reservePriv: backupReserve.reserve_priv, reservePriv: backupReserve.reserve_priv,
requestedQuery: false,
bankInfo, bankInfo,
timestampCreated: backupReserve.timestamp_created, timestampCreated: backupReserve.timestamp_created,
timestampBankConfirmed: timestampBankConfirmed:

View File

@ -43,6 +43,7 @@ import {
ReserveRecordStatus, ReserveRecordStatus,
WithdrawCoinSource, WithdrawCoinSource,
WalletStoresV1, WalletStoresV1,
OperationStatus,
} from "../db.js"; } from "../db.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js"; import { readSuccessResponseJsonOrThrow } from "../util/http.js";
@ -186,7 +187,6 @@ async function recoupWithdrawCoin(
denomPub: coin.denomPub, denomPub: coin.denomPub,
denomPubHash: coin.denomPubHash, denomPubHash: coin.denomPubHash,
denomSig: coin.denomSig, denomSig: coin.denomSig,
recoupAmount: coin.currentAmount,
}); });
const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl); const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl);
logger.trace(`requesting recoup via ${reqUrl.href}`); logger.trace(`requesting recoup via ${reqUrl.href}`);
@ -233,13 +233,9 @@ async function recoupWithdrawCoin(
updatedCoin.status = CoinStatus.Dormant; updatedCoin.status = CoinStatus.Dormant;
const currency = updatedCoin.currentAmount.currency; const currency = updatedCoin.currentAmount.currency;
updatedCoin.currentAmount = Amounts.getZero(currency); updatedCoin.currentAmount = Amounts.getZero(currency);
if (updatedReserve.reserveStatus === ReserveRecordStatus.DORMANT) { updatedReserve.reserveStatus = ReserveRecordStatus.QUERYING_STATUS;
updatedReserve.reserveStatus = ReserveRecordStatus.QUERYING_STATUS; updatedReserve.retryInfo = initRetryInfo();
updatedReserve.retryInfo = initRetryInfo(); updatedReserve.operationStatus = OperationStatus.Pending;
} else {
updatedReserve.requestedQuery = true;
updatedReserve.retryInfo = initRetryInfo();
}
await tx.coins.put(updatedCoin); await tx.coins.put(updatedCoin);
await tx.reserves.put(updatedReserve); await tx.reserves.put(updatedReserve);
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx); await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
@ -268,9 +264,11 @@ async function recoupRefreshCoin(
denomPub: coin.denomPub, denomPub: coin.denomPub,
denomPubHash: coin.denomPubHash, denomPubHash: coin.denomPubHash,
denomSig: coin.denomSig, denomSig: coin.denomSig,
recoupAmount: coin.currentAmount,
}); });
const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl); const reqUrl = new URL(
`/coins/${coin.coinPub}/recoup-refresh`,
coin.exchangeBaseUrl,
);
logger.trace(`making recoup request for ${coin.coinPub}`); logger.trace(`making recoup request for ${coin.coinPub}`);
const resp = await ws.http.postJson(reqUrl.href, recoupRequest); const resp = await ws.http.postJson(reqUrl.href, recoupRequest);
@ -381,7 +379,7 @@ async function processRecoupGroupImpl(
} }
const ps = recoupGroup.coinPubs.map(async (x, i) => { const ps = recoupGroup.coinPubs.map(async (x, i) => {
try { try {
processRecoup(ws, recoupGroupId, i); await processRecoup(ws, recoupGroupId, i);
} catch (e) { } catch (e) {
logger.warn(`processRecoup failed: ${e}`); logger.warn(`processRecoup failed: ${e}`);
throw e; throw e;
@ -408,7 +406,7 @@ async function processRecoupGroupImpl(
} }
for (const r of reserveSet.values()) { for (const r of reserveSet.values()) {
processReserve(ws, r).catch((e) => { processReserve(ws, r, true).catch((e) => {
logger.error(`processing reserve ${r} after recoup failed`); logger.error(`processing reserve ${r} after recoup failed`);
}); });
} }
@ -460,6 +458,9 @@ export async function createRecoupGroup(
return recoupGroupId; return recoupGroupId;
} }
/**
* Run the recoup protocol for a single coin in a recoup group.
*/
async function processRecoup( async function processRecoup(
ws: InternalWalletState, ws: InternalWalletState,
recoupGroupId: string, recoupGroupId: string,
@ -486,7 +487,7 @@ async function processRecoup(
const coin = await tx.coins.get(coinPub); const coin = await tx.coins.get(coinPub);
if (!coin) { if (!coin) {
throw Error(`Coin ${coinPub} not found, can't request payback`); throw Error(`Coin ${coinPub} not found, can't request recoup`);
} }
return coin; return coin;
}); });

View File

@ -155,7 +155,6 @@ export async function createReserve(
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
lastError: undefined, lastError: undefined,
currency: req.amount.currency, currency: req.amount.currency,
requestedQuery: false,
operationStatus: OperationStatus.Pending, operationStatus: OperationStatus.Pending,
}; };
@ -255,7 +254,6 @@ export async function forceQueryReserve(
reserve.operationStatus = OperationStatus.Pending; reserve.operationStatus = OperationStatus.Pending;
break; break;
default: default:
reserve.requestedQuery = true;
break; break;
} }
reserve.retryInfo = initRetryInfo(); reserve.retryInfo = initRetryInfo();

View File

@ -164,7 +164,7 @@ export async function readUnexpectedResponseDetails(
} }
return makeErrorDetails( return makeErrorDetails(
TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
"Unexpected error code in response", `Unexpected HTTP status (${httpResponse.status}) in response`,
{ {
requestUrl: httpResponse.requestUrl, requestUrl: httpResponse.requestUrl,
httpStatusCode: httpResponse.status, httpStatusCode: httpResponse.status,
@ -220,7 +220,7 @@ export function throwUnexpectedRequestError(
throw new OperationFailedError( throw new OperationFailedError(
makeErrorDetails( makeErrorDetails(
TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
"Unexpected error code in response", `Unexpected HTTP status ${httpResponse.status} in response`,
{ {
requestUrl: httpResponse.requestUrl, requestUrl: httpResponse.requestUrl,
httpStatusCode: httpResponse.status, httpStatusCode: httpResponse.status,