Browse Source

Prevent locks to happen during scanning

master
Gaëtan Renaudeau 7 years ago
parent
commit
a044c149c0
  1. 4
      src/commands/libcoreScanAccounts.js
  2. 9
      src/components/DeviceBusyIndicator.js
  3. 11
      src/helpers/deviceAccess.js
  4. 113
      src/helpers/libcore.js
  5. 3
      src/helpers/withLibcore.js
  6. 4
      src/renderer/events.js

4
src/commands/libcoreScanAccounts.js

@ -17,6 +17,7 @@ const cmd: Command<Input, Result> = createCommand(
'libcoreScanAccounts', 'libcoreScanAccounts',
({ devicePath, currencyId }) => ({ devicePath, currencyId }) =>
Observable.create(o => { Observable.create(o => {
let unsubscribed = false
// TODO scanAccountsOnDevice should directly return a Observable so we just have to pass-in // TODO scanAccountsOnDevice should directly return a Observable so we just have to pass-in
withLibcore(core => withLibcore(core =>
scanAccountsOnDevice({ scanAccountsOnDevice({
@ -26,6 +27,7 @@ const cmd: Command<Input, Result> = createCommand(
onAccountScanned: account => { onAccountScanned: account => {
o.next(account) o.next(account)
}, },
isUnsubscribed: () => unsubscribed,
}).then( }).then(
() => { () => {
o.complete() o.complete()
@ -37,7 +39,7 @@ const cmd: Command<Input, Result> = createCommand(
) )
function unsubscribe() { function unsubscribe() {
// FIXME not implemented unsubscribed = true
} }
return unsubscribe return unsubscribe

9
src/components/DeviceBusyIndicator.js

@ -14,10 +14,10 @@ const Indicator = styled.div`
` `
// NB this is done like this to be extremely performant. we don't want redux for this.. // NB this is done like this to be extremely performant. we don't want redux for this..
const perPaths = {} let globalBusy = false
const instances = [] const instances = []
export const onSetDeviceBusy = (path, busy) => { export const onSetDeviceBusy = busy => {
perPaths[path] = busy globalBusy = busy
instances.forEach(i => i.forceUpdate()) instances.forEach(i => i.forceUpdate())
} }
@ -30,8 +30,7 @@ class DeviceBusyIndicator extends PureComponent<{}> {
instances.splice(i, 1) instances.splice(i, 1)
} }
render() { render() {
const busy = Object.values(perPaths).reduce((busy, b) => busy || b, false) return <Indicator busy={globalBusy} />
return <Indicator busy={busy} />
} }
} }

11
src/helpers/deviceAccess.js

@ -1,5 +1,6 @@
// @flow // @flow
import logger from 'logger' import logger from 'logger'
import throttle from 'lodash/throttle'
import type Transport from '@ledgerhq/hw-transport' import type Transport from '@ledgerhq/hw-transport'
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid' import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
import { retry } from './promise' import { retry } from './promise'
@ -26,10 +27,19 @@ let busy = false
TransportNodeHid.setListenDevicesPollingSkip(() => busy) TransportNodeHid.setListenDevicesPollingSkip(() => busy)
const refreshBusyUIState = throttle(() => {
process.send({
type: 'setDeviceBusy',
busy,
})
}, 100)
export const withDevice: WithDevice = devicePath => job => { export const withDevice: WithDevice = devicePath => job => {
const p = queue.then(async () => { const p = queue.then(async () => {
busy = true busy = true
refreshBusyUIState()
try { try {
// FIXME: remove this retry
const t = await retry(() => TransportNodeHid.open(devicePath), { maxRetry: 1 }) const t = await retry(() => TransportNodeHid.open(devicePath), { maxRetry: 1 })
t.setDebugMode(logger.apdu) t.setDebugMode(logger.apdu)
try { try {
@ -40,6 +50,7 @@ export const withDevice: WithDevice = devicePath => job => {
} }
} finally { } finally {
busy = false busy = false
refreshBusyUIState()
} }
}) })

113
src/helpers/libcore.js

@ -36,65 +36,63 @@ type Props = {
devicePath: string, devicePath: string,
currencyId: string, currencyId: string,
onAccountScanned: AccountRaw => void, onAccountScanned: AccountRaw => void,
isUnsubscribed: () => boolean,
} }
export function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> { export async function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> {
const { devicePath, currencyId, onAccountScanned, core } = props const { devicePath, currencyId, onAccountScanned, core, isUnsubscribed } = props
const currency = getCryptoCurrencyById(currencyId) const currency = getCryptoCurrencyById(currencyId)
return withDevice(devicePath)(async transport => { const commonParams = {
const hwApp = new Btc(transport) core,
currencyId,
onAccountScanned,
devicePath,
isUnsubscribed,
}
const commonParams = { let allAccounts = []
core,
currencyId,
onAccountScanned,
hwApp,
}
let allAccounts = [] const nonSegwitAccounts = await scanAccountsOnDeviceBySegwit({
...commonParams,
showNewAccount: !!SHOW_LEGACY_NEW_ACCOUNT || !currency.supportsSegwit,
isSegwit: false,
isUnsplit: false,
})
allAccounts = allAccounts.concat(nonSegwitAccounts)
const nonSegwitAccounts = await scanAccountsOnDeviceBySegwit({ if (currency.supportsSegwit) {
const segwitAccounts = await scanAccountsOnDeviceBySegwit({
...commonParams, ...commonParams,
showNewAccount: !!SHOW_LEGACY_NEW_ACCOUNT || !currency.supportsSegwit, showNewAccount: true,
isSegwit: false, isSegwit: true,
isUnsplit: false, isUnsplit: false,
}) })
allAccounts = allAccounts.concat(nonSegwitAccounts) allAccounts = allAccounts.concat(segwitAccounts)
}
// TODO: put that info inside currency itself
if (currencyId in SPLITTED_CURRENCIES) {
const splittedAccounts = await scanAccountsOnDeviceBySegwit({
...commonParams,
isSegwit: false,
showNewAccount: false,
isUnsplit: true,
})
allAccounts = allAccounts.concat(splittedAccounts)
if (currency.supportsSegwit) { if (currency.supportsSegwit) {
const segwitAccounts = await scanAccountsOnDeviceBySegwit({ const segwitAccounts = await scanAccountsOnDeviceBySegwit({
...commonParams, ...commonParams,
showNewAccount: true,
isSegwit: true,
isUnsplit: false,
})
allAccounts = allAccounts.concat(segwitAccounts)
}
// TODO: put that info inside currency itself
if (currencyId in SPLITTED_CURRENCIES) {
const splittedAccounts = await scanAccountsOnDeviceBySegwit({
...commonParams,
isSegwit: false,
showNewAccount: false, showNewAccount: false,
isUnsplit: true, isUnsplit: true,
isSegwit: true,
}) })
allAccounts = allAccounts.concat(splittedAccounts) allAccounts = allAccounts.concat(segwitAccounts)
if (currency.supportsSegwit) {
const segwitAccounts = await scanAccountsOnDeviceBySegwit({
...commonParams,
showNewAccount: false,
isUnsplit: true,
isSegwit: true,
})
allAccounts = allAccounts.concat(segwitAccounts)
}
} }
}
return allAccounts return allAccounts
})
} }
function encodeWalletName({ function encodeWalletName({
@ -114,17 +112,19 @@ function encodeWalletName({
async function scanAccountsOnDeviceBySegwit({ async function scanAccountsOnDeviceBySegwit({
core, core,
hwApp, devicePath,
currencyId, currencyId,
onAccountScanned, onAccountScanned,
isUnsubscribed,
isSegwit, isSegwit,
isUnsplit, isUnsplit,
showNewAccount, showNewAccount,
}: { }: {
core: *, core: *,
hwApp: Object, devicePath: string,
currencyId: string, currencyId: string,
onAccountScanned: AccountRaw => void, onAccountScanned: AccountRaw => void,
isUnsubscribed: () => boolean,
isSegwit: boolean, // FIXME all segwit to change to 'purpose' isSegwit: boolean, // FIXME all segwit to change to 'purpose'
showNewAccount: boolean, showNewAccount: boolean,
isUnsplit: boolean, isUnsplit: boolean,
@ -135,7 +135,11 @@ async function scanAccountsOnDeviceBySegwit({
const path = `${isSegwit ? '49' : '44'}'/${coinType}'` const path = `${isSegwit ? '49' : '44'}'/${coinType}'`
const { publicKey } = await hwApp.getWalletPublicKey(path, false, isSegwit) const { publicKey } = await withDevice(devicePath)(async transport =>
new Btc(transport).getWalletPublicKey(path, false, isSegwit),
)
if (isUnsubscribed()) return []
const walletName = encodeWalletName({ publicKey, currencyId, isSegwit, isUnsplit }) const walletName = encodeWalletName({ publicKey, currencyId, isSegwit, isUnsplit })
@ -148,7 +152,7 @@ async function scanAccountsOnDeviceBySegwit({
const accounts = await scanNextAccount({ const accounts = await scanNextAccount({
core, core,
wallet, wallet,
hwApp, devicePath,
currencyId, currencyId,
accountsCount, accountsCount,
accountIndex: 0, accountIndex: 0,
@ -157,6 +161,7 @@ async function scanAccountsOnDeviceBySegwit({
isSegwit, isSegwit,
isUnsplit, isUnsplit,
showNewAccount, showNewAccount,
isUnsubscribed,
}) })
return accounts return accounts
@ -164,12 +169,14 @@ async function scanAccountsOnDeviceBySegwit({
const hexToBytes = str => Array.from(Buffer.from(str, 'hex')) const hexToBytes = str => Array.from(Buffer.from(str, 'hex'))
const createAccount = async (wallet, hwApp) => { const createAccount = async (wallet, devicePath) => {
const accountCreationInfos = await wallet.getNextAccountCreationInfo() const accountCreationInfos = await wallet.getNextAccountCreationInfo()
await accountCreationInfos.derivations.reduce( await accountCreationInfos.derivations.reduce(
(promise, derivation) => (promise, derivation) =>
promise.then(async () => { promise.then(async () => {
const { publicKey, chainCode } = await hwApp.getWalletPublicKey(derivation) const { publicKey, chainCode } = await withDevice(devicePath)(async transport =>
new Btc(transport).getWalletPublicKey(derivation),
)
accountCreationInfos.publicKeys.push(hexToBytes(publicKey)) accountCreationInfos.publicKeys.push(hexToBytes(publicKey))
accountCreationInfos.chainCodes.push(hexToBytes(chainCode)) accountCreationInfos.chainCodes.push(hexToBytes(chainCode))
}), }),
@ -222,7 +229,7 @@ async function scanNextAccount(props: {
// $FlowFixMe // $FlowFixMe
wallet: NJSWallet, wallet: NJSWallet,
core: *, core: *,
hwApp: Object, devicePath: string,
currencyId: string, currencyId: string,
accountsCount: number, accountsCount: number,
accountIndex: number, accountIndex: number,
@ -231,11 +238,12 @@ async function scanNextAccount(props: {
isSegwit: boolean, isSegwit: boolean,
isUnsplit: boolean, isUnsplit: boolean,
showNewAccount: boolean, showNewAccount: boolean,
isUnsubscribed: () => boolean,
}): Promise<AccountRaw[]> { }): Promise<AccountRaw[]> {
const { const {
core, core,
wallet, wallet,
hwApp, devicePath,
currencyId, currencyId,
accountsCount, accountsCount,
accountIndex, accountIndex,
@ -244,6 +252,7 @@ async function scanNextAccount(props: {
isSegwit, isSegwit,
isUnsplit, isUnsplit,
showNewAccount, showNewAccount,
isUnsubscribed,
} = props } = props
// create account only if account has not been scanned yet // create account only if account has not been scanned yet
@ -252,13 +261,17 @@ async function scanNextAccount(props: {
const njsAccount = hasBeenScanned const njsAccount = hasBeenScanned
? await wallet.getAccount(accountIndex) ? await wallet.getAccount(accountIndex)
: await createAccount(wallet, hwApp) : await createAccount(wallet, devicePath)
if (isUnsubscribed()) return []
const shouldSyncAccount = true // TODO: let's sync everytime. maybe in the future we can optimize. const shouldSyncAccount = true // TODO: let's sync everytime. maybe in the future we can optimize.
if (shouldSyncAccount) { if (shouldSyncAccount) {
await coreSyncAccount(core, njsAccount) await coreSyncAccount(core, njsAccount)
} }
if (isUnsubscribed()) return []
const query = njsAccount.queryOperations() const query = njsAccount.queryOperations()
const ops = await query.complete().execute() const ops = await query.complete().execute()
@ -273,6 +286,8 @@ async function scanNextAccount(props: {
ops, ops,
}) })
if (isUnsubscribed()) return []
const isEmpty = ops.length === 0 const isEmpty = ops.length === 0
if (!isEmpty || showNewAccount) { if (!isEmpty || showNewAccount) {

3
src/helpers/withLibcore.js

@ -14,7 +14,8 @@ export default async function withLibcore<A>(job: Job<A>): Promise<A> {
busy: true, busy: true,
}) })
} }
return job(core) const res = await job(core)
return res
} finally { } finally {
if (--counter === 0) { if (--counter === 0) {
process.send({ process.send({

4
src/renderer/events.js

@ -105,8 +105,8 @@ export default ({ store }: { store: Object }) => {
onSetLibcoreBusy(busy) onSetLibcoreBusy(busy)
}) })
ipcRenderer.on('setDeviceBusy', (event: any, { busy, devicePath }) => { ipcRenderer.on('setDeviceBusy', (event: any, { busy }) => {
onSetDeviceBusy(devicePath, busy) onSetDeviceBusy(busy)
}) })
} }

Loading…
Cancel
Save