Skip to content

Commit 7b3feb0

Browse files
committed
Emit currency wallets before their engines exist
Hoist a read of publicKey.json + walletCache.json to the top of the engine pixie, ahead of the storage-wallet sync, and seed Redux from it, so the walletApi gate (which drops its engine condition) opens within one pixie tick on a warm login. Without the cache the gate opens on the same conditions as before, keeping cold-start behavior unchanged. makeCurrencyWalletApi loses its engine and tools constructor parameters. Engine-backed methods wait internally via getEngine(), which bails out if the wallet is deleted mid-wait and rethrows engineFailure so a broken plugin surfaces as a rejected call instead of a hang. Mutations that write synced-repo files gate on the storage wallet instead, which loads well before the engine. otherMethods is guaranteed to be {} pre-engine and switches to the engine's bridgified methods once it lands. A cacheSaver sub-pixie watches the cache-relevant Redux slice and persists it per wallet, throttled to one trailing-edge write per 5s, guarded against post-logout writes, and giving up after 3 consecutive failures. Engine creation and start scheduling are unchanged.
1 parent 600301a commit 7b3feb0

2 files changed

Lines changed: 249 additions & 33 deletions

File tree

src/core/currency/wallet/currency-wallet-api.ts

Lines changed: 103 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
EdgeEncodeUri,
2626
EdgeGetReceiveAddressOptions,
2727
EdgeGetTransactionsOptions,
28+
EdgeOtherMethods,
2829
EdgeParsedUri,
2930
EdgePaymentProtocolInfo,
3031
EdgeReceiveAddress,
@@ -45,7 +46,8 @@ import {
4546
} from '../../../types/types'
4647
import { makeMetaTokens } from '../../account/custom-tokens'
4748
import { splitWalletInfo } from '../../login/splitting'
48-
import { toApiInput } from '../../root-pixie'
49+
import { getCurrencyTools } from '../../plugins/plugins-selectors'
50+
import { RootProps, toApiInput } from '../../root-pixie'
4951
import { makeStorageWalletApi } from '../../storage/storage-api'
5052
import { getCurrencyMultiplier } from '../currency-selectors'
5153
import {
@@ -92,36 +94,84 @@ type SavedSpendTargets = EdgeTransaction['spendTargets']
9294
*/
9395
export function makeCurrencyWalletApi(
9496
input: CurrencyWalletInput,
95-
engine: EdgeCurrencyEngine,
96-
tools: EdgeCurrencyTools,
9797
publicWalletInfo: EdgeWalletInfo
9898
): EdgeCurrencyWallet {
9999
const ai = toApiInput(input)
100+
const { walletId } = input.props
100101
const { accountId, pluginId, walletInfo } = input.props.walletState
101102
const plugin = input.props.state.plugins.currency[pluginId]
102103
const { unsafeBroadcastTx = false, unsafeMakeSpend = false } =
103104
plugin.currencyInfo
104105

106+
/**
107+
* The wallet API object exists before the engine does,
108+
* so engine-backed methods wait for the engine internally.
109+
* Bail out if the wallet is deleted mid-wait, and re-throw
110+
* `engineFailure` so a broken plugin surfaces as a rejected
111+
* method call instead of a hang.
112+
*/
113+
function getEngine(): Promise<EdgeCurrencyEngine> {
114+
return ai.waitFor((props: RootProps): EdgeCurrencyEngine | undefined => {
115+
if (props.state.currency.wallets[walletId] == null) {
116+
throw new Error(`Wallet id ${walletId} does not exist in this account`)
117+
}
118+
const { engineFailure } = props.state.currency.wallets[walletId]
119+
if (engineFailure != null) throw engineFailure
120+
return props.output.currency.wallets[walletId]?.engine
121+
})
122+
}
123+
124+
async function getTools(): Promise<EdgeCurrencyTools> {
125+
return await getCurrencyTools(ai, pluginId)
126+
}
127+
128+
/**
129+
* Methods that write synced-repo files need the storage wallet,
130+
* not the engine. The repo loads well before the engine,
131+
* so this wait is much shorter than `getEngine`.
132+
*/
133+
function getStorage(): Promise<true> {
134+
return ai.waitFor((props: RootProps): true | undefined => {
135+
if (props.state.currency.wallets[walletId] == null) {
136+
throw new Error(`Wallet id ${walletId} does not exist in this account`)
137+
}
138+
const { engineFailure } = props.state.currency.wallets[walletId]
139+
if (engineFailure != null) throw engineFailure
140+
if (props.state.storageWallets[walletId] != null) return true
141+
})
142+
}
143+
105144
const storageWalletApi = makeStorageWalletApi(ai, walletInfo)
106145

107146
const fakeCallbacks = makeCurrencyWalletCallbacks(input)
108147

109-
const otherMethods: { [name: string]: (...args: any[]) => any } = {}
110-
if (engine.otherMethods != null) {
111-
for (const name of Object.keys(engine.otherMethods)) {
112-
const method = engine.otherMethods[name]
113-
if (typeof method !== 'function') continue
114-
otherMethods[name] = method
148+
// The core guarantees `otherMethods` is `{}` (never `undefined`) before
149+
// the engine exists, so property probes stay safe on the GUI side.
150+
// Once the engine lands, the pixie watcher's `update` propagates the
151+
// engine's bridgified methods through the same getter:
152+
const emptyOtherMethods = bridgifyObject({})
153+
let engineOtherMethods: EdgeOtherMethods | undefined
154+
155+
function makeEngineOtherMethods(
156+
engine: EdgeCurrencyEngine
157+
): EdgeOtherMethods {
158+
const otherMethods: { [name: string]: (...args: any[]) => any } = {}
159+
if (engine.otherMethods != null) {
160+
for (const name of Object.keys(engine.otherMethods)) {
161+
const method = engine.otherMethods[name]
162+
if (typeof method !== 'function') continue
163+
otherMethods[name] = method
164+
}
115165
}
116-
}
117-
if (engine.otherMethodsWithKeys != null) {
118-
for (const name of Object.keys(engine.otherMethodsWithKeys)) {
119-
const method = engine.otherMethodsWithKeys[name]
120-
if (typeof method !== 'function') continue
121-
otherMethods[name] = (...args) => method(walletInfo.keys, ...args)
166+
if (engine.otherMethodsWithKeys != null) {
167+
for (const name of Object.keys(engine.otherMethodsWithKeys)) {
168+
const method = engine.otherMethodsWithKeys[name]
169+
if (typeof method !== 'function') continue
170+
otherMethods[name] = (...args) => method(walletInfo.keys, ...args)
171+
}
122172
}
173+
return bridgifyObject(otherMethods)
123174
}
124-
bridgifyObject(otherMethods)
125175

126176
const out: EdgeCurrencyWallet & InternalWalletMethods = {
127177
on: onMethod,
@@ -143,8 +193,13 @@ export function makeCurrencyWalletApi(
143193
get localDisklet(): Disklet {
144194
return storageWalletApi.localDisklet
145195
},
146-
publicWalletInfo,
196+
get publicWalletInfo(): EdgeWalletInfo {
197+
// The cache-loaded value may be upgraded (re-derived) later,
198+
// so always serve the latest one from Redux:
199+
return input.props.walletState.publicWalletInfo ?? publicWalletInfo
200+
},
147201
async sync(): Promise<void> {
202+
await getStorage()
148203
await storageWalletApi.sync()
149204
},
150205
get type(): string {
@@ -156,6 +211,7 @@ export function makeCurrencyWalletApi(
156211
return input.props.walletState.name
157212
},
158213
async renameWallet(name: string): Promise<void> {
214+
await getStorage()
159215
await renameCurrencyWallet(input, name)
160216
},
161217

@@ -164,6 +220,7 @@ export function makeCurrencyWalletApi(
164220
return input.props.walletState.fiat
165221
},
166222
async setFiatCurrencyCode(fiatCurrencyCode: string): Promise<void> {
223+
await getStorage()
167224
await setCurrencyWalletFiat(input, fiatCurrencyCode)
168225
},
169226

@@ -206,6 +263,7 @@ export function makeCurrencyWalletApi(
206263
if (input.props.walletState.currencyInfo.hasWalletSettings !== true) {
207264
throw new Error('Wallet settings unsupported')
208265
}
266+
await getStorage()
209267
await saveWalletSettingsFile(input, settings)
210268
},
211269

@@ -270,6 +328,7 @@ export function makeCurrencyWalletApi(
270328

271329
// Transactions history:
272330
async getNumTransactions(opts: EdgeTokenIdOptions): Promise<number> {
331+
const engine = await getEngine()
273332
const upgradedCurrency = upgradeCurrencyCode({
274333
allTokens: input.props.state.accounts[accountId].allTokens[pluginId],
275334
currencyInfo: plugin.currencyInfo,
@@ -282,6 +341,7 @@ export function makeCurrencyWalletApi(
282341
async $internalStreamTransactions(
283342
opts: EdgeStreamTransactionOptions
284343
): Promise<InternalWalletStream> {
344+
const engine = await getEngine()
285345
const {
286346
afterDate,
287347
batchSize = 10,
@@ -416,6 +476,7 @@ export function makeCurrencyWalletApi(
416476
async getAddresses(
417477
opts: EdgeGetReceiveAddressOptions
418478
): Promise<EdgeAddress[]> {
479+
const engine = await getEngine()
419480
if (engine.getAddresses != null) {
420481
return await engine.getAddresses(opts)
421482
} else {
@@ -515,12 +576,15 @@ export function makeCurrencyWalletApi(
515576

516577
// Sending:
517578
async broadcastTx(tx: EdgeTransaction): Promise<EdgeTransaction> {
579+
const engine = await getEngine()
580+
518581
// Only provide wallet info if currency requires it:
519582
const privateKeys = unsafeBroadcastTx ? walletInfo.keys : undefined
520583

521584
return await engine.broadcastTx(tx, { privateKeys })
522585
},
523586
async getMaxSpendable(spendInfo: EdgeSpendInfo): Promise<string> {
587+
const engine = await getEngine()
524588
return await getMaxSpendableInner(
525589
spendInfo,
526590
plugin,
@@ -532,6 +596,7 @@ export function makeCurrencyWalletApi(
532596
async getPaymentProtocolInfo(
533597
paymentProtocolUrl: string
534598
): Promise<EdgePaymentProtocolInfo> {
599+
const engine = await getEngine()
535600
if (engine.getPaymentProtocolInfo == null) {
536601
throw new Error(
537602
"'getPaymentProtocolInfo' is not implemented on wallets of this type"
@@ -540,6 +605,7 @@ export function makeCurrencyWalletApi(
540605
return await engine.getPaymentProtocolInfo(paymentProtocolUrl)
541606
},
542607
async makeSpend(spendInfo: EdgeSpendInfo): Promise<EdgeTransaction> {
608+
const engine = await getEngine()
543609
spendInfo = upgradeMemos(spendInfo, plugin.currencyInfo)
544610
const {
545611
assetAction,
@@ -634,6 +700,7 @@ export function makeCurrencyWalletApi(
634700
return tx
635701
},
636702
async saveTx(transaction: EdgeTransaction): Promise<void> {
703+
const engine = await getEngine()
637704
if (input.props.walletState.txs[transaction.txid] == null) {
638705
const { fileName, txFile } = await setupNewTxMetadata(
639706
input,
@@ -653,6 +720,7 @@ export function makeCurrencyWalletApi(
653720
},
654721

655722
async saveTxAction(opts): Promise<void> {
723+
await getEngine()
656724
const { txid, tokenId, assetAction, savedAction } = opts
657725
await updateCurrencyWalletTxMetadata(
658726
input,
@@ -666,6 +734,7 @@ export function makeCurrencyWalletApi(
666734
},
667735

668736
async saveTxMetadata(opts: EdgeSaveTxMetadataOptions): Promise<void> {
737+
await getEngine()
669738
const { txid, tokenId, metadata } = opts
670739

671740
await updateCurrencyWalletTxMetadata(
@@ -681,6 +750,7 @@ export function makeCurrencyWalletApi(
681750
bytes: Uint8Array,
682751
opts: EdgeSignMessageOptions = {}
683752
): Promise<string> {
753+
const engine = await getEngine()
684754
const privateKeys = walletInfo.keys
685755

686756
if (engine.signBytes != null) {
@@ -706,18 +776,21 @@ export function makeCurrencyWalletApi(
706776
message: string,
707777
opts: EdgeSignMessageOptions = {}
708778
): Promise<string> {
779+
const engine = await getEngine()
709780
if (engine.signMessage == null) {
710781
throw new Error(`${pluginId} doesn't support signing messages`)
711782
}
712783
const privateKeys = walletInfo.keys
713784
return await engine.signMessage(message, privateKeys, opts)
714785
},
715786
async signTx(tx: EdgeTransaction): Promise<EdgeTransaction> {
787+
const engine = await getEngine()
716788
const privateKeys = walletInfo.keys
717789

718790
return await engine.signTx(tx, privateKeys)
719791
},
720792
async sweepPrivateKeys(spendInfo: EdgeSpendInfo): Promise<EdgeTransaction> {
793+
const engine = await getEngine()
721794
if (engine.sweepPrivateKeys == null) {
722795
throw new Error('Sweeping this currency is not supported.')
723796
}
@@ -726,6 +799,7 @@ export function makeCurrencyWalletApi(
726799

727800
// Accelerating:
728801
async accelerate(tx: EdgeTransaction): Promise<EdgeTransaction | null> {
802+
const engine = await getEngine()
729803
if (engine.accelerate == null) return null
730804
return await engine.accelerate(tx)
731805
},
@@ -737,9 +811,11 @@ export function makeCurrencyWalletApi(
737811

738812
// Wallet management:
739813
async dumpData(): Promise<EdgeDataDump> {
814+
const engine = await getEngine()
740815
return await engine.dumpData()
741816
},
742817
async resyncBlockchain(): Promise<void> {
818+
const engine = await getEngine()
743819
const shortId = input.props.walletId.slice(0, 2)
744820
input.props.log.warn(`enabledTokenIds: ${shortId} resyncBlockchain`)
745821
ai.props.dispatch({
@@ -764,6 +840,7 @@ export function makeCurrencyWalletApi(
764840

765841
// URI handling:
766842
async encodeUri(options: EdgeEncodeUri): Promise<string> {
843+
const tools = await getTools()
767844
return await tools.encodeUri(
768845
options,
769846
makeMetaTokens(
@@ -772,6 +849,7 @@ export function makeCurrencyWalletApi(
772849
)
773850
},
774851
async parseUri(uri: string, currencyCode?: string): Promise<EdgeParsedUri> {
852+
const tools = await getTools()
775853
const parsedUri = await tools.parseUri(
776854
uri,
777855
currencyCode,
@@ -792,7 +870,14 @@ export function makeCurrencyWalletApi(
792870
},
793871

794872
// Generic:
795-
otherMethods
873+
get otherMethods(): EdgeOtherMethods {
874+
const engine = input.props.walletOutput?.engine
875+
if (engine == null) return emptyOtherMethods
876+
if (engineOtherMethods == null) {
877+
engineOtherMethods = makeEngineOtherMethods(engine)
878+
}
879+
return engineOtherMethods
880+
}
796881
}
797882

798883
return bridgifyObject(out)

0 commit comments

Comments
 (0)