Browse Source

Optimize a bit libcoreSignAndBroadcast and libcoreSyncAccount perf

master
Gaëtan Renaudeau 7 years ago
parent
commit
d19546b08f
  1. 92
      src/bridge/LibcoreBridge.js
  2. 51
      src/commands/libcoreSignAndBroadcast.js
  3. 9
      src/commands/libcoreSyncAccount.js
  4. 4
      src/components/EnsureDeviceApp.js
  5. 4
      src/components/modals/Receive/steps/04-step-receive-funds.js
  6. 8
      src/helpers/bip32.js
  7. 32
      src/helpers/libcore.js
  8. 2
      src/logger/logger.js

92
src/bridge/LibcoreBridge.js

@ -120,41 +120,48 @@ const LibcoreBridge: WalletBridge<Transaction> = {
},
synchronize: account =>
libcoreSyncAccount.send({ rawAccount: encodeAccount(account) }).pipe(
map(rawSyncedAccount => {
const syncedAccount = decodeAccount(rawSyncedAccount)
return account => {
const accountOps = account.operations
const syncedOps = syncedAccount.operations
const patch: $Shape<Account> = {
id: syncedAccount.id,
freshAddress: syncedAccount.freshAddress,
freshAddressPath: syncedAccount.freshAddressPath,
balance: syncedAccount.balance,
blockHeight: syncedAccount.blockHeight,
lastSyncDate: new Date(),
}
const hasChanged =
accountOps.length !== syncedOps.length || // size change, we do a full refresh for now...
(accountOps.length > 0 &&
syncedOps.length > 0 &&
(accountOps[0].accountId !== syncedOps[0].accountId ||
accountOps[0].id !== syncedOps[0].id || // if same size, only check if the last item has changed.
accountOps[0].blockHeight !== syncedOps[0].blockHeight))
if (hasChanged) {
patch.operations = syncedAccount.operations
patch.pendingOperations = [] // For now, we assume a change will clean the pendings.
}
return {
...account,
...patch,
libcoreSyncAccount
.send({
accountId: account.id,
freshAddressPath: account.freshAddressPath,
index: account.index,
currencyId: account.currency.id,
})
.pipe(
map(rawSyncedAccount => {
const syncedAccount = decodeAccount(rawSyncedAccount)
return account => {
const accountOps = account.operations
const syncedOps = syncedAccount.operations
const patch: $Shape<Account> = {
id: syncedAccount.id,
freshAddress: syncedAccount.freshAddress,
freshAddressPath: syncedAccount.freshAddressPath,
balance: syncedAccount.balance,
blockHeight: syncedAccount.blockHeight,
lastSyncDate: new Date(),
}
const hasChanged =
accountOps.length !== syncedOps.length || // size change, we do a full refresh for now...
(accountOps.length > 0 &&
syncedOps.length > 0 &&
(accountOps[0].accountId !== syncedOps[0].accountId ||
accountOps[0].id !== syncedOps[0].id || // if same size, only check if the last item has changed.
accountOps[0].blockHeight !== syncedOps[0].blockHeight))
if (hasChanged) {
patch.operations = syncedAccount.operations
patch.pendingOperations = [] // For now, we assume a change will clean the pendings.
}
return {
...account,
...patch,
}
}
}
}),
),
}),
),
pullMoreOperations: () => Promise.reject(notImplemented),
@ -201,11 +208,15 @@ const LibcoreBridge: WalletBridge<Transaction> = {
.catch(() => BigNumber(0))
.then(totalFees => a.balance.minus(totalFees || 0)),
signAndBroadcast: (account, transaction, deviceId) => {
const encodedAccount = encodeAccount(account) // FIXME no need to send the whole account over the threads
return libcoreSignAndBroadcast
signAndBroadcast: (account, transaction, deviceId) =>
libcoreSignAndBroadcast
.send({
account: encodedAccount,
accountId: account.id,
currencyId: account.currency.id,
xpub: account.xpub,
freshAddress: account.freshAddress,
freshAddressPath: account.freshAddressPath,
index: account.index,
transaction: serializeTransaction(transaction),
deviceId,
})
@ -215,14 +226,13 @@ const LibcoreBridge: WalletBridge<Transaction> = {
case 'broadcasted':
return {
type: 'broadcasted',
operation: decodeOperation(encodedAccount, e.operation),
operation: decodeOperation(encodeAccount(account), e.operation),
}
default:
return e
}
}),
)
},
),
addPendingOperation: (account, operation) => ({
...account,

51
src/commands/libcoreSignAndBroadcast.js

@ -2,11 +2,11 @@
import logger from 'logger'
import { BigNumber } from 'bignumber.js'
import type { AccountRaw, OperationRaw } from '@ledgerhq/live-common/lib/types'
import type { OperationRaw } from '@ledgerhq/live-common/lib/types'
import Btc from '@ledgerhq/hw-app-btc'
import { Observable } from 'rxjs'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies'
import { isSegwitAccount } from 'helpers/bip32'
import { isSegwitPath } from 'helpers/bip32'
import { libcoreAmountToBigNumber, bigNumberToLibcoreAmount } from 'helpers/libcore'
import withLibcore from 'helpers/withLibcore'
@ -21,7 +21,12 @@ type BitcoinLikeTransaction = {
}
type Input = {
account: AccountRaw, // FIXME there is no reason we send the whole AccountRaw
accountId: string,
currencyId: string,
xpub: string,
freshAddress: string,
freshAddressPath: string,
index: number,
transaction: BitcoinLikeTransaction,
deviceId: string,
}
@ -32,13 +37,18 @@ type Result = { type: 'signed' } | { type: 'broadcasted', operation: OperationRa
const cmd: Command<Input, Result> = createCommand(
'libcoreSignAndBroadcast',
({ account, transaction, deviceId }) =>
({ accountId, currencyId, xpub, freshAddress, freshAddressPath, index, transaction, deviceId }) =>
Observable.create(o => {
let unsubscribed = false
const isCancelled = () => unsubscribed
withLibcore(core =>
doSignAndBroadcast({
account,
accountId,
currencyId,
xpub,
freshAddress,
freshAddressPath,
index,
transaction,
deviceId,
core,
@ -151,7 +161,12 @@ async function signTransaction({
}
export async function doSignAndBroadcast({
account,
accountId,
currencyId,
xpub,
freshAddress,
freshAddressPath,
index,
transaction,
deviceId,
core,
@ -159,7 +174,12 @@ export async function doSignAndBroadcast({
onSigned,
onOperationBroadcasted,
}: {
account: AccountRaw,
accountId: string,
currencyId: string,
xpub: string,
freshAddress: string,
freshAddressPath: string,
index: number,
transaction: BitcoinLikeTransaction,
deviceId: string,
core: *,
@ -167,10 +187,10 @@ export async function doSignAndBroadcast({
onSigned: () => void,
onOperationBroadcasted: (optimisticOp: $Exact<OperationRaw>) => void,
}): Promise<void> {
const { walletName } = accountIdHelper.decode(account.id)
const { walletName } = accountIdHelper.decode(accountId)
const njsWallet = await core.getPoolInstance().getWallet(walletName)
if (isCancelled()) return
const njsAccount = await njsWallet.getAccount(account.index)
const njsAccount = await njsWallet.getAccount(index)
if (isCancelled()) return
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
@ -194,16 +214,16 @@ export async function doSignAndBroadcast({
const hasTimestamp = !!njsWalletCurrency.bitcoinLikeNetworkParameters.UsesTimestampedTransaction
// TODO: const timestampDelay = njsWalletCurrency.bitcoinLikeNetworkParameters.TimestampDelay
const currency = getCryptoCurrencyById(account.currencyId)
const currency = getCryptoCurrencyById(currencyId)
const signedTransaction = await withDevice(deviceId)(async transport =>
signTransaction({
hwApp: new Btc(transport),
currencyId: account.currencyId,
currencyId,
transaction: builded,
sigHashType: parseInt(sigHashType, 16),
supportsSegwit: !!currency.supportsSegwit,
isSegwit: isSegwitAccount(account),
isSegwit: isSegwitPath(freshAddressPath),
hasTimestamp,
}),
)
@ -221,7 +241,7 @@ export async function doSignAndBroadcast({
// NB we don't check isCancelled() because the broadcast is not cancellable now!
onOperationBroadcasted({
id: `${account.xpub}-${txHash}-OUT`,
id: `${xpub}-${txHash}-OUT`,
hash: txHash,
type: 'OUT',
value: BigNumber(transaction.amount)
@ -230,9 +250,10 @@ export async function doSignAndBroadcast({
fee: fee.toString(),
blockHash: null,
blockHeight: null,
senders: [account.freshAddress],
// FIXME for senders and recipients, can we ask the libcore?
senders: [freshAddress],
recipients: [transaction.recipient],
accountId: account.id,
accountId,
date: new Date().toISOString(),
})
}

9
src/commands/libcoreSyncAccount.js

@ -8,13 +8,16 @@ import { syncAccount } from 'helpers/libcore'
import withLibcore from 'helpers/withLibcore'
type Input = {
rawAccount: AccountRaw, // FIXME there is no reason we send the whole AccountRaw
accountId: string,
freshAddressPath: string,
currencyId: string,
index: number,
}
type Result = AccountRaw
const cmd: Command<Input, Result> = createCommand('libcoreSyncAccount', ({ rawAccount }) =>
fromPromise(withLibcore(core => syncAccount({ rawAccount, core }))),
const cmd: Command<Input, Result> = createCommand('libcoreSyncAccount', accountInfos =>
fromPromise(withLibcore(core => syncAccount({ ...accountInfos, core }))),
)
export default cmd

4
src/components/EnsureDeviceApp.js

@ -11,7 +11,7 @@ import logger from 'logger'
import getAddress from 'commands/getAddress'
import { createCancelablePolling } from 'helpers/promise'
import { standardDerivation } from 'helpers/derivations'
import { isSegwitAccount } from 'helpers/bip32'
import { isSegwitPath } from 'helpers/bip32'
import { BtcUnmatchedApp } from 'helpers/getAddressForCurrency/btc'
import DeviceInteraction from 'components/DeviceInteraction'
@ -124,7 +124,7 @@ async function getAddressFromAccountOrCurrency(device, account, currency) {
path: account
? account.freshAddressPath
: standardDerivation({ currency, segwit: false, x: 0 }),
segwit: account ? isSegwitAccount(account) : false,
segwit: account ? isSegwitPath(account.freshAddressPath) : false,
})
.toPromise()
return address

4
src/components/modals/Receive/steps/04-step-receive-funds.js

@ -5,7 +5,7 @@ import React, { PureComponent } from 'react'
import TrackPage from 'analytics/TrackPage'
import getAddress from 'commands/getAddress'
import { isSegwitAccount } from 'helpers/bip32'
import { isSegwitPath } from 'helpers/bip32'
import Box from 'components/base/Box'
import CurrentAddressForAccount from 'components/CurrentAddressForAccount'
import { DisconnectedDevice, WrongDeviceForAccount } from 'config/errors'
@ -29,7 +29,7 @@ export default class StepReceiveFunds extends PureComponent<StepProps> {
currencyId: account.currency.id,
devicePath: device.path,
path: account.freshAddressPath,
segwit: isSegwitAccount(account),
segwit: isSegwitPath(account.freshAddressPath),
verify: true,
}
const { address } = await getAddress.send(params).toPromise()

8
src/helpers/bip32.js

@ -1,16 +1,11 @@
// @flow
import type { Account, AccountRaw } from '@ledgerhq/live-common/lib/types'
type SplitConfig = {
coinType: number,
}
export const isSegwitPath = (path: string): boolean => path.startsWith("49'")
export const isSegwitAccount = (account: Account | AccountRaw): boolean =>
isSegwitPath(account.freshAddressPath)
export const isUnsplitPath = (path: string, splitConfig: SplitConfig) => {
try {
const coinType = parseInt(path.split('/')[1], 10)
@ -19,6 +14,3 @@ export const isUnsplitPath = (path: string, splitConfig: SplitConfig) => {
return false
}
}
export const isUnsplitAccount = (account: Account | AccountRaw, splitConfig: ?SplitConfig) =>
!!splitConfig && isUnsplitPath(account.freshAddressPath, splitConfig)

32
src/helpers/libcore.js

@ -12,7 +12,7 @@ import { SHOW_LEGACY_NEW_ACCOUNT } from 'config/constants'
import type { AccountRaw, OperationRaw, OperationType } from '@ledgerhq/live-common/lib/types'
import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgercore_doc'
import { isSegwitAccount, isUnsplitAccount } from 'helpers/bip32'
import { isSegwitPath, isUnsplitPath } from 'helpers/bip32'
import * as accountIdHelper from 'helpers/accountId'
import { createCustomErrorClass, deserializeError } from './errors'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName'
@ -482,10 +482,22 @@ function buildOperationRaw({
}
}
export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: AccountRaw }) {
const decodedAccountId = accountIdHelper.decode(rawAccount.id)
const isSegwit = isSegwitAccount(rawAccount)
const isUnsplit = isUnsplitAccount(rawAccount, SPLITTED_CURRENCIES[rawAccount.currencyId])
export async function syncAccount({
accountId,
freshAddressPath,
currencyId,
index,
core,
}: {
core: *,
accountId: string,
freshAddressPath: string,
currencyId: string,
index: number,
}) {
const decodedAccountId = accountIdHelper.decode(accountId)
const isSegwit = isSegwitPath(freshAddressPath)
const isUnsplit = isUnsplitPath(freshAddressPath, SPLITTED_CURRENCIES[currencyId])
let njsWallet
try {
njsWallet = await core.getPoolInstance().getWallet(decodedAccountId.walletName)
@ -494,7 +506,7 @@ export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: A
njsWallet = await getOrCreateWallet(
core,
decodedAccountId.walletName,
rawAccount.currencyId,
currencyId,
isSegwit,
isUnsplit,
)
@ -502,10 +514,10 @@ export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: A
let njsAccount
try {
njsAccount = await njsWallet.getAccount(rawAccount.index)
njsAccount = await njsWallet.getAccount(index)
} catch (e) {
logger.warn(`Have to recreate the account... (${e.message})`)
const extendedInfos = await njsWallet.getExtendedKeyAccountCreationInfo(rawAccount.index)
const extendedInfos = await njsWallet.getExtendedKeyAccountCreationInfo(index)
extendedInfos.extendedKeys.push(decodedAccountId.xpub)
njsAccount = await njsWallet.newAccountWithExtendedKeyInfo(extendedInfos)
}
@ -521,9 +533,9 @@ export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: A
njsAccount,
isSegwit,
isUnsplit,
accountIndex: rawAccount.index,
accountIndex: index,
wallet: njsWallet,
currencyId: rawAccount.currencyId,
currencyId,
core,
ops,
})

2
src/logger/logger.js

@ -146,7 +146,7 @@ const logNetwork = !__DEV__ || DEBUG_NETWORK
const logAnalytics = !__DEV__ || DEBUG_ANALYTICS
const logApdu = !__DEV__ || DEBUG_DEVICE
const blacklistTooVerboseCommandInput = ['libcoreSyncAccount', 'libcoreSignAndBroadcast']
const blacklistTooVerboseCommandInput = []
const blacklistTooVerboseCommandResponse = [
'libcoreSyncAccount',
'libcoreScanAccounts',

Loading…
Cancel
Save