2019-12-02 00:42:40 +01:00
|
|
|
/*
|
|
|
|
This file is part of GNU Taler
|
|
|
|
(C) 2019 GNUnet e.V.
|
|
|
|
|
|
|
|
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/>
|
|
|
|
*/
|
|
|
|
|
|
|
|
import { HttpRequestLibrary } from "../util/http";
|
2020-07-28 19:47:12 +02:00
|
|
|
import { NextUrlResult, BalancesResponse } from "../types/walletTypes";
|
2019-12-05 19:38:19 +01:00
|
|
|
import { CryptoApi, CryptoWorkerFactory } from "../crypto/workers/cryptoApi";
|
|
|
|
import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo";
|
|
|
|
import { Logger } from "../util/logging";
|
2019-12-12 20:53:15 +01:00
|
|
|
import { PendingOperationsResponse } from "../types/pending";
|
|
|
|
import { WalletNotification } from "../types/notifications";
|
2019-12-12 22:39:45 +01:00
|
|
|
import { Database } from "../util/query";
|
2020-08-18 14:53:06 +02:00
|
|
|
import { openPromise, OpenedPromise } from "../util/promiseUtils";
|
2019-12-05 19:38:19 +01:00
|
|
|
|
|
|
|
type NotificationListener = (n: WalletNotification) => void;
|
|
|
|
|
|
|
|
const logger = new Logger("state.ts");
|
|
|
|
|
2020-08-18 14:53:06 +02:00
|
|
|
export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
|
|
|
|
export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock";
|
|
|
|
|
2019-12-05 19:38:19 +01:00
|
|
|
export class InternalWalletState {
|
|
|
|
cachedNextUrl: { [fulfillmentUrl: string]: NextUrlResult } = {};
|
|
|
|
memoProcessReserve: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
|
|
|
memoMakePlanchet: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
|
|
|
memoGetPending: AsyncOpMemoSingle<
|
|
|
|
PendingOperationsResponse
|
|
|
|
> = new AsyncOpMemoSingle();
|
2020-07-28 19:47:12 +02:00
|
|
|
memoGetBalance: AsyncOpMemoSingle<BalancesResponse> = new AsyncOpMemoSingle();
|
2019-12-05 19:38:19 +01:00
|
|
|
memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
2020-03-11 20:14:28 +01:00
|
|
|
memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
2019-12-02 00:42:40 +01:00
|
|
|
cryptoApi: CryptoApi;
|
2019-12-05 19:38:19 +01:00
|
|
|
|
|
|
|
listeners: NotificationListener[] = [];
|
|
|
|
|
2020-08-18 14:53:06 +02:00
|
|
|
/**
|
|
|
|
* Promises that are waiting for a particular resource.
|
|
|
|
*/
|
|
|
|
private resourceWaiters: Record<string, OpenedPromise<void>[]> = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resources that are currently locked.
|
|
|
|
*/
|
|
|
|
private resourceLocks: Set<string> = new Set();
|
|
|
|
|
2019-12-05 19:38:19 +01:00
|
|
|
constructor(
|
2019-12-12 22:39:45 +01:00
|
|
|
public db: Database,
|
2019-12-05 19:38:19 +01:00
|
|
|
public http: HttpRequestLibrary,
|
|
|
|
cryptoWorkerFactory: CryptoWorkerFactory,
|
|
|
|
) {
|
|
|
|
this.cryptoApi = new CryptoApi(cryptoWorkerFactory);
|
|
|
|
}
|
|
|
|
|
2020-08-18 14:53:06 +02:00
|
|
|
notify(n: WalletNotification): void {
|
2019-12-05 19:38:19 +01:00
|
|
|
logger.trace("Notification", n);
|
|
|
|
for (const l of this.listeners) {
|
|
|
|
const nc = JSON.parse(JSON.stringify(n));
|
2020-04-06 17:35:51 +02:00
|
|
|
setTimeout(() => {
|
2019-12-05 19:38:19 +01:00
|
|
|
l(nc);
|
2020-04-06 17:35:51 +02:00
|
|
|
}, 0);
|
2019-12-05 19:38:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addNotificationListener(f: (n: WalletNotification) => void): void {
|
|
|
|
this.listeners.push(f);
|
|
|
|
}
|
2020-08-18 14:53:06 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Run an async function after acquiring a list of locks, identified
|
|
|
|
* by string tokens.
|
|
|
|
*/
|
|
|
|
async runSequentialized<T>(tokens: string[], f: () => Promise<T>) {
|
|
|
|
// Make sure locks are always acquired in the same order
|
|
|
|
tokens = [... tokens].sort();
|
|
|
|
|
|
|
|
for (const token of tokens) {
|
|
|
|
if (this.resourceLocks.has(token)) {
|
|
|
|
const p = openPromise<void>();
|
|
|
|
let waitList = this.resourceWaiters[token];
|
|
|
|
if (!waitList) {
|
|
|
|
waitList = this.resourceWaiters[token] = [];
|
|
|
|
}
|
|
|
|
waitList.push(p);
|
|
|
|
await p.promise;
|
|
|
|
}
|
|
|
|
this.resourceLocks.add(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
logger.trace(`begin exclusive execution on ${JSON.stringify(tokens)}`);
|
|
|
|
const result = await f();
|
|
|
|
logger.trace(`end exclusive execution on ${JSON.stringify(tokens)}`);
|
|
|
|
return result;
|
|
|
|
} finally {
|
|
|
|
for (const token of tokens) {
|
|
|
|
this.resourceLocks.delete(token);
|
|
|
|
let waiter = (this.resourceWaiters[token] ?? []).shift();
|
|
|
|
if (waiter) {
|
|
|
|
waiter.resolve();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-12-05 19:38:19 +01:00
|
|
|
}
|