wallet-core: simplify coin record
we only track the allocation now, not the remaining amount
This commit is contained in:
parent
4d70391f3d
commit
e075134ffc
@ -475,6 +475,7 @@ export interface BackupRecoupGroup {
|
|||||||
|
|
||||||
timestamp_finish?: TalerProtocolTimestamp;
|
timestamp_finish?: TalerProtocolTimestamp;
|
||||||
finish_clock?: TalerProtocolTimestamp;
|
finish_clock?: TalerProtocolTimestamp;
|
||||||
|
// FIXME: Use some enum here!
|
||||||
finish_is_failure?: boolean;
|
finish_is_failure?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -483,7 +484,6 @@ export interface BackupRecoupGroup {
|
|||||||
coins: {
|
coins: {
|
||||||
coin_pub: string;
|
coin_pub: string;
|
||||||
recoup_finished: boolean;
|
recoup_finished: boolean;
|
||||||
old_amount: BackupAmountString;
|
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -582,9 +582,14 @@ export interface BackupCoin {
|
|||||||
denom_sig: UnblindedSignature;
|
denom_sig: UnblindedSignature;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Amount that's left on the coin.
|
* Information about where and how the coin was spent.
|
||||||
*/
|
*/
|
||||||
current_amount: BackupAmountString;
|
spend_allocation:
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
amount: BackupAmountString;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blinding key used when withdrawing the coin.
|
* Blinding key used when withdrawing the coin.
|
||||||
|
@ -968,60 +968,6 @@ export class WithdrawBatchResponse {
|
|||||||
ev_sigs: WithdrawResponse[];
|
ev_sigs: WithdrawResponse[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Easy to process format for the public data of coins
|
|
||||||
* managed by the wallet.
|
|
||||||
*/
|
|
||||||
export interface CoinDumpJson {
|
|
||||||
coins: Array<{
|
|
||||||
/**
|
|
||||||
* The coin's denomination's public key.
|
|
||||||
*/
|
|
||||||
denom_pub: DenominationPubKey;
|
|
||||||
/**
|
|
||||||
* Hash of denom_pub.
|
|
||||||
*/
|
|
||||||
denom_pub_hash: string;
|
|
||||||
/**
|
|
||||||
* Value of the denomination (without any fees).
|
|
||||||
*/
|
|
||||||
denom_value: string;
|
|
||||||
/**
|
|
||||||
* Public key of the coin.
|
|
||||||
*/
|
|
||||||
coin_pub: string;
|
|
||||||
/**
|
|
||||||
* Base URL of the exchange for the coin.
|
|
||||||
*/
|
|
||||||
exchange_base_url: string;
|
|
||||||
/**
|
|
||||||
* Remaining value on the coin, to the knowledge of
|
|
||||||
* the wallet.
|
|
||||||
*/
|
|
||||||
remaining_value: string;
|
|
||||||
/**
|
|
||||||
* Public key of the parent coin.
|
|
||||||
* Only present if this coin was obtained via refreshing.
|
|
||||||
*/
|
|
||||||
refresh_parent_coin_pub: string | undefined;
|
|
||||||
/**
|
|
||||||
* Public key of the reserve for this coin.
|
|
||||||
* Only present if this coin was obtained via refreshing.
|
|
||||||
*/
|
|
||||||
withdrawal_reserve_pub: string | undefined;
|
|
||||||
/**
|
|
||||||
* Is the coin suspended?
|
|
||||||
* Suspended coins are not considered for payments.
|
|
||||||
*/
|
|
||||||
coin_suspended: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information about the age restriction
|
|
||||||
*/
|
|
||||||
ageCommitmentProof: AgeCommitmentProof | undefined;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MerchantPayResponse {
|
export interface MerchantPayResponse {
|
||||||
sig: string;
|
sig: string;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,10 @@ import {
|
|||||||
ExchangeAuditor,
|
ExchangeAuditor,
|
||||||
UnblindedSignature,
|
UnblindedSignature,
|
||||||
} from "./taler-types.js";
|
} from "./taler-types.js";
|
||||||
import { OrderShortInfo, codecForOrderShortInfo } from "./transactions-types.js";
|
import {
|
||||||
|
OrderShortInfo,
|
||||||
|
codecForOrderShortInfo,
|
||||||
|
} from "./transactions-types.js";
|
||||||
import { BackupRecovery } from "./backup-types.js";
|
import { BackupRecovery } from "./backup-types.js";
|
||||||
import { PaytoUri } from "./payto.js";
|
import { PaytoUri } from "./payto.js";
|
||||||
import { TalerErrorCode } from "./taler-error-codes.js";
|
import { TalerErrorCode } from "./taler-error-codes.js";
|
||||||
@ -141,6 +144,77 @@ export function mkAmount(
|
|||||||
return { value, fraction, currency };
|
return { value, fraction, currency };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status of a coin.
|
||||||
|
*/
|
||||||
|
export enum CoinStatus {
|
||||||
|
/**
|
||||||
|
* Withdrawn and never shown to anybody.
|
||||||
|
*/
|
||||||
|
Fresh = "fresh",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fresh, but currently marked as "suspended", thus won't be used
|
||||||
|
* for spending. Used for testing.
|
||||||
|
*/
|
||||||
|
FreshSuspended = "fresh-suspended",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A coin that has been spent and refreshed.
|
||||||
|
*/
|
||||||
|
Dormant = "dormant",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Easy to process format for the public data of coins
|
||||||
|
* managed by the wallet.
|
||||||
|
*/
|
||||||
|
export interface CoinDumpJson {
|
||||||
|
coins: Array<{
|
||||||
|
/**
|
||||||
|
* The coin's denomination's public key.
|
||||||
|
*/
|
||||||
|
denom_pub: DenominationPubKey;
|
||||||
|
/**
|
||||||
|
* Hash of denom_pub.
|
||||||
|
*/
|
||||||
|
denom_pub_hash: string;
|
||||||
|
/**
|
||||||
|
* Value of the denomination (without any fees).
|
||||||
|
*/
|
||||||
|
denom_value: string;
|
||||||
|
/**
|
||||||
|
* Public key of the coin.
|
||||||
|
*/
|
||||||
|
coin_pub: string;
|
||||||
|
/**
|
||||||
|
* Base URL of the exchange for the coin.
|
||||||
|
*/
|
||||||
|
exchange_base_url: string;
|
||||||
|
/**
|
||||||
|
* Public key of the parent coin.
|
||||||
|
* Only present if this coin was obtained via refreshing.
|
||||||
|
*/
|
||||||
|
refresh_parent_coin_pub: string | undefined;
|
||||||
|
/**
|
||||||
|
* Public key of the reserve for this coin.
|
||||||
|
* Only present if this coin was obtained via refreshing.
|
||||||
|
*/
|
||||||
|
withdrawal_reserve_pub: string | undefined;
|
||||||
|
coin_status: CoinStatus;
|
||||||
|
spend_allocation:
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
amount: string;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
/**
|
||||||
|
* Information about the age restriction
|
||||||
|
*/
|
||||||
|
ageCommitmentProof: AgeCommitmentProof | undefined;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
export enum ConfirmPayResultType {
|
export enum ConfirmPayResultType {
|
||||||
Done = "done",
|
Done = "done",
|
||||||
Pending = "pending",
|
Pending = "pending",
|
||||||
@ -568,10 +642,11 @@ export enum RefreshReason {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for coin public keys.
|
* Request to refresh a single coin.
|
||||||
*/
|
*/
|
||||||
export interface CoinPublicKey {
|
export interface CoinRefreshRequest {
|
||||||
readonly coinPub: string;
|
readonly coinPub: string;
|
||||||
|
readonly amount: AmountJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1105,9 +1105,7 @@ advancedCli
|
|||||||
console.log(`coin ${coin.coin_pub}`);
|
console.log(`coin ${coin.coin_pub}`);
|
||||||
console.log(` exchange ${coin.exchange_base_url}`);
|
console.log(` exchange ${coin.exchange_base_url}`);
|
||||||
console.log(` denomPubHash ${coin.denom_pub_hash}`);
|
console.log(` denomPubHash ${coin.denom_pub_hash}`);
|
||||||
console.log(
|
console.log(` status ${coin.coin_status}`);
|
||||||
` remaining amount ${Amounts.stringify(coin.remaining_value)}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -193,7 +193,7 @@ export async function runRefundIncrementalTest(t: GlobalTestState) {
|
|||||||
.map((x) => x.amountEffective),
|
.map((x) => x.amountEffective),
|
||||||
).amount;
|
).amount;
|
||||||
|
|
||||||
t.assertAmountEquals("TESTKUDOS:8.33", effective);
|
t.assertAmountEquals("TESTKUDOS:8.59", effective);
|
||||||
}
|
}
|
||||||
|
|
||||||
await t.shutdown();
|
await t.shutdown();
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { Amounts } from "@gnu-taler/taler-util";
|
import { Amounts, CoinStatus } from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
|
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
|
||||||
import {
|
import {
|
||||||
@ -32,7 +32,7 @@ import {
|
|||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
WalletCli,
|
WalletCli,
|
||||||
getPayto
|
getPayto,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import { SimpleTestEnvironment } from "../harness/helpers.js";
|
import { SimpleTestEnvironment } from "../harness/helpers.js";
|
||||||
|
|
||||||
@ -184,7 +184,10 @@ export async function runWallettestingTest(t: GlobalTestState) {
|
|||||||
let susp: string | undefined;
|
let susp: string | undefined;
|
||||||
{
|
{
|
||||||
for (const c of coinDump.coins) {
|
for (const c of coinDump.coins) {
|
||||||
if (0 === Amounts.cmp(c.remaining_value, "TESTKUDOS:8")) {
|
if (
|
||||||
|
c.coin_status === CoinStatus.Fresh &&
|
||||||
|
0 === Amounts.cmp(c.denom_value, "TESTKUDOS:8")
|
||||||
|
) {
|
||||||
susp = c.coin_pub;
|
susp = c.coin_pub;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,8 @@ import {
|
|||||||
ExchangeGlobalFees,
|
ExchangeGlobalFees,
|
||||||
DenomSelectionState,
|
DenomSelectionState,
|
||||||
TransactionIdStr,
|
TransactionIdStr,
|
||||||
|
CoinRefreshRequest,
|
||||||
|
CoinStatus,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { RetryInfo, RetryTags } from "./util/retries.js";
|
import { RetryInfo, RetryTags } from "./util/retries.js";
|
||||||
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
|
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
|
||||||
@ -603,27 +605,6 @@ export interface PlanchetRecord {
|
|||||||
ageCommitmentProof?: AgeCommitmentProof;
|
ageCommitmentProof?: AgeCommitmentProof;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Status of a coin.
|
|
||||||
*/
|
|
||||||
export enum CoinStatus {
|
|
||||||
/**
|
|
||||||
* Withdrawn and never shown to anybody.
|
|
||||||
*/
|
|
||||||
Fresh = "fresh",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fresh, but currently marked as "suspended", thus won't be used
|
|
||||||
* for spending. Used for testing.
|
|
||||||
*/
|
|
||||||
FreshSuspended = "fresh-suspended",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A coin that has been spent and refreshed.
|
|
||||||
*/
|
|
||||||
Dormant = "dormant",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CoinSourceType {
|
export enum CoinSourceType {
|
||||||
Withdraw = "withdraw",
|
Withdraw = "withdraw",
|
||||||
Refresh = "refresh",
|
Refresh = "refresh",
|
||||||
@ -692,14 +673,6 @@ export interface CoinRecord {
|
|||||||
*/
|
*/
|
||||||
denomSig: UnblindedSignature;
|
denomSig: UnblindedSignature;
|
||||||
|
|
||||||
/**
|
|
||||||
* Amount that's left on the coin.
|
|
||||||
*
|
|
||||||
* FIXME: This is pretty redundant with "allocation" and "status".
|
|
||||||
* Do we really need this?
|
|
||||||
*/
|
|
||||||
currentAmount: AmountJson;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base URL that identifies the exchange from which we got the
|
* Base URL that identifies the exchange from which we got the
|
||||||
* coin.
|
* coin.
|
||||||
@ -732,7 +705,7 @@ export interface CoinRecord {
|
|||||||
* - Diagnostics
|
* - Diagnostics
|
||||||
* - Idempotency of applying a coin selection (e.g. after re-selection)
|
* - Idempotency of applying a coin selection (e.g. after re-selection)
|
||||||
*/
|
*/
|
||||||
allocation: CoinAllocation | undefined;
|
spendAllocation: CoinAllocation | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum age of purchases that can be made with this coin.
|
* Maximum age of purchases that can be made with this coin.
|
||||||
@ -1461,18 +1434,11 @@ export interface RecoupGroupRecord {
|
|||||||
*/
|
*/
|
||||||
recoupFinishedPerCoin: boolean[];
|
recoupFinishedPerCoin: boolean[];
|
||||||
|
|
||||||
/**
|
|
||||||
* We store old amount (i.e. before recoup) of recouped coins here,
|
|
||||||
* as the balance of a recouped coin is set to zero when the
|
|
||||||
* recoup group is created.
|
|
||||||
*/
|
|
||||||
oldAmountPerCoin: AmountJson[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public keys of coins that should be scheduled for refreshing
|
* Public keys of coins that should be scheduled for refreshing
|
||||||
* after all individual recoups are done.
|
* after all individual recoups are done.
|
||||||
*/
|
*/
|
||||||
scheduleRefreshCoins: string[];
|
scheduleRefreshCoins: CoinRefreshRequest[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BackupProviderStateTag {
|
export enum BackupProviderStateTag {
|
||||||
@ -1875,7 +1841,6 @@ export const WalletStoresV1 = {
|
|||||||
"exchangeTos",
|
"exchangeTos",
|
||||||
describeContents<ExchangeTosRecord>({
|
describeContents<ExchangeTosRecord>({
|
||||||
keyPath: ["exchangeBaseUrl", "etag"],
|
keyPath: ["exchangeBaseUrl", "etag"],
|
||||||
autoIncrement: true,
|
|
||||||
}),
|
}),
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
@ -38,7 +38,7 @@ import {
|
|||||||
CancellationToken,
|
CancellationToken,
|
||||||
DenominationInfo,
|
DenominationInfo,
|
||||||
RefreshGroupId,
|
RefreshGroupId,
|
||||||
CoinPublicKey,
|
CoinRefreshRequest,
|
||||||
RefreshReason,
|
RefreshReason,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { CryptoDispatcher } from "./crypto/workers/cryptoDispatcher.js";
|
import { CryptoDispatcher } from "./crypto/workers/cryptoDispatcher.js";
|
||||||
@ -86,7 +86,7 @@ export interface RefreshOperations {
|
|||||||
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
||||||
coinAvailability: typeof WalletStoresV1.coinAvailability;
|
coinAvailability: typeof WalletStoresV1.coinAvailability;
|
||||||
}>,
|
}>,
|
||||||
oldCoinPubs: CoinPublicKey[],
|
oldCoinPubs: CoinRefreshRequest[],
|
||||||
reason: RefreshReason,
|
reason: RefreshReason,
|
||||||
): Promise<RefreshGroupId>;
|
): Promise<RefreshGroupId>;
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ import {
|
|||||||
BACKUP_VERSION_MINOR,
|
BACKUP_VERSION_MINOR,
|
||||||
canonicalizeBaseUrl,
|
canonicalizeBaseUrl,
|
||||||
canonicalJson,
|
canonicalJson,
|
||||||
|
CoinStatus,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
hash,
|
hash,
|
||||||
@ -63,7 +64,6 @@ import {
|
|||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
CoinStatus,
|
|
||||||
ConfigRecordKey,
|
ConfigRecordKey,
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
PurchaseStatus,
|
PurchaseStatus,
|
||||||
@ -206,7 +206,6 @@ export async function exportBackup(
|
|||||||
coins: recoupGroup.coinPubs.map((x, i) => ({
|
coins: recoupGroup.coinPubs.map((x, i) => ({
|
||||||
coin_pub: x,
|
coin_pub: x,
|
||||||
recoup_finished: recoupGroup.recoupFinishedPerCoin[i],
|
recoup_finished: recoupGroup.recoupFinishedPerCoin[i],
|
||||||
old_amount: Amounts.stringify(recoupGroup.oldAmountPerCoin[i]),
|
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -259,8 +258,13 @@ export async function exportBackup(
|
|||||||
blinding_key: coin.blindingKey,
|
blinding_key: coin.blindingKey,
|
||||||
coin_priv: coin.coinPriv,
|
coin_priv: coin.coinPriv,
|
||||||
coin_source: bcs,
|
coin_source: bcs,
|
||||||
current_amount: Amounts.stringify(coin.currentAmount),
|
|
||||||
fresh: coin.status === CoinStatus.Fresh,
|
fresh: coin.status === CoinStatus.Fresh,
|
||||||
|
spend_allocation: coin.spendAllocation
|
||||||
|
? {
|
||||||
|
amount: coin.spendAllocation.amount,
|
||||||
|
id: coin.spendAllocation.id,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
denom_sig: coin.denomSig,
|
denom_sig: coin.denomSig,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
BackupRefundState,
|
BackupRefundState,
|
||||||
BackupWgType,
|
BackupWgType,
|
||||||
codecForContractTerms,
|
codecForContractTerms,
|
||||||
|
CoinStatus,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
DenomSelectionState,
|
DenomSelectionState,
|
||||||
j2s,
|
j2s,
|
||||||
@ -41,10 +42,8 @@ import {
|
|||||||
CoinRecord,
|
CoinRecord,
|
||||||
CoinSource,
|
CoinSource,
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
CoinStatus,
|
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
DenominationVerificationStatus,
|
DenominationVerificationStatus,
|
||||||
OperationStatus,
|
|
||||||
ProposalDownloadInfo,
|
ProposalDownloadInfo,
|
||||||
PurchaseStatus,
|
PurchaseStatus,
|
||||||
PurchasePayInfo,
|
PurchasePayInfo,
|
||||||
@ -272,7 +271,6 @@ export async function importCoin(
|
|||||||
blindingKey: backupCoin.blinding_key,
|
blindingKey: backupCoin.blinding_key,
|
||||||
coinEvHash: compCoin.coinEvHash,
|
coinEvHash: compCoin.coinEvHash,
|
||||||
coinPriv: backupCoin.coin_priv,
|
coinPriv: backupCoin.coin_priv,
|
||||||
currentAmount: Amounts.parseOrThrow(backupCoin.current_amount),
|
|
||||||
denomSig: backupCoin.denom_sig,
|
denomSig: backupCoin.denom_sig,
|
||||||
coinPub: compCoin.coinPub,
|
coinPub: compCoin.coinPub,
|
||||||
exchangeBaseUrl,
|
exchangeBaseUrl,
|
||||||
@ -284,7 +282,7 @@ export async function importCoin(
|
|||||||
// FIXME!
|
// FIXME!
|
||||||
ageCommitmentProof: undefined,
|
ageCommitmentProof: undefined,
|
||||||
// FIXME!
|
// FIXME!
|
||||||
allocation: undefined,
|
spendAllocation: undefined,
|
||||||
};
|
};
|
||||||
if (coinRecord.status === CoinStatus.Fresh) {
|
if (coinRecord.status === CoinStatus.Fresh) {
|
||||||
await makeCoinAvailable(ws, tx, coinRecord);
|
await makeCoinAvailable(ws, tx, coinRecord);
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
Amounts,
|
Amounts,
|
||||||
Logger,
|
Logger,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { CoinStatus, WalletStoresV1 } from "../db.js";
|
import { WalletStoresV1 } from "../db.js";
|
||||||
import { GetReadOnlyAccess } from "../util/query.js";
|
import { GetReadOnlyAccess } from "../util/query.js";
|
||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
|
|
||||||
@ -42,6 +42,7 @@ export async function getBalancesInsideTransaction(
|
|||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
tx: GetReadOnlyAccess<{
|
tx: GetReadOnlyAccess<{
|
||||||
coins: typeof WalletStoresV1.coins;
|
coins: typeof WalletStoresV1.coins;
|
||||||
|
coinAvailability: typeof WalletStoresV1.coinAvailability;
|
||||||
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
||||||
withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
|
withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
|
||||||
}>,
|
}>,
|
||||||
@ -64,12 +65,14 @@ export async function getBalancesInsideTransaction(
|
|||||||
return balanceStore[currency];
|
return balanceStore[currency];
|
||||||
};
|
};
|
||||||
|
|
||||||
await tx.coins.iter().forEach((c) => {
|
await tx.coinAvailability.iter().forEach((ca) => {
|
||||||
// Only count fresh coins, as dormant coins will
|
const b = initBalance(ca.currency);
|
||||||
// already be in a refresh session.
|
for (let i = 0; i < ca.freshCoinCount; i++) {
|
||||||
if (c.status === CoinStatus.Fresh) {
|
b.available = Amounts.add(b.available, {
|
||||||
const b = initBalance(c.currentAmount.currency);
|
currency: ca.currency,
|
||||||
b.available = Amounts.add(b.available, c.currentAmount).amount;
|
fraction: ca.amountFrac,
|
||||||
|
value: ca.amountVal,
|
||||||
|
}).amount;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -139,7 +142,13 @@ export async function getBalances(
|
|||||||
logger.trace("starting to compute balance");
|
logger.trace("starting to compute balance");
|
||||||
|
|
||||||
const wbal = await ws.db
|
const wbal = await ws.db
|
||||||
.mktx((x) => [x.coins, x.refreshGroups, x.purchases, x.withdrawalGroups])
|
.mktx((x) => [
|
||||||
|
x.coins,
|
||||||
|
x.coinAvailability,
|
||||||
|
x.refreshGroups,
|
||||||
|
x.purchases,
|
||||||
|
x.withdrawalGroups,
|
||||||
|
])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
return getBalancesInsideTransaction(ws, tx);
|
return getBalancesInsideTransaction(ws, tx);
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
import {
|
import {
|
||||||
AmountJson,
|
AmountJson,
|
||||||
Amounts,
|
Amounts,
|
||||||
|
CoinRefreshRequest,
|
||||||
|
CoinStatus,
|
||||||
j2s,
|
j2s,
|
||||||
Logger,
|
Logger,
|
||||||
RefreshReason,
|
RefreshReason,
|
||||||
@ -29,7 +31,7 @@ import {
|
|||||||
TransactionIdStr,
|
TransactionIdStr,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { WalletStoresV1, CoinStatus, CoinRecord } from "../db.js";
|
import { WalletStoresV1, CoinRecord } from "../db.js";
|
||||||
import { makeErrorDetail, TalerError } from "../errors.js";
|
import { makeErrorDetail, TalerError } from "../errors.js";
|
||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
|
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
|
||||||
@ -103,11 +105,19 @@ export async function spendCoins(
|
|||||||
}>,
|
}>,
|
||||||
csi: CoinsSpendInfo,
|
csi: CoinsSpendInfo,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
let refreshCoinPubs: CoinRefreshRequest[] = [];
|
||||||
for (let i = 0; i < csi.coinPubs.length; i++) {
|
for (let i = 0; i < csi.coinPubs.length; i++) {
|
||||||
const coin = await tx.coins.get(csi.coinPubs[i]);
|
const coin = await tx.coins.get(csi.coinPubs[i]);
|
||||||
if (!coin) {
|
if (!coin) {
|
||||||
throw Error("coin allocated for payment doesn't exist anymore");
|
throw Error("coin allocated for payment doesn't exist anymore");
|
||||||
}
|
}
|
||||||
|
const denom = await ws.getDenomInfo(
|
||||||
|
ws,
|
||||||
|
tx,
|
||||||
|
coin.exchangeBaseUrl,
|
||||||
|
coin.denomPubHash,
|
||||||
|
);
|
||||||
|
checkDbInvariant(!!denom);
|
||||||
const coinAvailability = await tx.coinAvailability.get([
|
const coinAvailability = await tx.coinAvailability.get([
|
||||||
coin.exchangeBaseUrl,
|
coin.exchangeBaseUrl,
|
||||||
coin.denomPubHash,
|
coin.denomPubHash,
|
||||||
@ -116,7 +126,7 @@ export async function spendCoins(
|
|||||||
checkDbInvariant(!!coinAvailability);
|
checkDbInvariant(!!coinAvailability);
|
||||||
const contrib = csi.contributions[i];
|
const contrib = csi.contributions[i];
|
||||||
if (coin.status !== CoinStatus.Fresh) {
|
if (coin.status !== CoinStatus.Fresh) {
|
||||||
const alloc = coin.allocation;
|
const alloc = coin.spendAllocation;
|
||||||
if (!alloc) {
|
if (!alloc) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -131,15 +141,18 @@ export async function spendCoins(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
coin.status = CoinStatus.Dormant;
|
coin.status = CoinStatus.Dormant;
|
||||||
coin.allocation = {
|
coin.spendAllocation = {
|
||||||
id: csi.allocationId,
|
id: csi.allocationId,
|
||||||
amount: Amounts.stringify(contrib),
|
amount: Amounts.stringify(contrib),
|
||||||
};
|
};
|
||||||
const remaining = Amounts.sub(coin.currentAmount, contrib);
|
const remaining = Amounts.sub(denom.value, contrib);
|
||||||
if (remaining.saturated) {
|
if (remaining.saturated) {
|
||||||
throw Error("not enough remaining balance on coin for payment");
|
throw Error("not enough remaining balance on coin for payment");
|
||||||
}
|
}
|
||||||
coin.currentAmount = remaining.amount;
|
refreshCoinPubs.push({
|
||||||
|
amount: remaining.amount,
|
||||||
|
coinPub: coin.coinPub,
|
||||||
|
});
|
||||||
checkDbInvariant(!!coinAvailability);
|
checkDbInvariant(!!coinAvailability);
|
||||||
if (coinAvailability.freshCoinCount === 0) {
|
if (coinAvailability.freshCoinCount === 0) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -150,9 +163,6 @@ export async function spendCoins(
|
|||||||
await tx.coins.put(coin);
|
await tx.coins.put(coin);
|
||||||
await tx.coinAvailability.put(coinAvailability);
|
await tx.coinAvailability.put(coinAvailability);
|
||||||
}
|
}
|
||||||
const refreshCoinPubs = csi.coinPubs.map((x) => ({
|
|
||||||
coinPub: x,
|
|
||||||
}));
|
|
||||||
await ws.refreshOps.createRefreshGroup(
|
await ws.refreshOps.createRefreshGroup(
|
||||||
ws,
|
ws,
|
||||||
tx,
|
tx,
|
||||||
|
@ -40,7 +40,8 @@ import {
|
|||||||
codecForMerchantPayResponse,
|
codecForMerchantPayResponse,
|
||||||
codecForProposal,
|
codecForProposal,
|
||||||
CoinDepositPermission,
|
CoinDepositPermission,
|
||||||
CoinPublicKey,
|
CoinRefreshRequest,
|
||||||
|
CoinStatus,
|
||||||
ConfirmPayResult,
|
ConfirmPayResult,
|
||||||
ConfirmPayResultType,
|
ConfirmPayResultType,
|
||||||
ContractTerms,
|
ContractTerms,
|
||||||
@ -78,7 +79,6 @@ import {
|
|||||||
AllowedExchangeInfo,
|
AllowedExchangeInfo,
|
||||||
BackupProviderStateTag,
|
BackupProviderStateTag,
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
CoinStatus,
|
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
PurchaseRecord,
|
PurchaseRecord,
|
||||||
PurchaseStatus,
|
PurchaseStatus,
|
||||||
@ -2084,7 +2084,7 @@ async function applySuccessfulRefund(
|
|||||||
denominations: typeof WalletStoresV1.denominations;
|
denominations: typeof WalletStoresV1.denominations;
|
||||||
}>,
|
}>,
|
||||||
p: PurchaseRecord,
|
p: PurchaseRecord,
|
||||||
refreshCoinsMap: Record<string, { coinPub: string }>,
|
refreshCoinsMap: Record<string, CoinRefreshRequest>,
|
||||||
r: MerchantCoinRefundSuccessStatus,
|
r: MerchantCoinRefundSuccessStatus,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// FIXME: check signature before storing it as valid!
|
// FIXME: check signature before storing it as valid!
|
||||||
@ -2102,31 +2102,23 @@ async function applySuccessfulRefund(
|
|||||||
if (!denom) {
|
if (!denom) {
|
||||||
throw Error("inconsistent database");
|
throw Error("inconsistent database");
|
||||||
}
|
}
|
||||||
refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub };
|
|
||||||
const refundAmount = Amounts.parseOrThrow(r.refund_amount);
|
const refundAmount = Amounts.parseOrThrow(r.refund_amount);
|
||||||
const refundFee = denom.fees.feeRefund;
|
const refundFee = denom.fees.feeRefund;
|
||||||
|
const amountLeft = Amounts.sub(refundAmount, refundFee).amount;
|
||||||
coin.status = CoinStatus.Dormant;
|
coin.status = CoinStatus.Dormant;
|
||||||
coin.currentAmount = Amounts.add(coin.currentAmount, refundAmount).amount;
|
|
||||||
coin.currentAmount = Amounts.sub(coin.currentAmount, refundFee).amount;
|
|
||||||
logger.trace(`coin amount after is ${Amounts.stringify(coin.currentAmount)}`);
|
|
||||||
await tx.coins.put(coin);
|
await tx.coins.put(coin);
|
||||||
|
|
||||||
const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
|
const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
|
||||||
.iter(coin.exchangeBaseUrl)
|
.iter(coin.exchangeBaseUrl)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
const amountLeft = Amounts.sub(
|
|
||||||
Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
|
|
||||||
.amount,
|
|
||||||
denom.fees.feeRefund,
|
|
||||||
).amount;
|
|
||||||
|
|
||||||
const totalRefreshCostBound = getTotalRefreshCost(
|
const totalRefreshCostBound = getTotalRefreshCost(
|
||||||
allDenoms,
|
allDenoms,
|
||||||
DenominationRecord.toDenomInfo(denom),
|
DenominationRecord.toDenomInfo(denom),
|
||||||
amountLeft,
|
amountLeft,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub, amount: amountLeft };
|
||||||
|
|
||||||
p.refunds[refundKey] = {
|
p.refunds[refundKey] = {
|
||||||
type: RefundState.Applied,
|
type: RefundState.Applied,
|
||||||
obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
|
obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
|
||||||
@ -2167,9 +2159,9 @@ async function storePendingRefund(
|
|||||||
.iter(coin.exchangeBaseUrl)
|
.iter(coin.exchangeBaseUrl)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
|
// Refunded amount after fees.
|
||||||
const amountLeft = Amounts.sub(
|
const amountLeft = Amounts.sub(
|
||||||
Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
|
Amounts.parseOrThrow(r.refund_amount),
|
||||||
.amount,
|
|
||||||
denom.fees.feeRefund,
|
denom.fees.feeRefund,
|
||||||
).amount;
|
).amount;
|
||||||
|
|
||||||
@ -2197,7 +2189,7 @@ async function storeFailedRefund(
|
|||||||
denominations: typeof WalletStoresV1.denominations;
|
denominations: typeof WalletStoresV1.denominations;
|
||||||
}>,
|
}>,
|
||||||
p: PurchaseRecord,
|
p: PurchaseRecord,
|
||||||
refreshCoinsMap: Record<string, { coinPub: string }>,
|
refreshCoinsMap: Record<string, CoinRefreshRequest>,
|
||||||
r: MerchantCoinRefundFailureStatus,
|
r: MerchantCoinRefundFailureStatus,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const refundKey = getRefundKey(r);
|
const refundKey = getRefundKey(r);
|
||||||
@ -2221,8 +2213,7 @@ async function storeFailedRefund(
|
|||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
const amountLeft = Amounts.sub(
|
const amountLeft = Amounts.sub(
|
||||||
Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
|
Amounts.parseOrThrow(r.refund_amount),
|
||||||
.amount,
|
|
||||||
denom.fees.feeRefund,
|
denom.fees.feeRefund,
|
||||||
).amount;
|
).amount;
|
||||||
|
|
||||||
@ -2246,6 +2237,7 @@ async function storeFailedRefund(
|
|||||||
if (p.purchaseStatus === PurchaseStatus.AbortingWithRefund) {
|
if (p.purchaseStatus === PurchaseStatus.AbortingWithRefund) {
|
||||||
// Refund failed because the merchant didn't even try to deposit
|
// Refund failed because the merchant didn't even try to deposit
|
||||||
// the coin yet, so we try to refresh.
|
// the coin yet, so we try to refresh.
|
||||||
|
// FIXME: Is this case tested?!
|
||||||
if (r.exchange_code === TalerErrorCode.EXCHANGE_REFUND_DEPOSIT_NOT_FOUND) {
|
if (r.exchange_code === TalerErrorCode.EXCHANGE_REFUND_DEPOSIT_NOT_FOUND) {
|
||||||
const coin = await tx.coins.get(r.coin_pub);
|
const coin = await tx.coins.get(r.coin_pub);
|
||||||
if (!coin) {
|
if (!coin) {
|
||||||
@ -2271,14 +2263,11 @@ async function storeFailedRefund(
|
|||||||
contrib = payCoinSelection.coinContributions[i];
|
contrib = payCoinSelection.coinContributions[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (contrib) {
|
// FIXME: Is this case tested?!
|
||||||
coin.currentAmount = Amounts.add(coin.currentAmount, contrib).amount;
|
refreshCoinsMap[coin.coinPub] = {
|
||||||
coin.currentAmount = Amounts.sub(
|
coinPub: coin.coinPub,
|
||||||
coin.currentAmount,
|
amount: amountLeft,
|
||||||
denom.fees.feeRefund,
|
};
|
||||||
).amount;
|
|
||||||
}
|
|
||||||
refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub };
|
|
||||||
await tx.coins.put(coin);
|
await tx.coins.put(coin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2308,7 +2297,7 @@ async function acceptRefunds(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshCoinsMap: Record<string, CoinPublicKey> = {};
|
const refreshCoinsMap: Record<string, CoinRefreshRequest> = {};
|
||||||
|
|
||||||
for (const refundStatus of refunds) {
|
for (const refundStatus of refunds) {
|
||||||
const refundKey = getRefundKey(refundStatus);
|
const refundKey = getRefundKey(refundStatus);
|
||||||
@ -2350,6 +2339,7 @@ async function acceptRefunds(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const refreshCoinsPubs = Object.values(refreshCoinsMap);
|
const refreshCoinsPubs = Object.values(refreshCoinsMap);
|
||||||
|
logger.info(`refreshCoinMap ${j2s(refreshCoinsMap)}`);
|
||||||
if (refreshCoinsPubs.length > 0) {
|
if (refreshCoinsPubs.length > 0) {
|
||||||
await createRefreshGroup(
|
await createRefreshGroup(
|
||||||
ws,
|
ws,
|
||||||
|
@ -36,6 +36,7 @@ import {
|
|||||||
codecForAmountString,
|
codecForAmountString,
|
||||||
codecForAny,
|
codecForAny,
|
||||||
codecForExchangeGetContractResponse,
|
codecForExchangeGetContractResponse,
|
||||||
|
CoinStatus,
|
||||||
constructPayPullUri,
|
constructPayPullUri,
|
||||||
constructPayPushUri,
|
constructPayPushUri,
|
||||||
ContractTermsUtil,
|
ContractTermsUtil,
|
||||||
@ -63,17 +64,16 @@ import {
|
|||||||
WalletAccountMergeFlags,
|
WalletAccountMergeFlags,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
CoinStatus,
|
|
||||||
WithdrawalGroupStatus,
|
|
||||||
WalletStoresV1,
|
|
||||||
WithdrawalRecordType,
|
|
||||||
ReserveRecord,
|
ReserveRecord,
|
||||||
|
WalletStoresV1,
|
||||||
|
WithdrawalGroupStatus,
|
||||||
|
WithdrawalRecordType,
|
||||||
} from "../db.js";
|
} from "../db.js";
|
||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
|
import { makeTransactionId, spendCoins } from "../operations/common.js";
|
||||||
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
|
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
|
||||||
import { checkDbInvariant } from "../util/invariants.js";
|
import { checkDbInvariant } from "../util/invariants.js";
|
||||||
import { GetReadOnlyAccess } from "../util/query.js";
|
import { GetReadOnlyAccess } from "../util/query.js";
|
||||||
import { spendCoins, makeTransactionId } from "../operations/common.js";
|
|
||||||
import { updateExchangeFromUrl } from "./exchanges.js";
|
import { updateExchangeFromUrl } from "./exchanges.js";
|
||||||
import { internalCreateWithdrawalGroup } from "./withdraw.js";
|
import { internalCreateWithdrawalGroup } from "./withdraw.js";
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ import {
|
|||||||
Amounts,
|
Amounts,
|
||||||
codecForRecoupConfirmation,
|
codecForRecoupConfirmation,
|
||||||
codecForReserveStatus,
|
codecForReserveStatus,
|
||||||
|
CoinStatus,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
j2s,
|
j2s,
|
||||||
@ -40,7 +41,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
CoinStatus,
|
|
||||||
RecoupGroupRecord,
|
RecoupGroupRecord,
|
||||||
RefreshCoinSource,
|
RefreshCoinSource,
|
||||||
WalletStoresV1,
|
WalletStoresV1,
|
||||||
@ -50,6 +50,7 @@ import {
|
|||||||
} from "../db.js";
|
} from "../db.js";
|
||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
|
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
|
||||||
|
import { checkDbInvariant } from "../util/invariants.js";
|
||||||
import { GetReadWriteAccess } from "../util/query.js";
|
import { GetReadWriteAccess } from "../util/query.js";
|
||||||
import {
|
import {
|
||||||
OperationAttemptResult,
|
OperationAttemptResult,
|
||||||
@ -180,8 +181,6 @@ async function recoupWithdrawCoin(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updatedCoin.status = CoinStatus.Dormant;
|
updatedCoin.status = CoinStatus.Dormant;
|
||||||
const currency = updatedCoin.currentAmount.currency;
|
|
||||||
updatedCoin.currentAmount = Amounts.getZero(currency);
|
|
||||||
await tx.coins.put(updatedCoin);
|
await tx.coins.put(updatedCoin);
|
||||||
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
|
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
|
||||||
});
|
});
|
||||||
@ -265,16 +264,25 @@ async function recoupRefreshCoin(
|
|||||||
logger.warn("refresh old coin for recoup not found");
|
logger.warn("refresh old coin for recoup not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
revokedCoin.status = CoinStatus.Dormant;
|
const oldCoinDenom = await ws.getDenomInfo(
|
||||||
oldCoin.currentAmount = Amounts.add(
|
ws,
|
||||||
oldCoin.currentAmount,
|
tx,
|
||||||
recoupGroup.oldAmountPerCoin[coinIdx],
|
oldCoin.exchangeBaseUrl,
|
||||||
).amount;
|
oldCoin.denomPubHash,
|
||||||
logger.trace(
|
|
||||||
"recoup: setting old coin amount to",
|
|
||||||
Amounts.stringify(oldCoin.currentAmount),
|
|
||||||
);
|
);
|
||||||
recoupGroup.scheduleRefreshCoins.push(oldCoin.coinPub);
|
const revokedCoinDenom = await ws.getDenomInfo(
|
||||||
|
ws,
|
||||||
|
tx,
|
||||||
|
revokedCoin.exchangeBaseUrl,
|
||||||
|
revokedCoin.denomPubHash,
|
||||||
|
);
|
||||||
|
checkDbInvariant(!!oldCoinDenom);
|
||||||
|
checkDbInvariant(!!revokedCoinDenom);
|
||||||
|
revokedCoin.status = CoinStatus.Dormant;
|
||||||
|
recoupGroup.scheduleRefreshCoins.push({
|
||||||
|
coinPub: oldCoin.coinPub,
|
||||||
|
amount: Amounts.sub(oldCoinDenom.value, revokedCoinDenom.value).amount,
|
||||||
|
});
|
||||||
await tx.coins.put(revokedCoin);
|
await tx.coins.put(revokedCoin);
|
||||||
await tx.coins.put(oldCoin);
|
await tx.coins.put(oldCoin);
|
||||||
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
|
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
|
||||||
@ -410,7 +418,7 @@ export async function processRecoupGroupHandler(
|
|||||||
const refreshGroupId = await createRefreshGroup(
|
const refreshGroupId = await createRefreshGroup(
|
||||||
ws,
|
ws,
|
||||||
tx,
|
tx,
|
||||||
rg2.scheduleRefreshCoins.map((x) => ({ coinPub: x })),
|
rg2.scheduleRefreshCoins,
|
||||||
RefreshReason.Recoup,
|
RefreshReason.Recoup,
|
||||||
);
|
);
|
||||||
processRefreshGroup(ws, refreshGroupId.refreshGroupId).catch((e) => {
|
processRefreshGroup(ws, refreshGroupId.refreshGroupId).catch((e) => {
|
||||||
@ -442,8 +450,6 @@ export async function createRecoupGroup(
|
|||||||
timestampFinished: undefined,
|
timestampFinished: undefined,
|
||||||
timestampStarted: TalerProtocolTimestamp.now(),
|
timestampStarted: TalerProtocolTimestamp.now(),
|
||||||
recoupFinishedPerCoin: coinPubs.map(() => false),
|
recoupFinishedPerCoin: coinPubs.map(() => false),
|
||||||
// Will be populated later
|
|
||||||
oldAmountPerCoin: [],
|
|
||||||
scheduleRefreshCoins: [],
|
scheduleRefreshCoins: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -454,12 +460,6 @@ export async function createRecoupGroup(
|
|||||||
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
|
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (Amounts.isZero(coin.currentAmount)) {
|
|
||||||
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
recoupGroup.oldAmountPerCoin[coinIdx] = coin.currentAmount;
|
|
||||||
coin.currentAmount = Amounts.getZero(coin.currentAmount.currency);
|
|
||||||
await tx.coins.put(coin);
|
await tx.coins.put(coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,9 @@ import {
|
|||||||
amountToPretty,
|
amountToPretty,
|
||||||
codecForExchangeMeltResponse,
|
codecForExchangeMeltResponse,
|
||||||
codecForExchangeRevealResponse,
|
codecForExchangeRevealResponse,
|
||||||
CoinPublicKey,
|
|
||||||
CoinPublicKeyString,
|
CoinPublicKeyString,
|
||||||
|
CoinRefreshRequest,
|
||||||
|
CoinStatus,
|
||||||
DenominationInfo,
|
DenominationInfo,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
Duration,
|
Duration,
|
||||||
@ -55,9 +56,7 @@ import { CryptoApiStoppedError } from "../crypto/workers/cryptoDispatcher.js";
|
|||||||
import {
|
import {
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
CoinStatus,
|
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
OperationStatus,
|
|
||||||
RefreshCoinStatus,
|
RefreshCoinStatus,
|
||||||
RefreshGroupRecord,
|
RefreshGroupRecord,
|
||||||
RefreshOperationStatus,
|
RefreshOperationStatus,
|
||||||
@ -672,7 +671,6 @@ async function refreshReveal(
|
|||||||
blindingKey: pc.blindingKey,
|
blindingKey: pc.blindingKey,
|
||||||
coinPriv: pc.coinPriv,
|
coinPriv: pc.coinPriv,
|
||||||
coinPub: pc.coinPub,
|
coinPub: pc.coinPub,
|
||||||
currentAmount: ncd.value,
|
|
||||||
denomPubHash: ncd.denomPubHash,
|
denomPubHash: ncd.denomPubHash,
|
||||||
denomSig,
|
denomSig,
|
||||||
exchangeBaseUrl: oldCoin.exchangeBaseUrl,
|
exchangeBaseUrl: oldCoin.exchangeBaseUrl,
|
||||||
@ -684,7 +682,7 @@ async function refreshReveal(
|
|||||||
coinEvHash: pc.coinEvHash,
|
coinEvHash: pc.coinEvHash,
|
||||||
maxAge: pc.maxAge,
|
maxAge: pc.maxAge,
|
||||||
ageCommitmentProof: pc.ageCommitmentProof,
|
ageCommitmentProof: pc.ageCommitmentProof,
|
||||||
allocation: undefined,
|
spendAllocation: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
coins.push(coin);
|
coins.push(coin);
|
||||||
@ -845,7 +843,7 @@ export async function createRefreshGroup(
|
|||||||
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
||||||
coinAvailability: typeof WalletStoresV1.coinAvailability;
|
coinAvailability: typeof WalletStoresV1.coinAvailability;
|
||||||
}>,
|
}>,
|
||||||
oldCoinPubs: CoinPublicKey[],
|
oldCoinPubs: CoinRefreshRequest[],
|
||||||
reason: RefreshReason,
|
reason: RefreshReason,
|
||||||
): Promise<RefreshGroupId> {
|
): Promise<RefreshGroupId> {
|
||||||
const refreshGroupId = encodeCrock(getRandomBytes(32));
|
const refreshGroupId = encodeCrock(getRandomBytes(32));
|
||||||
@ -908,9 +906,8 @@ export async function createRefreshGroup(
|
|||||||
default:
|
default:
|
||||||
assertUnreachable(coin.status);
|
assertUnreachable(coin.status);
|
||||||
}
|
}
|
||||||
const refreshAmount = coin.currentAmount;
|
const refreshAmount = ocp.amount;
|
||||||
inputPerCoin.push(refreshAmount);
|
inputPerCoin.push(refreshAmount);
|
||||||
coin.currentAmount = Amounts.getZero(refreshAmount.currency);
|
|
||||||
await tx.coins.put(coin);
|
await tx.coins.put(coin);
|
||||||
const denoms = await getDenoms(coin.exchangeBaseUrl);
|
const denoms = await getDenoms(coin.exchangeBaseUrl);
|
||||||
const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
|
const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
|
||||||
@ -1008,7 +1005,7 @@ export async function autoRefresh(
|
|||||||
const coins = await tx.coins.indexes.byBaseUrl
|
const coins = await tx.coins.indexes.byBaseUrl
|
||||||
.iter(exchangeBaseUrl)
|
.iter(exchangeBaseUrl)
|
||||||
.toArray();
|
.toArray();
|
||||||
const refreshCoins: CoinPublicKey[] = [];
|
const refreshCoins: CoinRefreshRequest[] = [];
|
||||||
for (const coin of coins) {
|
for (const coin of coins) {
|
||||||
if (coin.status !== CoinStatus.Fresh) {
|
if (coin.status !== CoinStatus.Fresh) {
|
||||||
continue;
|
continue;
|
||||||
@ -1023,7 +1020,14 @@ export async function autoRefresh(
|
|||||||
}
|
}
|
||||||
const executeThreshold = getAutoRefreshExecuteThreshold(denom);
|
const executeThreshold = getAutoRefreshExecuteThreshold(denom);
|
||||||
if (AbsoluteTime.isExpired(executeThreshold)) {
|
if (AbsoluteTime.isExpired(executeThreshold)) {
|
||||||
refreshCoins.push(coin);
|
refreshCoins.push({
|
||||||
|
coinPub: coin.coinPub,
|
||||||
|
amount: {
|
||||||
|
value: denom.amountVal,
|
||||||
|
fraction: denom.amountFrac,
|
||||||
|
currency: denom.currency,
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const checkThreshold = getAutoRefreshCheckThreshold(denom);
|
const checkThreshold = getAutoRefreshCheckThreshold(denom);
|
||||||
minCheckThreshold = AbsoluteTime.min(
|
minCheckThreshold = AbsoluteTime.min(
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
BlindedDenominationSignature,
|
BlindedDenominationSignature,
|
||||||
codecForMerchantTipResponseV2,
|
codecForMerchantTipResponseV2,
|
||||||
codecForTipPickupGetResponse,
|
codecForTipPickupGetResponse,
|
||||||
|
CoinStatus,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
@ -41,7 +42,6 @@ import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
|
|||||||
import {
|
import {
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
CoinStatus,
|
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
TipRecord,
|
TipRecord,
|
||||||
} from "../db.js";
|
} from "../db.js";
|
||||||
@ -311,7 +311,6 @@ export async function processTip(
|
|||||||
coinIndex: i,
|
coinIndex: i,
|
||||||
walletTipId: walletTipId,
|
walletTipId: walletTipId,
|
||||||
},
|
},
|
||||||
currentAmount: DenominationRecord.getValue(denom),
|
|
||||||
denomPubHash: denom.denomPubHash,
|
denomPubHash: denom.denomPubHash,
|
||||||
denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig },
|
denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig },
|
||||||
exchangeBaseUrl: tipRecord.exchangeBaseUrl,
|
exchangeBaseUrl: tipRecord.exchangeBaseUrl,
|
||||||
@ -319,7 +318,7 @@ export async function processTip(
|
|||||||
coinEvHash: planchet.coinEvHash,
|
coinEvHash: planchet.coinEvHash,
|
||||||
maxAge: AgeRestriction.AGE_UNRESTRICTED,
|
maxAge: AgeRestriction.AGE_UNRESTRICTED,
|
||||||
ageCommitmentProof: planchet.ageCommitmentProof,
|
ageCommitmentProof: planchet.ageCommitmentProof,
|
||||||
allocation: undefined,
|
spendAllocation: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -540,7 +540,6 @@ function buildTransactionForTip(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* For a set of refund with the same executionTime.
|
* For a set of refund with the same executionTime.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
interface MergedRefundInfo {
|
interface MergedRefundInfo {
|
||||||
executionTime: TalerProtocolTimestamp;
|
executionTime: TalerProtocolTimestamp;
|
||||||
@ -556,7 +555,7 @@ function mergeRefundByExecutionTime(
|
|||||||
const refundByExecTime = rs.reduce((prev, refund) => {
|
const refundByExecTime = rs.reduce((prev, refund) => {
|
||||||
const key = `${refund.executionTime.t_s}`;
|
const key = `${refund.executionTime.t_s}`;
|
||||||
|
|
||||||
//refunds counts if applied
|
// refunds count if applied
|
||||||
const effective =
|
const effective =
|
||||||
refund.type === RefundState.Applied
|
refund.type === RefundState.Applied
|
||||||
? Amounts.sub(
|
? Amounts.sub(
|
||||||
@ -582,7 +581,10 @@ function mergeRefundByExecutionTime(
|
|||||||
v.amountAppliedEffective,
|
v.amountAppliedEffective,
|
||||||
effective,
|
effective,
|
||||||
).amount;
|
).amount;
|
||||||
v.amountAppliedRaw = Amounts.add(v.amountAppliedRaw).amount;
|
v.amountAppliedRaw = Amounts.add(
|
||||||
|
v.amountAppliedRaw,
|
||||||
|
refund.refundAmount,
|
||||||
|
).amount;
|
||||||
v.firstTimestamp = TalerProtocolTimestamp.min(
|
v.firstTimestamp = TalerProtocolTimestamp.min(
|
||||||
v.firstTimestamp,
|
v.firstTimestamp,
|
||||||
refund.obtainedTime,
|
refund.obtainedTime,
|
||||||
|
@ -36,6 +36,7 @@ import {
|
|||||||
codecForWithdrawBatchResponse,
|
codecForWithdrawBatchResponse,
|
||||||
codecForWithdrawOperationStatusResponse,
|
codecForWithdrawOperationStatusResponse,
|
||||||
codecForWithdrawResponse,
|
codecForWithdrawResponse,
|
||||||
|
CoinStatus,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
DenomSelectionState,
|
DenomSelectionState,
|
||||||
Duration,
|
Duration,
|
||||||
@ -57,7 +58,6 @@ import {
|
|||||||
TransactionType,
|
TransactionType,
|
||||||
UnblindedSignature,
|
UnblindedSignature,
|
||||||
URL,
|
URL,
|
||||||
VersionMatchResult,
|
|
||||||
WithdrawBatchResponse,
|
WithdrawBatchResponse,
|
||||||
WithdrawResponse,
|
WithdrawResponse,
|
||||||
WithdrawUriInfoResponse,
|
WithdrawUriInfoResponse,
|
||||||
@ -66,10 +66,8 @@ import { EddsaKeypair } from "../crypto/cryptoImplementation.js";
|
|||||||
import {
|
import {
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
CoinStatus,
|
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
DenominationVerificationStatus,
|
DenominationVerificationStatus,
|
||||||
ExchangeTosRecord,
|
|
||||||
PlanchetRecord,
|
PlanchetRecord,
|
||||||
PlanchetStatus,
|
PlanchetStatus,
|
||||||
WalletStoresV1,
|
WalletStoresV1,
|
||||||
@ -736,7 +734,6 @@ async function processPlanchetVerifyAndStoreCoin(
|
|||||||
blindingKey: planchet.blindingKey,
|
blindingKey: planchet.blindingKey,
|
||||||
coinPriv: planchet.coinPriv,
|
coinPriv: planchet.coinPriv,
|
||||||
coinPub: planchet.coinPub,
|
coinPub: planchet.coinPub,
|
||||||
currentAmount: denomInfo.value,
|
|
||||||
denomPubHash: planchet.denomPubHash,
|
denomPubHash: planchet.denomPubHash,
|
||||||
denomSig,
|
denomSig,
|
||||||
coinEvHash: planchet.coinEvHash,
|
coinEvHash: planchet.coinEvHash,
|
||||||
@ -750,7 +747,7 @@ async function processPlanchetVerifyAndStoreCoin(
|
|||||||
},
|
},
|
||||||
maxAge: planchet.maxAge,
|
maxAge: planchet.maxAge,
|
||||||
ageCommitmentProof: planchet.ageCommitmentProof,
|
ageCommitmentProof: planchet.ageCommitmentProof,
|
||||||
allocation: undefined,
|
spendAllocation: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const planchetCoinPub = planchet.coinPub;
|
const planchetCoinPub = planchet.coinPub;
|
||||||
|
@ -95,6 +95,8 @@ import {
|
|||||||
WalletNotification,
|
WalletNotification,
|
||||||
codecForSetDevModeRequest,
|
codecForSetDevModeRequest,
|
||||||
ExchangeTosStatusDetails,
|
ExchangeTosStatusDetails,
|
||||||
|
CoinRefreshRequest,
|
||||||
|
CoinStatus,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
|
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
|
||||||
import {
|
import {
|
||||||
@ -105,11 +107,9 @@ import { clearDatabase } from "./db-utils.js";
|
|||||||
import {
|
import {
|
||||||
AuditorTrustRecord,
|
AuditorTrustRecord,
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
CoinStatus,
|
|
||||||
ConfigRecordKey,
|
ConfigRecordKey,
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
ExchangeDetailsRecord,
|
ExchangeDetailsRecord,
|
||||||
ExchangeTosRecord,
|
|
||||||
exportDb,
|
exportDb,
|
||||||
importDb,
|
importDb,
|
||||||
WalletStoresV1,
|
WalletStoresV1,
|
||||||
@ -934,10 +934,15 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
|
|||||||
}),
|
}),
|
||||||
exchange_base_url: c.exchangeBaseUrl,
|
exchange_base_url: c.exchangeBaseUrl,
|
||||||
refresh_parent_coin_pub: refreshParentCoinPub,
|
refresh_parent_coin_pub: refreshParentCoinPub,
|
||||||
remaining_value: Amounts.stringify(c.currentAmount),
|
|
||||||
withdrawal_reserve_pub: withdrawalReservePub,
|
withdrawal_reserve_pub: withdrawalReservePub,
|
||||||
coin_suspended: c.status === CoinStatus.FreshSuspended,
|
coin_status: c.status,
|
||||||
ageCommitmentProof: c.ageCommitmentProof,
|
ageCommitmentProof: c.ageCommitmentProof,
|
||||||
|
spend_allocation: c.spendAllocation
|
||||||
|
? {
|
||||||
|
amount: c.spendAllocation.amount,
|
||||||
|
id: c.spendAllocation.id,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1153,7 +1158,6 @@ async function dispatchRequestInternal(
|
|||||||
}
|
}
|
||||||
case "forceRefresh": {
|
case "forceRefresh": {
|
||||||
const req = codecForForceRefreshRequest().decode(payload);
|
const req = codecForForceRefreshRequest().decode(payload);
|
||||||
const coinPubs = req.coinPubList.map((x) => ({ coinPub: x }));
|
|
||||||
const refreshGroupId = await ws.db
|
const refreshGroupId = await ws.db
|
||||||
.mktx((x) => [
|
.mktx((x) => [
|
||||||
x.refreshGroups,
|
x.refreshGroups,
|
||||||
@ -1162,6 +1166,24 @@ async function dispatchRequestInternal(
|
|||||||
x.coins,
|
x.coins,
|
||||||
])
|
])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
|
let coinPubs: CoinRefreshRequest[] = [];
|
||||||
|
for (const c of req.coinPubList) {
|
||||||
|
const coin = await tx.coins.get(c);
|
||||||
|
if (!coin) {
|
||||||
|
throw Error(`coin (pubkey ${c}) not found`);
|
||||||
|
}
|
||||||
|
const denom = await ws.getDenomInfo(
|
||||||
|
ws,
|
||||||
|
tx,
|
||||||
|
coin.exchangeBaseUrl,
|
||||||
|
coin.denomPubHash,
|
||||||
|
);
|
||||||
|
checkDbInvariant(!!denom);
|
||||||
|
coinPubs.push({
|
||||||
|
coinPub: c,
|
||||||
|
amount: denom?.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
return await createRefreshGroup(
|
return await createRefreshGroup(
|
||||||
ws,
|
ws,
|
||||||
tx,
|
tx,
|
||||||
|
Loading…
Reference in New Issue
Block a user