Browse Source

Merge pull request #659 from gre/scan-status

fix scan accounts
master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
29f8c3b563
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 201
      src/bridge/EthereumJSBridge.js
  2. 3
      src/bridge/LibcoreBridge.js
  3. 193
      src/bridge/RippleJSBridge.js
  4. 8
      src/bridge/UnsupportedBridge.js
  5. 55
      src/bridge/makeMockBridge.js
  6. 6
      src/bridge/types.js
  7. 6
      src/components/modals/AddAccounts/steps/03-step-import.js

201
src/bridge/EthereumJSBridge.js

@ -156,117 +156,118 @@ const fetchCurrentBlock = (perCurrencyId => currency => {
})({}) })({})
const EthereumBridge: WalletBridge<Transaction> = { const EthereumBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice(currency, deviceId, { next, complete, error }) { scanAccountsOnDevice: (currency, deviceId) =>
let finished = false Observable.create(o => {
const unsubscribe = () => { let finished = false
finished = true const unsubscribe = () => {
} finished = true
const api = apiForCurrency(currency) }
const api = apiForCurrency(currency)
// in future ideally what we want is:
// return mergeMap(addressesObservable, address => fetchAccount(address)) // in future ideally what we want is:
// return mergeMap(addressesObservable, address => fetchAccount(address))
let newAccountCount = 0
let newAccountCount = 0
async function stepAddress(
index, async function stepAddress(
{ address, path: freshAddressPath, publicKey }, index,
isStandard, { address, path: freshAddressPath, publicKey },
): { account?: Account, complete?: boolean } { isStandard,
const balance = await api.getAccountBalance(address) ): { account?: Account, complete?: boolean } {
if (finished) return { complete: true } const balance = await api.getAccountBalance(address)
const currentBlock = await fetchCurrentBlock(currency) if (finished) return { complete: true }
if (finished) return { complete: true } const currentBlock = await fetchCurrentBlock(currency)
let { txs } = await api.getTransactions(address) if (finished) return { complete: true }
if (finished) return { complete: true } let { txs } = await api.getTransactions(address)
if (finished) return { complete: true }
const freshAddress = address
const accountId = `ethereumjs:${currency.id}:${address}:${publicKey}` const freshAddress = address
const accountId = `ethereumjs:${currency.id}:${address}:${publicKey}`
if (txs.length === 0) {
// this is an empty account if (txs.length === 0) {
if (isStandard) { // this is an empty account
if (newAccountCount === 0) { if (isStandard) {
// first zero account will emit one account as opportunity to create a new account.. if (newAccountCount === 0) {
const account: $Exact<Account> = { // first zero account will emit one account as opportunity to create a new account..
id: accountId, const account: $Exact<Account> = {
xpub: '', id: accountId,
freshAddress, xpub: '',
freshAddressPath, freshAddress,
name: getNewAccountPlaceholderName(currency, index), freshAddressPath,
balance, name: getNewAccountPlaceholderName(currency, index),
blockHeight: currentBlock.height, balance,
index, blockHeight: currentBlock.height,
currency, index,
operations: [], currency,
pendingOperations: [], operations: [],
unit: currency.units[0], pendingOperations: [],
lastSyncDate: new Date(), unit: currency.units[0],
lastSyncDate: new Date(),
}
return { account, complete: true }
} }
return { account, complete: true } newAccountCount++
} }
newAccountCount++ // NB for legacy addresses maybe we will continue at least for the first 10 addresses
return { complete: true }
} }
// NB for legacy addresses maybe we will continue at least for the first 10 addresses
return { complete: true }
}
const account: $Exact<Account> = { const account: $Exact<Account> = {
id: accountId, id: accountId,
xpub: '', xpub: '',
freshAddress, freshAddress,
freshAddressPath, freshAddressPath,
name: getAccountPlaceholderName(currency, index, !isStandard), name: getAccountPlaceholderName(currency, index, !isStandard),
balance, balance,
blockHeight: currentBlock.height, blockHeight: currentBlock.height,
index, index,
currency, currency,
operations: [], operations: [],
pendingOperations: [], pendingOperations: [],
unit: currency.units[0], unit: currency.units[0],
lastSyncDate: new Date(), lastSyncDate: new Date(),
} }
for (let i = 0; i < 50; i++) { for (let i = 0; i < 50; i++) {
const api = apiForCurrency(account.currency) const api = apiForCurrency(account.currency)
const { block } = txs[txs.length - 1] const { block } = txs[txs.length - 1]
if (!block) break if (!block) break
const next = await api.getTransactions(account.freshAddress, block.hash) const next = await api.getTransactions(account.freshAddress, block.hash)
if (next.txs.length === 0) break if (next.txs.length === 0) break
txs = txs.concat(next.txs) txs = txs.concat(next.txs)
}
txs.reverse()
account.operations = mergeOps([], flatMap(txs, txToOps(account)))
return { account }
} }
txs.reverse()
account.operations = mergeOps([], flatMap(txs, txToOps(account))) async function main() {
return { account } try {
} const derivations = getDerivations(currency)
const last = derivations[derivations.length - 1]
async function main() { for (const derivation of derivations) {
try { const isStandard = last === derivation
const derivations = getDerivations(currency) for (let index = 0; index < 255; index++) {
const last = derivations[derivations.length - 1] const freshAddressPath = derivation({ currency, x: index, segwit: false })
for (const derivation of derivations) { const res = await getAddressCommand
const isStandard = last === derivation .send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath })
for (let index = 0; index < 255; index++) { .toPromise()
const freshAddressPath = derivation({ currency, x: index, segwit: false }) const r = await stepAddress(index, res, isStandard)
const res = await getAddressCommand if (r.account) o.next(r.account)
.send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath }) if (r.complete) {
.toPromise() break
const r = await stepAddress(index, res, isStandard) }
if (r.account) next(r.account)
if (r.complete) {
break
} }
} }
o.complete()
} catch (e) {
o.error(e)
} }
complete()
} catch (e) {
error(e)
} }
}
main() main()
return { unsubscribe } return unsubscribe
}, }),
synchronize: ({ freshAddress, blockHeight, currency, operations }) => synchronize: ({ freshAddress, blockHeight, currency, operations }) =>
Observable.create(o => { Observable.create(o => {

3
src/bridge/LibcoreBridge.js

@ -79,14 +79,13 @@ const getFees = async (a, transaction) => {
} }
const LibcoreBridge: WalletBridge<Transaction> = { const LibcoreBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice(currency, devicePath, observer) { scanAccountsOnDevice(currency, devicePath) {
return libcoreScanAccounts return libcoreScanAccounts
.send({ .send({
devicePath, devicePath,
currencyId: currency.id, currencyId: currency.id,
}) })
.pipe(map(decodeAccount)) .pipe(map(decodeAccount))
.subscribe(observer)
}, },
synchronize: account => synchronize: account =>

193
src/bridge/RippleJSBridge.js

@ -239,113 +239,114 @@ const getServerInfo = (map => endpointConfig => {
})({}) })({})
const RippleJSBridge: WalletBridge<Transaction> = { const RippleJSBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice(currency, deviceId, { next, complete, error }) { scanAccountsOnDevice: (currency, deviceId) =>
let finished = false Observable.create(o => {
const unsubscribe = () => { let finished = false
finished = true const unsubscribe = () => {
} finished = true
}
async function main() { async function main() {
const api = apiForEndpointConfig() const api = apiForEndpointConfig()
try { try {
await api.connect() await api.connect()
const serverInfo = await getServerInfo() const serverInfo = await getServerInfo()
const ledgers = serverInfo.completeLedgers.split('-') const ledgers = serverInfo.completeLedgers.split('-')
const minLedgerVersion = Number(ledgers[0]) const minLedgerVersion = Number(ledgers[0])
const maxLedgerVersion = Number(ledgers[1]) const maxLedgerVersion = Number(ledgers[1])
const derivations = getDerivations(currency)
for (const derivation of derivations) {
const legacy = derivation !== derivations[derivations.length - 1]
for (let index = 0; index < 255; index++) {
const freshAddressPath = derivation({ currency, x: index, segwit: false })
const { address, publicKey } = await await getAddress
.send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath })
.toPromise()
if (finished) return
const accountId = `ripplejs:${currency.id}:${address}:${publicKey}`
let info
try {
info = await api.getAccountInfo(address)
} catch (e) {
if (e.message !== 'actNotFound') {
throw e
}
}
// fresh address is address. ripple never changes. const derivations = getDerivations(currency)
const freshAddress = address for (const derivation of derivations) {
const legacy = derivation !== derivations[derivations.length - 1]
if (!info) { for (let index = 0; index < 255; index++) {
// account does not exist in Ripple server const freshAddressPath = derivation({ currency, x: index, segwit: false })
// we are generating a new account locally const { address, publicKey } = await await getAddress
if (!legacy) { .send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath })
next({ .toPromise()
id: accountId, if (finished) return
xpub: '',
name: getNewAccountPlaceholderName(currency, index), const accountId = `ripplejs:${currency.id}:${address}:${publicKey}`
freshAddress,
freshAddressPath, let info
balance: 0, try {
blockHeight: maxLedgerVersion, info = await api.getAccountInfo(address)
index, } catch (e) {
currency, if (e.message !== 'actNotFound') {
operations: [], throw e
pendingOperations: [], }
unit: currency.units[0],
archived: false,
lastSyncDate: new Date(),
})
} }
break
}
if (finished) return // fresh address is address. ripple never changes.
const balance = parseAPIValue(info.xrpBalance) const freshAddress = address
invariant(
!isNaN(balance) && isFinite(balance), if (!info) {
`Ripple: invalid balance=${balance} for address ${address}`, // account does not exist in Ripple server
) // we are generating a new account locally
if (!legacy) {
o.next({
id: accountId,
xpub: '',
name: getNewAccountPlaceholderName(currency, index),
freshAddress,
freshAddressPath,
balance: 0,
blockHeight: maxLedgerVersion,
index,
currency,
operations: [],
pendingOperations: [],
unit: currency.units[0],
archived: false,
lastSyncDate: new Date(),
})
}
break
}
const transactions = await api.getTransactions(address, { if (finished) return
minLedgerVersion, const balance = parseAPIValue(info.xrpBalance)
maxLedgerVersion, invariant(
}) !isNaN(balance) && isFinite(balance),
if (finished) return `Ripple: invalid balance=${balance} for address ${address}`,
)
const account: $Exact<Account> = {
id: accountId, const transactions = await api.getTransactions(address, {
xpub: '', minLedgerVersion,
name: getAccountPlaceholderName(currency, index, legacy), maxLedgerVersion,
freshAddress, })
freshAddressPath, if (finished) return
balance,
blockHeight: maxLedgerVersion, const account: $Exact<Account> = {
index, id: accountId,
currency, xpub: '',
operations: [], name: getAccountPlaceholderName(currency, index, legacy),
pendingOperations: [], freshAddress,
unit: currency.units[0], freshAddressPath,
lastSyncDate: new Date(), balance,
blockHeight: maxLedgerVersion,
index,
currency,
operations: [],
pendingOperations: [],
unit: currency.units[0],
lastSyncDate: new Date(),
}
account.operations = transactions.map(txToOperation(account))
o.next(account)
} }
account.operations = transactions.map(txToOperation(account))
next(account)
} }
o.complete()
} catch (e) {
o.error(e)
} finally {
api.disconnect()
} }
complete()
} catch (e) {
error(e)
} finally {
api.disconnect()
} }
}
main() main()
return { unsubscribe } return unsubscribe
}, }),
synchronize: ({ endpointConfig, freshAddress, blockHeight }) => synchronize: ({ endpointConfig, freshAddress, blockHeight }) =>
Observable.create(o => { Observable.create(o => {

8
src/bridge/UnsupportedBridge.js

@ -10,10 +10,10 @@ const UnsupportedBridge: WalletBridge<*> = {
o.error(genericError) o.error(genericError)
}), }),
scanAccountsOnDevice(currency, deviceId, { error }) { scanAccountsOnDevice: () =>
Promise.resolve(genericError).then(error) Observable.create(o => {
return { unsubscribe() {} } o.error(genericError)
}, }),
pullMoreOperations: () => Promise.reject(genericError), pullMoreOperations: () => Promise.reject(genericError),

55
src/bridge/makeMockBridge.js

@ -75,36 +75,37 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
} }
}), }),
scanAccountsOnDevice(currency, deviceId, { next, complete, error }) { scanAccountsOnDevice: currency =>
let unsubscribed = false Observable.create(o => {
let unsubscribed = false
async function job() {
if (Math.random() > scanAccountDeviceSuccessRate) { async function job() {
await delay(1000) if (Math.random() > scanAccountDeviceSuccessRate) {
if (!unsubscribed) error(new Error('scan failed')) await delay(1000)
return if (!unsubscribed) o.error(new Error('scan failed'))
} return
const nbAccountToGen = 3 }
for (let i = 0; i < nbAccountToGen && !unsubscribed; i++) { const nbAccountToGen = 3
await delay(500) for (let i = 0; i < nbAccountToGen && !unsubscribed; i++) {
const account = genAccount(String(Math.random()), { await delay(500)
operationsSize: 0, const account = genAccount(String(Math.random()), {
currency, operationsSize: 0,
}) currency,
account.unit = currency.units[0] })
if (!unsubscribed) next(account) account.unit = currency.units[0]
if (!unsubscribed) o.next(account)
}
if (!unsubscribed) o.complete()
} }
if (!unsubscribed) complete()
}
job() job()
return { return {
unsubscribe() { unsubscribe() {
unsubscribed = true unsubscribed = true
}, },
} }
}, }),
pullMoreOperations: async (_accountId, _desiredCount) => { pullMoreOperations: async (_accountId, _desiredCount) => {
await delay(1000) await delay(1000)

6
src/bridge/types.js

@ -33,11 +33,7 @@ export interface WalletBridge<Transaction> {
// the scan can stop once all accounts are discovered. // the scan can stop once all accounts are discovered.
// the function returns a Subscription and you MUST stop everything if it is unsubscribed. // the function returns a Subscription and you MUST stop everything if it is unsubscribed.
// TODO return Observable // TODO return Observable
scanAccountsOnDevice( scanAccountsOnDevice(currency: Currency, deviceId: DeviceId): Observable<Account>;
currency: Currency,
deviceId: DeviceId,
observer: Observer<Account>,
): Subscription;
// synchronize an account. meaning updating the account object with latest state. // synchronize an account. meaning updating the account object with latest state.
// function receives the initialAccount object so you can actually know what the user side currently have // function receives the initialAccount object so you can actually know what the user side currently have

6
src/components/modals/AddAccounts/steps/03-step-import.js

@ -17,7 +17,7 @@ import type { StepProps } from '../index'
class StepImport extends PureComponent<StepProps> { class StepImport extends PureComponent<StepProps> {
componentDidMount() { componentDidMount() {
this.startScanAccountsDevice() this.props.setState({ scanStatus: 'scanning' })
} }
componentDidUpdate(prevProps: StepProps) { componentDidUpdate(prevProps: StepProps) {
@ -72,9 +72,7 @@ class StepImport extends PureComponent<StepProps> {
// TODO: use the real device // TODO: use the real device
const devicePath = currentDevice.path const devicePath = currentDevice.path
setState({ scanStatus: 'scanning' }) this.scanSubscription = bridge.scanAccountsOnDevice(currency, devicePath).subscribe({
this.scanSubscription = bridge.scanAccountsOnDevice(currency, devicePath, {
next: account => { next: account => {
const { scannedAccounts, checkedAccountsIds, existingAccounts } = this.props const { scannedAccounts, checkedAccountsIds, existingAccounts } = this.props
const hasAlreadyBeenScanned = !!scannedAccounts.find(a => account.id === a.id) const hasAlreadyBeenScanned = !!scannedAccounts.find(a => account.id === a.id)

Loading…
Cancel
Save