Browse Source

Refactor major libcore

master
Gaëtan Renaudeau 7 years ago
parent
commit
94561fdba5
  1. 2
      package.json
  2. 12
      src/commands/libcoreGetFees.js
  3. 4
      src/commands/libcoreHardReset.js
  4. 12
      src/commands/libcoreSignAndBroadcast.js
  5. 4
      src/commands/libcoreSyncAccount.js
  6. 2
      src/commands/libcoreValidAddress.js
  7. 193
      src/helpers/init-libcore.js
  8. 104
      src/helpers/libcore.js
  9. 26
      src/helpers/withLibcore.js
  10. 8
      src/logger.js
  11. 6
      yarn.lock

2
package.json

@ -41,7 +41,7 @@
"@ledgerhq/hw-app-xrp": "^4.13.0",
"@ledgerhq/hw-transport": "^4.13.0",
"@ledgerhq/hw-transport-node-hid": "^4.13.0",
"@ledgerhq/ledger-core": "1.9.0",
"@ledgerhq/ledger-core": "2.0.0-rc.1",
"@ledgerhq/live-common": "2.30.0",
"async": "^2.6.1",
"axios": "^0.18.0",

12
src/commands/libcoreGetFees.js

@ -33,14 +33,20 @@ const cmd: Command<Input, Result> = createCommand(
withLibcore(async core => {
const { walletName } = accountIdHelper.decode(accountId)
const njsWallet = await core.getWallet(walletName)
const njsWallet = await core.getPoolInstance().getWallet(walletName)
if (isCancelled()) return
const njsAccount = await njsWallet.getAccount(accountIndex)
if (isCancelled()) return
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
const amount = core.createAmount(njsWalletCurrency, transaction.amount)
const feesPerByte = core.createAmount(njsWalletCurrency, transaction.feePerByte)
const amount = new core.NJSAmount(njsWalletCurrency, transaction.amount).fromLong(
njsWalletCurrency,
transaction.amount,
)
const feesPerByte = new core.NJSAmount(njsWalletCurrency, transaction.feePerByte).fromLong(
njsWalletCurrency,
transaction.feePerByte,
)
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
if (!isValidAddress(core, njsWalletCurrency, transaction.recipient)) {
// FIXME this is a bug in libcore. later it will probably check this and we can remove this check

4
src/commands/libcoreHardReset.js

@ -6,9 +6,9 @@ import withLibcore from 'helpers/withLibcore'
const cmd = createCommand('libcoreHardReset', () =>
Observable.create(o => {
withLibcore(async (core, njsWalletPool) => {
withLibcore(async core => {
try {
njsWalletPool.eraseDataSince(new Date(0))
core.getPoolInstance().eraseDataSince(new Date(0))
o.complete()
} catch (e) {
o.error(e)

12
src/commands/libcoreSignAndBroadcast.js

@ -153,14 +153,20 @@ export async function doSignAndBroadcast({
onOperationBroadcasted: (optimisticOp: $Exact<OperationRaw>) => void,
}): Promise<void> {
const { walletName } = accountIdHelper.decode(account.id)
const njsWallet = await core.getWallet(walletName)
const njsWallet = await core.getPoolInstance().getWallet(walletName)
if (isCancelled()) return
const njsAccount = await njsWallet.getAccount(account.index)
if (isCancelled()) return
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
const amount = core.createAmount(njsWalletCurrency, transaction.amount)
const fees = core.createAmount(njsWalletCurrency, transaction.feePerByte)
const amount = new core.NJSAmount(njsWalletCurrency, transaction.amount).fromLong(
njsWalletCurrency,
transaction.amount,
)
const fees = new core.NJSAmount(njsWalletCurrency, transaction.feePerByte).fromLong(
njsWalletCurrency,
transaction.feePerByte,
)
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
// TODO: check if is valid address. if not, it will fail silently on invalid

4
src/commands/libcoreSyncAccount.js

@ -14,9 +14,7 @@ type Input = {
type Result = AccountRaw
const cmd: Command<Input, Result> = createCommand('libcoreSyncAccount', ({ rawAccount }) =>
fromPromise(
withLibcore((core, njsWalletPool) => syncAccount({ rawAccount, core, njsWalletPool })),
),
fromPromise(withLibcore(core => syncAccount({ rawAccount, core }))),
)
export default cmd

2
src/commands/libcoreValidAddress.js

@ -15,7 +15,7 @@ const cmd: Command<Input, boolean> = createCommand(
({ currencyId, address }) =>
fromPromise(
withLibcore(async core => {
const currency = await core.getCurrency(currencyId)
const currency = await core.getPoolInstance().getCurrency(currencyId)
return isValidAddress(core, currency, address)
}),
),

193
src/helpers/init-libcore.js

@ -0,0 +1,193 @@
// @flow
import logger from 'logger'
import invariant from 'invariant'
import network from 'api/network'
const lib = require('@ledgerhq/ledger-core')
const crypto = require('crypto')
const path = require('path')
const fs = require('fs')
const MAX_RANDOM = 2684869021
const bytesArrayToString = (bytesArray = []) => Buffer.from(bytesArray).toString()
const stringToBytesArray = str => Array.from(Buffer.from(str))
const NJSExecutionContextImpl = {
execute: runnable => {
try {
runnable.run()
} catch (e) {
logger.log(e)
}
},
delay: (runnable, ms) => setTimeout(() => runnable.run(), ms),
}
const ThreadContexts = {}
const getSerialExecutionContext = name => {
let currentContext = ThreadContexts[name]
if (!currentContext) {
currentContext = new lib.NJSExecutionContext(NJSExecutionContextImpl)
ThreadContexts[name] = currentContext
}
return currentContext
}
const NJSThreadDispatcher = new lib.NJSThreadDispatcher({
contexts: ThreadContexts,
getThreadPoolExecutionContext: name => getSerialExecutionContext(name),
getMainExecutionContext: () => getSerialExecutionContext('main'),
getSerialExecutionContext,
newLock: () => {
logger.warn('libcore NJSThreadDispatcher: newLock: Not implemented')
},
})
function createHttpConnection(res, err) {
if (!res) {
return null
}
const headersMap = new Map()
Object.keys(res.headers).forEach(key => {
if (typeof res.headers[key] === 'string') {
headersMap.set(key, res.headers[key])
}
})
const NJSHttpUrlConnectionImpl = {
getStatusCode: () => Number(res.status),
getStatusText: () => res.statusText,
getHeaders: () => headersMap,
readBody: () => ({
error: err ? { code: 0, message: 'something went wrong' } : null,
data: stringToBytesArray(JSON.stringify(res.data)),
}),
}
return new lib.NJSHttpUrlConnection(NJSHttpUrlConnectionImpl)
}
const NJSHttpClient = new lib.NJSHttpClient({
execute: async r => {
const method = r.getMethod()
const headersMap = r.getHeaders()
let data = r.getBody()
if (Array.isArray(data)) {
const dataStr = bytesArrayToString(data)
try {
data = JSON.parse(dataStr)
} catch (e) {
// not a json !?
}
}
const url = r.getUrl()
const headers = {}
headersMap.forEach((v, k) => {
headers[k] = v
})
let res
try {
// $FlowFixMe
res = await network({ method: lib.METHODS[method], url, headers, data })
const urlConnection = createHttpConnection(res)
r.complete(urlConnection, null)
} catch (err) {
const urlConnection = createHttpConnection(res, err.message)
r.complete(urlConnection, { code: 0, message: err.message })
}
},
})
const NJSWebSocketClient = new lib.NJSWebSocketClient({
connect: (url, connection) => {
connection.OnConnect()
},
send: (connection, data) => {
connection.OnMessage(data)
},
disconnect: connection => {
connection.OnClose()
},
})
const NJSLogPrinter = new lib.NJSLogPrinter({
context: {},
printError: message => logger.libcore('Error', message),
printInfo: message => logger.libcore('Info', message),
printDebug: message => logger.libcore('Debug', message),
printWarning: message => logger.libcore('Warning', message),
printApdu: message => logger.libcore('Apdu', message),
printCriticalError: message => logger.libcore('CriticalError', message),
getContext: () => NJSThreadDispatcher.getMainExecutionContext(),
})
const NJSRandomNumberGenerator = new lib.NJSRandomNumberGenerator({
getRandomBytes: size => crypto.randomBytes(size),
getRandomInt: () => Math.random() * MAX_RANDOM,
getRandomLong: () => Math.random() * MAX_RANDOM * MAX_RANDOM,
})
const NJSDatabaseBackend = new lib.NJSDatabaseBackend()
const NJSDynamicObject = new lib.NJSDynamicObject()
let walletPoolInstance = null
const instanciateWalletPool = ({ dbPath }) => {
try {
fs.mkdirSync(dbPath)
} catch (err) {
if (err.code !== 'EEXIST') {
throw err
}
}
const NJSPathResolver = new lib.NJSPathResolver({
resolveLogFilePath: pathToResolve => {
const hash = pathToResolve.replace(/\//g, '__')
return path.resolve(dbPath, `./log_file_${hash}`)
},
resolvePreferencesPath: pathToResolve => {
const hash = pathToResolve.replace(/\//g, '__')
return path.resolve(dbPath, `./preferences_${hash}`)
},
resolveDatabasePath: pathToResolve => {
const hash = pathToResolve.replace(/\//g, '__')
return path.resolve(dbPath, `./database_${hash}`)
},
})
walletPoolInstance = new lib.NJSWalletPool(
'ledger_live_desktop',
'',
NJSHttpClient,
NJSWebSocketClient,
NJSPathResolver,
NJSLogPrinter,
NJSThreadDispatcher,
NJSRandomNumberGenerator,
NJSDatabaseBackend,
NJSDynamicObject,
)
return walletPoolInstance
}
const getPoolInstance = () => {
if (!walletPoolInstance) {
instanciateWalletPool({
// sqlite files will be located in the app local data folder
dbPath: process.env.LEDGER_LIVE_SQLITE_PATH,
})
}
invariant(walletPoolInstance, "can't initialize walletPoolInstance")
return walletPoolInstance
}
export default {
...lib,
getSerialExecutionContext,
getPoolInstance,
}

104
src/helpers/libcore.js

@ -123,6 +123,58 @@ async function scanAccountsOnDeviceBySegwit({
return accounts
}
const hexToBytes = str => Array.from(Buffer.from(str, 'hex'))
const createAccount = async (wallet, hwApp) => {
const accountCreationInfos = await wallet.getNextAccountCreationInfo()
await accountCreationInfos.derivations.reduce(
(promise, derivation) =>
promise.then(async () => {
const { publicKey, chainCode } = await hwApp.getWalletPublicKey(derivation)
accountCreationInfos.publicKeys.push(hexToBytes(publicKey))
accountCreationInfos.chainCodes.push(hexToBytes(chainCode))
}),
Promise.resolve(),
)
return wallet.newAccountWithInfo(accountCreationInfos)
}
function createEventReceiver(core, cb) {
return new core.NJSEventReceiver({
onEvent: event => cb(event),
})
}
function subscribeToEventBus(core, eventBus, receiver) {
eventBus.subscribe(core.getSerialExecutionContext('main'), receiver)
}
const coreSyncAccount = (core, account) =>
new Promise((resolve, reject) => {
const eventReceiver = createEventReceiver(core, e => {
const code = e.getCode()
if (code === core.EVENT_CODE.UNDEFINED || code === core.EVENT_CODE.SYNCHRONIZATION_FAILED) {
const payload = e.getPayload()
const message = (
(payload && payload.getString('EV_SYNC_ERROR_MESSAGE')) ||
'Sync failed'
).replace(' (EC_PRIV_KEY_INVALID_FORMAT)', '')
reject(new Error(message))
return
}
if (
code === core.EVENT_CODE.SYNCHRONIZATION_SUCCEED ||
code === core.EVENT_CODE.SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT
) {
resolve(() => {
eventBus.unsubscribe(eventReceiver)
})
}
})
const eventBus = account.synchronize()
subscribeToEventBus(core, eventBus, eventReceiver)
})
async function scanNextAccount(props: {
// $FlowFixMe
wallet: NJSWallet,
@ -155,11 +207,11 @@ async function scanNextAccount(props: {
const njsAccount = hasBeenScanned
? await wallet.getAccount(accountIndex)
: await core.createAccount(wallet, hwApp)
: await createAccount(wallet, hwApp)
const shouldSyncAccount = true // TODO: let's sync everytime. maybe in the future we can optimize.
if (shouldSyncAccount) {
await core.syncAccount(njsAccount)
await coreSyncAccount(core, njsAccount)
}
const query = njsAccount.queryOperations()
@ -190,25 +242,38 @@ async function scanNextAccount(props: {
return scanNextAccount({ ...props, accountIndex: accountIndex + 1 })
}
const createWalletConfig = (core, configMap = {}) => {
const config = new core.NJSDynamicObject()
for (const i in configMap) {
if (configMap.hasOwnProperty(i)) {
config.putString(i, configMap[i])
}
}
return config
}
async function getOrCreateWallet(
core: *,
WALLET_IDENTIFIER: string,
currencyId: string,
isSegwit: boolean,
): NJSWallet {
const pool = core.getPoolInstance()
try {
const wallet = await core.getWallet(WALLET_IDENTIFIER)
const wallet = await pool.getWallet(WALLET_IDENTIFIER)
return wallet
} catch (err) {
const currency = await core.getCurrency(currencyId)
const currency = await pool.getCurrency(currencyId)
const walletConfig = isSegwit
? {
KEYCHAIN_ENGINE: 'BIP49_P2SH',
KEYCHAIN_DERIVATION_SCHEME: "49'/<coin_type>'/<account>'/<node>/<address>",
}
: undefined
const njsWalletConfig = core.createWalletConfig(walletConfig)
const wallet = await core.createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig)
const njsWalletConfig = createWalletConfig(core, walletConfig)
const wallet = await core
.getPoolInstance()
.createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig)
return wallet
}
}
@ -342,33 +407,12 @@ function buildOperationRaw({
}
}
export async function getNJSAccount({
accountRaw,
njsWalletPool,
}: {
accountRaw: AccountRaw,
njsWalletPool: *,
}) {
const decodedAccountId = accountIdHelper.decode(accountRaw.id)
const njsWallet = await njsWalletPool.getWallet(decodedAccountId.walletName)
const njsAccount = await njsWallet.getAccount(accountRaw.index)
return njsAccount
}
export async function syncAccount({
rawAccount,
core,
njsWalletPool,
}: {
core: *,
rawAccount: AccountRaw,
njsWalletPool: *,
}) {
export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: AccountRaw }) {
const decodedAccountId = accountIdHelper.decode(rawAccount.id)
const njsWallet = await njsWalletPool.getWallet(decodedAccountId.walletName)
const njsWallet = await core.getPoolInstance().getWallet(decodedAccountId.walletName)
const njsAccount = await njsWallet.getAccount(rawAccount.index)
const unsub = await core.syncAccount(njsAccount)
const unsub = await coreSyncAccount(core, njsAccount)
unsub()
const query = njsAccount.queryOperations()

26
src/helpers/withLibcore.js

@ -1,26 +1,10 @@
// @flow
import invariant from 'invariant'
import network from 'api/network'
const core = require('@ledgerhq/ledger-core')
core.setHttpQueryImplementation(network)
let walletPoolInstance: ?Object = null
// TODO: `core` and `NJSWalletPool` should be typed
type Job<A> = (Object, Object) => Promise<A>
// TODO: `core` should be typed
type Job<A> = Object => Promise<A>
export default function withLibcore<A>(job: Job<A>): Promise<A> {
if (!walletPoolInstance) {
walletPoolInstance = core.instanciateWalletPool({
// sqlite files will be located in the app local data folder
dbPath: process.env.LEDGER_LIVE_SQLITE_PATH,
})
}
const walletPool = walletPoolInstance
invariant(walletPool, 'core.instanciateWalletPool returned null !!')
return job(core, walletPool)
const core = require('./init-libcore').default
core.getPoolInstance()
return job(core)
}

8
src/logger.js

@ -51,6 +51,7 @@ const logCmds = !__DEV__ || process.env.DEBUG_COMMANDS
const logDb = !__DEV__ || process.env.DEBUG_DB
const logRedux = !__DEV__ || process.env.DEBUG_ACTION
const logTabkey = !__DEV__ || process.env.DEBUG_TAB_KEY
const logLibcore = !__DEV__ || process.env.DEBUG_LIBCORE
export default {
onCmd: (type: string, id: string, spentTime: number, data?: any) => {
@ -103,6 +104,13 @@ export default {
addLog('keydown', msg)
},
libcore: (level: string, msg: string) => {
if (logLibcore) {
console.log(`🛠 ${level}: ${msg}`)
}
addLog('action', `🛠 ${level}: ${msg}`)
},
// General functions in case the hooks don't apply
log: (...args: any) => {

6
yarn.lock

@ -1502,9 +1502,9 @@
dependencies:
events "^2.0.0"
"@ledgerhq/ledger-core@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-1.9.0.tgz#3240857e76f3c2b17ba02d120b96fd1b13b50789"
"@ledgerhq/ledger-core@2.0.0-rc.1":
version "2.0.0-rc.1"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.1.tgz#0b31f7d2c693b9c11d4093dbb0896f13c33bf141"
dependencies:
"@ledgerhq/hw-app-btc" "^4.7.3"
"@ledgerhq/hw-transport-node-hid" "^4.7.6"

Loading…
Cancel
Save