diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-12-10 21:34:56 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-12-10 21:34:56 +0100 |
commit | 6947e79bbc258f7bc96af424ddb71a511f0c15a3 (patch) | |
tree | 26e9d8206ccda54f3a7a5c7e23b322417ea5b85b /src | |
parent | b855c547fba01106a4a8c3699fd451bdfcadcf8d (diff) |
implement db garbage collection (fixes #4526, #4188)
Diffstat (limited to 'src')
-rw-r--r-- | src/types.ts | 7 | ||||
-rw-r--r-- | src/wallet.ts | 93 |
2 files changed, 95 insertions, 5 deletions
diff --git a/src/types.ts b/src/types.ts index 39f0a850d..ec4929c33 100644 --- a/src/types.ts +++ b/src/types.ts @@ -506,6 +506,13 @@ export interface ExchangeRecord { lastUpdateTime: number; /** + * When did we actually use this exchange last (in milliseconds). If we + * never used the exchange for anything but just updated its info, this is + * set to 0. (Currently only updated when reserves are created.) + */ + lastUsedTime: number; + + /** * Last observed protocol version. */ protocolVersion?: string; diff --git a/src/wallet.ts b/src/wallet.ts index 744eba16a..13f4dc6c2 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -660,13 +660,17 @@ export class Wallet { this.badge = badge; this.notifier = notifier; this.cryptoApi = new CryptoApi(); - - this.fillDefaults(); - this.resumePendingFromDb(); - this.timerGroup = new TimerGroup(); - this.timerGroup.every(1000 * 60 * 15, () => this.updateExchanges()); + const init = async () => { + await this.fillDefaults().catch((e) => console.log(e)); + await this.collectGarbage().catch((e) => console.log(e)); + this.updateExchanges(); + this.resumePendingFromDb(); + this.timerGroup.every(1000 * 60 * 15, () => this.updateExchanges()); + }; + + init(); } private async fillDefaults() { @@ -1216,6 +1220,19 @@ export class Wallet { /** + * Update the timestamp of when an exchange was used. + */ + async updateExchangeUsedTime(exchangeBaseUrl: string): Promise<void> { + const now = (new Date()).getTime(); + const update = (r: ExchangeRecord) => { + r.lastUsedTime = now; + return r; + }; + await this.q().mutate(Stores.exchanges, exchangeBaseUrl, update).finish(); + } + + + /** * Create a reserve, but do not flag it as confirmed yet. * * Adds the corresponding exchange as a trusted exchange if it is neither @@ -1240,6 +1257,7 @@ export class Wallet { timestamp_depleted: 0, }; + await this.updateExchangeUsedTime(req.exchange); const exchangeInfo = await this.updateExchangeFromUrl(req.exchange); const {isAudited, isTrusted} = await this.getExchangeTrust(exchangeInfo); let currencyRecord = await this.q().get(Stores.currencies, exchangeInfo.currency); @@ -1734,6 +1752,7 @@ export class Wallet { baseUrl, currency: exchangeKeysJson.denoms[0].value.currency, lastUpdateTime: updateTimeSec, + lastUsedTime: 0, masterPublicKey: exchangeKeysJson.master_public_key, }; console.log("making fresh exchange"); @@ -2949,4 +2968,68 @@ export class Wallet { }; return tipStatus; } + + /** + * Remove unreferenced / expired data from the wallet's database + * based on the current system time. + */ + async collectGarbage() { + const nowMilli = (new Date()).getTime(); + const nowSec = Math.floor(nowMilli / 1000); + + const gcReserve = (r: ReserveRecord, n: number) => { + // This rule to purge reserves is a bit over-eager, since we still might + // receive an emergency payback from the exchange. In this case we need + // to wait for the exchange to wire the money back or change this rule to + // wait until all coins from the reserve were spent. + if (r.timestamp_depleted) { + return true; + } + return false; + }; + await this.q().deleteIf(Stores.reserves, gcReserve).finish(); + + const gcProposal = (d: ProposalRecord, n: number) => { + // Delete proposal after 60 minutes or 5 minutes before pay deadline, + // whatever comes first. + let deadlinePayMilli = getTalerStampSec(d.contractTerms.pay_deadline)! * 1000; + let deadlineExpireMilli = nowMilli + (1000 * 60 * 60); + return d.timestamp < Math.min(deadlinePayMilli, deadlineExpireMilli); + }; + await this.q().deleteIf(Stores.proposals, gcProposal).finish(); + + const activeExchanges: string[] = []; + const gcExchange = (d: ExchangeRecord, n: number) => { + // Delete if if unused and last update more than 20 minutes ago + if (!d.lastUsedTime && nowMilli > d.lastUpdateTime + (1000 * 60 * 20)) { + return true; + } + activeExchanges.push(d.baseUrl); + return false; + } + + await this.q().deleteIf(Stores.exchanges, gcExchange).finish(); + + const gcDenominations = (d: DenominationRecord, n: number) => { + if (nowSec > getTalerStampSec(d.stampExpireDeposit)!) { + return true; + } + if (activeExchanges.indexOf(d.exchangeBaseUrl) < 0) { + return true; + } + return false; + }; + await this.q().deleteIf(Stores.denominations, gcDenominations).finish(); + + const gcWireFees = (r: ExchangeWireFeesRecord, n: number) => { + if (activeExchanges.indexOf(r.exchangeBaseUrl) < 0) { + return true; + } + return false; + }; + await this.q().deleteIf(Stores.exchangeWireFees, gcWireFees).finish(); + + + // FIXME(#5210) also GC coins + } } |