Skip to content

Commit 2bfc435

Browse files
fixup! Cache and restore wallets
fixup! Cache and restore wallets Co-authored-by: Cursor <[email protected]>
1 parent 72ffa1b commit 2bfc435

File tree

6 files changed

+62
-43
lines changed

6 files changed

+62
-43
lines changed

src/core/account/account-pixie.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import { close, update } from 'yaob'
1111
import {
1212
asMaybeOtpError,
1313
EdgeAccount,
14+
EdgeCurrencyInfo,
1415
EdgeCurrencyWallet,
1516
EdgePluginMap,
1617
EdgeTokenMap
1718
} from '../../types/types'
1819
import { makePeriodicTask } from '../../util/periodic-task'
1920
import { snooze } from '../../util/snooze'
20-
import { loadWalletCache } from '../cache/cache-wallet-loader'
21+
import { loadWalletCache, WalletCacheSetup } from '../cache/cache-wallet-loader'
2122
import {
2223
makeWalletCacheSaver,
2324
WalletCacheSaver
@@ -41,6 +42,11 @@ import {
4142

4243
export const EXPEDITED_SYNC_INTERVAL = 5000
4344

45+
/** Returns the disklet path for the account's wallet cache file. */
46+
function getWalletCachePath(storageWalletId: string): string {
47+
return `accountCache/${storageWalletId}/walletCache.json`
48+
}
49+
4450
export interface AccountOutput {
4551
readonly accountApi: EdgeAccount
4652
readonly currencyWallets: { [walletId: string]: EdgeCurrencyWallet }
@@ -95,27 +101,20 @@ const accountPixie: TamePixie<AccountProps> = combinePixies({
95101

96102
// Try to load wallet cache for instant UI.
97103
// Returns the cache setup if successful, undefined otherwise.
98-
async function tryLoadCache(): Promise<
99-
import('../cache/cache-wallet-loader').WalletCacheSetup | undefined
100-
> {
104+
// Assumes storage wallets are already initialized.
105+
async function tryLoadCache(): Promise<WalletCacheSetup | undefined> {
101106
try {
102107
const storageWalletId = accountWalletInfos[0]?.id
103108
if (storageWalletId == null) {
104109
return undefined
105110
}
106111

107-
// Initialize ALL account storage wallets first.
108-
// This is cheap (just file reads) and avoids race conditions.
109-
await Promise.all(
110-
accountWalletInfos.map(info => addStorageWallet(ai, info))
111-
)
112-
113-
const cachePath = `accountCache/${storageWalletId}/walletCache.json`
112+
const cachePath = getWalletCachePath(storageWalletId)
114113
const cacheJson = await ai.props.io.disklet.getText(cachePath)
115114

116115
// Build currency info map from loaded plugins:
117116
const currencyInfos: {
118-
[pluginId: string]: import('../../types/types').EdgeCurrencyInfo
117+
[pluginId: string]: EdgeCurrencyInfo
119118
} = {}
120119
for (const pluginId of Object.keys(state.plugins.currency)) {
121120
currencyInfos[pluginId] =
@@ -148,8 +147,8 @@ const accountPixie: TamePixie<AccountProps> = combinePixies({
148147
// Check for "file not found" errors which are expected on first login:
149148
let isExpectedError = false
150149
if (error instanceof Error) {
151-
// Disklet throws Error with 'Cannot read file' message
152-
if (error.message.includes('Cannot read')) {
150+
// Disklet throws Error with 'Cannot read file ...' message
151+
if (error.message.startsWith('Cannot read file')) {
153152
isExpectedError = true
154153
}
155154
// Node.js-style errors have a 'code' property
@@ -171,6 +170,12 @@ const accountPixie: TamePixie<AccountProps> = combinePixies({
171170
try {
172171
await waitForPlugins(ai)
173172

173+
// Initialize storage wallets (cheap file reads, needed for both paths):
174+
await Promise.all(
175+
accountWalletInfos.map(info => addStorageWallet(ai, info))
176+
)
177+
log.warn('Login: synced account repos')
178+
174179
// Try cache-first login for instant UI:
175180
const cacheSetup = await tryLoadCache()
176181
if (cacheSetup != null) {
@@ -191,6 +196,10 @@ const accountPixie: TamePixie<AccountProps> = combinePixies({
191196
})
192197
.catch((error: unknown) => {
193198
log.error('Login: background loading failed:', error)
199+
input.props.dispatch({
200+
type: 'ACCOUNT_LOAD_FAILED',
201+
payload: { accountId, error }
202+
})
194203
})
195204

196205
return await stopUpdates
@@ -199,12 +208,6 @@ const accountPixie: TamePixie<AccountProps> = combinePixies({
199208
// Normal login flow (no cache available):
200209
await loadBuiltinTokens(ai, accountId)
201210

202-
// Start the repo:
203-
await Promise.all(
204-
accountWalletInfos.map(info => addStorageWallet(ai, info))
205-
)
206-
log.warn('Login: synced account repos')
207-
208211
await loadAllFiles()
209212
log.warn('Login: loaded files')
210213

@@ -342,7 +345,7 @@ const accountPixie: TamePixie<AccountProps> = combinePixies({
342345
if (accountApi != null && cacheSaver == null) {
343346
const storageWalletId = accountState.accountWalletInfos[0]?.id
344347
if (storageWalletId != null) {
345-
const cachePath = `accountCache/${storageWalletId}/walletCache.json`
348+
const cachePath = getWalletCachePath(storageWalletId)
346349

347350
cacheSaver = makeWalletCacheSaver(
348351
accountApi,

src/core/cache/cache-utils.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,28 @@ export function makeRealObjectPoller<T>(
2020
): {
2121
tryGet: () => T | undefined
2222
waitFor: () => Promise<T>
23+
cancel: () => void
2324
} {
2425
let sharedPromise: Promise<T> | undefined
26+
let activeTimeoutId: ReturnType<typeof setTimeout> | undefined
27+
let activeReject: ((error: Error) => void) | undefined
2528

2629
function tryGet(): T | undefined {
2730
return getter()
2831
}
2932

33+
function cancel(): void {
34+
if (activeTimeoutId != null) {
35+
clearTimeout(activeTimeoutId)
36+
activeTimeoutId = undefined
37+
}
38+
if (activeReject != null) {
39+
activeReject(new Error(`Poller for ${label} was cancelled`))
40+
activeReject = undefined
41+
}
42+
sharedPromise = undefined
43+
}
44+
3045
function waitFor(): Promise<T> {
3146
// Fast path: already available
3247
const immediate = tryGet()
@@ -37,20 +52,22 @@ export function makeRealObjectPoller<T>(
3752

3853
sharedPromise = new Promise((resolve, reject) => {
3954
const startTime = Date.now()
40-
let timeoutId: ReturnType<typeof setTimeout> | undefined
55+
activeReject = reject
4156

4257
const cleanup = (): void => {
43-
if (timeoutId != null) {
44-
clearTimeout(timeoutId)
45-
timeoutId = undefined
58+
if (activeTimeoutId != null) {
59+
clearTimeout(activeTimeoutId)
60+
activeTimeoutId = undefined
4661
}
62+
activeReject = undefined
4763
}
4864

4965
const check = (): void => {
5066
try {
5167
const real = tryGet()
5268
if (real != null) {
5369
cleanup()
70+
sharedPromise = undefined
5471
resolve(real)
5572
return
5673
}
@@ -64,21 +81,21 @@ export function makeRealObjectPoller<T>(
6481
return
6582
}
6683

67-
timeoutId = setTimeout(check, POLL_INTERVAL_MS)
84+
activeTimeoutId = setTimeout(check, POLL_INTERVAL_MS)
6885
} catch (error) {
6986
cleanup()
7087
sharedPromise = undefined
7188
reject(error)
7289
}
7390
}
7491

75-
timeoutId = setTimeout(check, POLL_INTERVAL_MS)
92+
activeTimeoutId = setTimeout(check, POLL_INTERVAL_MS)
7693
})
7794

7895
return sharedPromise
7996
}
8097

81-
return { tryGet, waitFor }
98+
return { tryGet, waitFor, cancel }
8299
}
83100

84101
/**

src/core/cache/cache-wallet-loader.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,13 @@ export interface WalletCacheSetup {
3131
* Callback to get a real wallet by ID for delegation.
3232
* Returns undefined if the real wallet is not yet available.
3333
*/
34-
export type RealWalletLookup = (
35-
walletId: string
36-
) => EdgeCurrencyWallet | undefined
34+
type RealWalletLookup = (walletId: string) => EdgeCurrencyWallet | undefined
3735

3836
/**
3937
* Callback to get a real config by pluginId for delegation.
4038
* Returns undefined if the real config is not yet available.
4139
*/
42-
export type RealConfigLookup = (
43-
pluginId: string
44-
) => EdgeCurrencyConfig | undefined
40+
type RealConfigLookup = (pluginId: string) => EdgeCurrencyConfig | undefined
4541

4642
/**
4743
* Options for loading wallet cache.

src/core/cache/cache-wallet-saver.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { Disklet } from 'disklet'
22

33
import { EdgeAccount, EdgeLog } from '../../types/types'
4-
import { PARENT_CURRENCY_KEY, WalletCacheFile } from './cache-wallet-cleaners'
4+
import {
5+
asWalletCacheFile,
6+
PARENT_CURRENCY_KEY,
7+
WalletCacheFile
8+
} from './cache-wallet-cleaners'
59

610
/** Default minimum interval between saves: 5 seconds */
711
const DEFAULT_THROTTLE_MS = 5000
@@ -149,12 +153,14 @@ export function makeWalletCacheSaver(
149153
})
150154
}
151155

152-
const cacheFile: WalletCacheFile = {
156+
// Validate at write time so malformed data is caught immediately
157+
// rather than producing an unusable cache on next login:
158+
const cacheFile: WalletCacheFile = asWalletCacheFile({
153159
version: 1,
154160
tokens,
155161
wallets,
156162
configOtherMethodNames
157-
}
163+
})
158164

159165
const cacheJson = JSON.stringify(cacheFile, null, 2)
160166
await disklet.setText(cachePath, cacheJson)

src/core/cache/cached-currency-wallet.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ export function makeCachedCurrencyWallet(
217217
// ($internalStreamTransactions is called by client-side streamTransactions)
218218
const wallet: EdgeCurrencyWallet & InternalWalletMethods = {
219219
// Note: watch/on callbacks registered on this cached wallet will not fire
220-
// because `update(cachedWallet)` is never called by the pixie system.
220+
// from the pixie system (which only calls update() on real wallets).
221+
// Setters like renameWallet/setFiatCurrencyCode call update(wallet) to
222+
// push local changes through yaob, but watch/on won't fire reactively.
221223
// This is acceptable because:
222224
// - All getters delegate to the real wallet, so reads return live data.
223225
// - The GUI re-grabs wallets from `account.currencyWallets` on re-render,

test/fake/fake-currency-plugin.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
InsufficientFundsError
2525
} from '../../src/index'
2626
import { upgradeCurrencyCode } from '../../src/types/type-helpers'
27-
import { snooze } from '../../src/util/snooze'
2827

2928
const GENESIS_BLOCK = 1231006505
3029

@@ -135,10 +134,6 @@ class FakeCurrencyEngine implements EdgeCurrencyEngine {
135134
: {
136135
testMethod: async (arg: string): Promise<string> => {
137136
return `testMethod called with: ${arg}`
138-
},
139-
testMethodWithDelay: async (arg: string): Promise<string> => {
140-
await snooze(100)
141-
return `delayed: ${arg}`
142137
}
143138
}
144139
this.walletId = walletInfo.id

0 commit comments

Comments
 (0)