From 436b388486595fd28403cb60d0a7b0dab99c57af Mon Sep 17 00:00:00 2001 From: classicalliu Date: Wed, 9 Oct 2019 13:55:09 +0800 Subject: [PATCH 01/16] feat: add lock to input --- .../src/database/chain/entities/input.ts | 9 +++++- .../1570522869590-AddLockToInput.ts | 17 +++++++++++ .../src/database/chain/ormconfig.ts | 8 +++++- packages/neuron-wallet/src/services/cells.ts | 2 ++ .../src/services/indexer/queue.ts | 27 ++++++++++++------ .../src/services/sync/get-blocks.ts | 28 ++++++++++++++----- .../src/services/tx/transaction-persistor.ts | 9 ++++++ 7 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 packages/neuron-wallet/src/database/chain/migrations/1570522869590-AddLockToInput.ts diff --git a/packages/neuron-wallet/src/database/chain/entities/input.ts b/packages/neuron-wallet/src/database/chain/entities/input.ts index 7b4ba19234..bf498f4e6b 100644 --- a/packages/neuron-wallet/src/database/chain/entities/input.ts +++ b/packages/neuron-wallet/src/database/chain/entities/input.ts @@ -1,5 +1,5 @@ import { Entity, BaseEntity, Column, ManyToOne, PrimaryGeneratedColumn } from 'typeorm' -import { OutPoint, Input as InputInterface } from 'types/cell-types' +import { OutPoint, Input as InputInterface, Script } from 'types/cell-types' import Transaction from './transaction' /* eslint @typescript-eslint/no-unused-vars: "warn" */ @@ -33,6 +33,13 @@ export default class Input extends BaseEntity { }) lockHash: string | null = null + // cellbase input has no previous output lock script + @Column({ + type: 'simple-json', + nullable: true, + }) + lock: Script | null = null + @ManyToOne(_type => Transaction, transaction => transaction.inputs, { onDelete: 'CASCADE' }) transaction!: Transaction diff --git a/packages/neuron-wallet/src/database/chain/migrations/1570522869590-AddLockToInput.ts b/packages/neuron-wallet/src/database/chain/migrations/1570522869590-AddLockToInput.ts new file mode 100644 index 0000000000..0763c385ff --- /dev/null +++ b/packages/neuron-wallet/src/database/chain/migrations/1570522869590-AddLockToInput.ts @@ -0,0 +1,17 @@ +import {MigrationInterface, QueryRunner, TableColumn} from "typeorm"; + +export class AddLockToInput1570522869590 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn('input', new TableColumn({ + name: 'lock', + type: 'text', + isNullable: true, + })) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn('input', 'lock') + } + +} diff --git a/packages/neuron-wallet/src/database/chain/ormconfig.ts b/packages/neuron-wallet/src/database/chain/ormconfig.ts index f054f9dc59..d71471384b 100644 --- a/packages/neuron-wallet/src/database/chain/ormconfig.ts +++ b/packages/neuron-wallet/src/database/chain/ormconfig.ts @@ -12,6 +12,7 @@ import SyncInfo from './entities/sync-info' import { InitMigration1566959757554 } from './migrations/1566959757554-InitMigration' import { AddTypeAndHasData1567144517514 } from './migrations/1567144517514-AddTypeAndHasData' import { ChangeHasDataDefault1568621556467 } from './migrations/1568621556467-ChangeHasDataDefault' +import { AddLockToInput1570522869590 } from './migrations/1570522869590-AddLockToInput' export const CONNECTION_NOT_FOUND_NAME = 'ConnectionNotFoundError' @@ -32,7 +33,12 @@ const connectOptions = async (genesisBlockHash: string): Promise { - let checkResult: boolean[][] = [] + public checkAndSave = async (blocks: Block[], lockHashes: string[]): Promise => { for (const block of blocks) { - const checkAndSave = new CheckAndSave(block, lockHashes) - const result = await checkAndSave.process() - checkResult.push(result) + for (const tx of block.transactions) { + const checkTx = new CheckTx(tx) + const addresses = await checkTx.check(lockHashes) + if (addresses.length > 0) { + for (const input of tx.inputs!) { + const previousTxWithStatus = await this.getTransaction(input.previousOutput!.txHash) + const previousTx = TypeConvert.toTransaction(previousTxWithStatus.transaction) + const previousOutput = previousTx.outputs![+input.previousOutput!.index] + input.lock = previousOutput.lock + input.lockHash = LockUtils.lockScriptToHash(input.lock) + input.capacity = previousOutput.capacity + } + await TransactionPersistor.saveFetchTx(tx) + addressesUsedSubject.next(addresses) + } + } } - return checkResult } public retryGetBlock = async (num: string): Promise => { diff --git a/packages/neuron-wallet/src/services/tx/transaction-persistor.ts b/packages/neuron-wallet/src/services/tx/transaction-persistor.ts index 504bb92cef..1f885fa49e 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-persistor.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-persistor.ts @@ -117,6 +117,7 @@ export class TransactionPersistor { input.transaction = tx input.capacity = i.capacity || null input.lockHash = i.lockHash || null + input.lock = i.lock || null input.since = i.since! inputs.push(input) @@ -263,6 +264,14 @@ export class TransactionPersistor { return txEntity } + public static get = async (txHash: string) => { + const txEntity: TransactionEntity | undefined = await getConnection() + .getRepository(TransactionEntity) + .findOne(txHash, { relations: ['inputs', 'outputs'] }) + + return txEntity + } + public static saveSentTx = async ( transaction: TransactionWithoutHash, txHash: string From 109dfe3ed236d9b2421769325680647f0be366fc Mon Sep 17 00:00:00 2001 From: CL Date: Wed, 9 Oct 2019 14:06:06 +0800 Subject: [PATCH 02/16] chore: remove semicolon in import Co-Authored-By: Chen Yu --- packages/neuron-wallet/src/services/indexer/queue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neuron-wallet/src/services/indexer/queue.ts b/packages/neuron-wallet/src/services/indexer/queue.ts index 68ee8377e2..2bdc71f6c4 100644 --- a/packages/neuron-wallet/src/services/indexer/queue.ts +++ b/packages/neuron-wallet/src/services/indexer/queue.ts @@ -13,7 +13,7 @@ import IndexerTransaction from 'services/tx/indexer-transaction' import IndexerRPC from './indexer-rpc' import HexUtils from 'utils/hex' import { TxUniqueFlagCache } from './tx-unique-flag' -import TransactionEntity from 'database/chain/entities/transaction'; +import TransactionEntity from 'database/chain/entities/transaction' export interface LockHashInfo { lockHash: string From 116646cadbe3f3634fb6f39e9f9672044b1f0275 Mon Sep 17 00:00:00 2001 From: classicalliu Date: Wed, 9 Oct 2019 14:37:18 +0800 Subject: [PATCH 03/16] chore: catch `updateMetaInfo` error when init database --- .../src/startup/sync-block-task/init-database.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/neuron-wallet/src/startup/sync-block-task/init-database.ts b/packages/neuron-wallet/src/startup/sync-block-task/init-database.ts index 2728d9baae..635fd1df81 100644 --- a/packages/neuron-wallet/src/startup/sync-block-task/init-database.ts +++ b/packages/neuron-wallet/src/startup/sync-block-task/init-database.ts @@ -9,8 +9,13 @@ export const initDatabase = async () => { try { const hash = await genesisBlockHash() await initConnection(hash) - const systemScriptInfo = await LockUtils.systemScript() - updateMetaInfo({ genesisBlockHash: hash, systemScriptInfo }) + + try { + const systemScriptInfo = await LockUtils.systemScript() + updateMetaInfo({ genesisBlockHash: hash, systemScriptInfo }) + } catch (err) { + logger.error('update systemScriptInfo failed:', err) + } } catch (err) { logger.debug('initDatabase error:', err) try { From 9de23ccaacb2c86bcd42ebca618a3b089666fccb Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Thu, 10 Oct 2019 11:11:20 +0800 Subject: [PATCH 04/16] Dismiss alerts if correct actions are taken (#976) * feat(neuron-wallet): notify ui the error code if it exists * feat(neuron-ui): dismiss password-incorrect alerts once a correct one is inputted. * test(e2e): test the auto-dismission when the correct password is submitted. * feat(neuron-ui): dismiss password-incorrect alerts on sending transactions successfully * Update packages/neuron-wallet/tests-e2e/tests/notification.ts Co-Authored-By: CL --- packages/neuron-ui/src/locales/en.json | 2 ++ packages/neuron-ui/src/locales/zh.json | 2 ++ .../remote/controllerMethodWrapper.ts | 4 +-- .../stateProvider/actionCreators/app.ts | 2 +- .../actionCreators/transactions.ts | 4 +-- .../stateProvider/actionCreators/wallets.ts | 26 ++++++++++++------- .../src/states/stateProvider/reducer.ts | 13 ++++++++++ packages/neuron-ui/src/utils/const.ts | 1 + .../src/controllers/wallets/index.ts | 2 +- .../neuron-wallet/src/decorators/errors.ts | 7 ++++- .../neuron-wallet/src/exceptions/wallet.ts | 1 + .../tests-e2e/tests/notification.ts | 17 +++++++++++- 12 files changed, 64 insertions(+), 17 deletions(-) diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 34c89e06a3..27d4f4b88c 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -274,6 +274,8 @@ "100": "Amount is not enough", "101": "The amount {{amount}} CKB is too small, please enter an amount no less than 61 CKB", "102": "$t(messages.fields.{{fieldName}}) is invalid", + "103": "$t(messages.fields.keystore-password) is incorrect", + "104": "Connection to the node is failed", "201": "$t(messages.fields.{{fieldName}}) is required", "202": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is used", "203": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is too long, it should be shorter than or equal to {{length}}", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 5c18d9f008..9e018928df 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -274,6 +274,8 @@ "100": "余额不足", "101": "金额 {{amount}} CKB 太小, 请输入一个不小于 61 CKB 的值", "102": "$t(messages.fields.{{fieldName}})无效", + "103": "$t(messages.fields.keystore-password) 不正确", + "104": "未连接到节点", "201": "缺少$t(messages.fields.{{fieldName}})", "202": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 已被使用", "203": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 太长, 其长度应不超过 {{length}}", diff --git a/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts b/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts index 6bb884f9af..1ff755309d 100644 --- a/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts +++ b/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts @@ -60,7 +60,7 @@ export const controllerMethodWrapper = (controllerName: string) => ( } } - if (res.status) { + if (res.status === 1) { return { status: 1, result: res.result || null, @@ -68,7 +68,7 @@ export const controllerMethodWrapper = (controllerName: string) => ( } return { - status: 0, + status: res.status || 0, message: typeof res.message === 'string' ? { content: res.message } : res.message || '', } } diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts index a517ad397e..1111c41d9d 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts @@ -15,7 +15,7 @@ import { export const initAppState = () => (dispatch: StateDispatch, history: any) => { getNeuronWalletState() .then(res => { - if (res.status) { + if (res.status === 1) { const { wallets = [], currentWallet: wallet = initStates.wallet, diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts index c44d227137..f960734a5e 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts @@ -9,7 +9,7 @@ import { addNotification } from './app' export const updateTransactionList = (params: GetTransactionListParams) => (dispatch: StateDispatch) => { getTransactionList(params).then(res => { - if (res.status) { + if (res.status === 1) { dispatch({ type: NeuronWalletActions.UpdateTransactionList, payload: res.result, @@ -32,7 +32,7 @@ export const updateTransactionDescription = (params: Controller.UpdateTransactio payload: descriptionParams, }) // update local description before remote description to avoid the flicker on the field updateRemoteTransactionDescription(params).then(res => { - if (res.status) { + if (res.status === 1) { dispatch({ type: NeuronWalletActions.UpdateTransactionDescription, payload: descriptionParams, diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts index 4801c91848..b740546667 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts @@ -18,7 +18,7 @@ import initStates from 'states/initStates' import { WalletWizardPath } from 'components/WalletWizard' import i18n from 'utils/i18n' import { wallets as walletsCache, currentWallet as currentWalletCache } from 'services/localCache' -import { Routes } from 'utils/const' +import { Routes, ErrorCode } from 'utils/const' import { addressesToBalance, failureResToNotification } from 'utils/formatters' import { NeuronWalletActions } from '../reducer' import { addNotification, addPopup } from './app' @@ -118,7 +118,7 @@ export const updateWalletProperty = (params: Controller.UpdateWalletParams) => ( history?: any ) => { updateWallet(params).then(res => { - if (res.status) { + if (res.status === 1) { addPopup('update-wallet-successfully')(dispatch) if (history) { history.push(Routes.SettingsWallets) @@ -130,7 +130,7 @@ export const updateWalletProperty = (params: Controller.UpdateWalletParams) => ( } export const setCurrentWallet = (id: string) => (dispatch: StateDispatch) => { setRemoteCurrentWallet(id).then(res => { - if (res.status) { + if (res.status === 1) { dispatch({ type: AppActions.Ignore, payload: null, @@ -152,6 +152,10 @@ export const sendTransaction = (params: Controller.SendTransaction) => (dispatch sendCapacity(params) .then(res => { if (res.status === 1) { + dispatch({ + type: AppActions.ClearNotificationsOfCode, + payload: ErrorCode.PasswordIncorrect, + }) history.push(Routes.History) } else { addNotification({ @@ -188,7 +192,7 @@ export const updateAddressListAndBalance = (params: Controller.GetAddressesByWal dispatch: StateDispatch ) => { getAddressesByWalletID(params).then(res => { - if (res.status) { + if (res.status === 1) { const addresses = res.result || [] const balance = addressesToBalance(addresses) dispatch({ @@ -213,7 +217,7 @@ export const updateAddressDescription = (params: Controller.UpdateAddressDescrip payload: descriptionParams, }) updateRemoteAddressDescription(params).then(res => { - if (res.status) { + if (res.status === 1) { dispatch({ type: NeuronWalletActions.UpdateAddressDescription, payload: descriptionParams, @@ -230,8 +234,12 @@ export const deleteWallet = (params: Controller.DeleteWalletParams) => (dispatch payload: null, }) deleteRemoteWallet(params).then(res => { - if (res.status) { + if (res.status === 1) { addPopup('delete-wallet-successfully')(dispatch) + dispatch({ + type: AppActions.ClearNotificationsOfCode, + payload: ErrorCode.PasswordIncorrect, + }) } else { addNotification(failureResToNotification(res))(dispatch) } @@ -244,10 +252,10 @@ export const backupWallet = (params: Controller.BackupWalletParams) => (dispatch payload: null, }) backupRemoteWallet(params).then(res => { - if (res.status) { + if (res.status === 1) { dispatch({ - type: AppActions.Ignore, - payload: null, + type: AppActions.ClearNotificationsOfCode, + payload: ErrorCode.PasswordIncorrect, }) } else { addNotification(failureResToNotification(res))(dispatch) diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index 9012980aed..f18446bdda 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -34,6 +34,7 @@ export enum AppActions { UpdateMessage = 'updateMessage', AddNotification = 'addNotification', DismissNotification = 'dismissNotification', + ClearNotificationsOfCode = 'clearNotificationsOfCode', ClearNotifications = 'clearNotifications', CleanTransaction = 'cleanTransaction', CleanTransactions = 'cleanTransactions', @@ -474,6 +475,18 @@ export const reducer = ( }, } } + case AppActions.ClearNotificationsOfCode: { + return { + ...state, + app: { + ...app, + messages: { + ...app.messages, + }, + notifications: app.notifications.filter(({ code }) => code !== payload), + }, + } + } case AppActions.ClearNotifications: { return { ...state, diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts index ad02db9781..656190adf3 100644 --- a/packages/neuron-ui/src/utils/const.ts +++ b/packages/neuron-ui/src/utils/const.ts @@ -71,6 +71,7 @@ export enum ErrorCode { // Errors from neuron-wallet AmountNotEnough = 100, AmountTooSmall = 101, + PasswordIncorrect = 103, // Parameter validation errors from neuron-ui FieldRequired = 201, FieldUsed = 202, diff --git a/packages/neuron-wallet/src/controllers/wallets/index.ts b/packages/neuron-wallet/src/controllers/wallets/index.ts index 5e913e97eb..6bae1cdad3 100644 --- a/packages/neuron-wallet/src/controllers/wallets/index.ts +++ b/packages/neuron-wallet/src/controllers/wallets/index.ts @@ -387,7 +387,7 @@ export default class WalletsController { } catch (err) { logger.error(`sendCapacity:`, err) return { - status: ResponseCode.Fail, + status: err.code || ResponseCode.Fail, message: `Error: "${err.message}"`, } } diff --git a/packages/neuron-wallet/src/decorators/errors.ts b/packages/neuron-wallet/src/decorators/errors.ts index ffaf91dec8..d61af6c823 100644 --- a/packages/neuron-wallet/src/decorators/errors.ts +++ b/packages/neuron-wallet/src/decorators/errors.ts @@ -1,6 +1,8 @@ import { ResponseCode } from 'utils/const' import logger from 'utils/logger' +const NODE_DISCONNECTED_CODE = 104 + export const CatchControllerError = (_target: any, _name: string, descriptor: PropertyDescriptor) => { const originalMethod = descriptor.value return { @@ -10,8 +12,11 @@ export const CatchControllerError = (_target: any, _name: string, descriptor: Pr return await originalMethod(...args) } catch (err) { logger.error(`CatchControllerError:`, err) + if (err.code === 'ECONNREFUSED') { + err.code = NODE_DISCONNECTED_CODE + } return { - status: ResponseCode.Fail, + status: err.code || ResponseCode.Fail, message: typeof err.message === 'string' ? { content: err.message } : err.message, } } diff --git a/packages/neuron-wallet/src/exceptions/wallet.ts b/packages/neuron-wallet/src/exceptions/wallet.ts index e766a0b868..b621f0781c 100644 --- a/packages/neuron-wallet/src/exceptions/wallet.ts +++ b/packages/neuron-wallet/src/exceptions/wallet.ts @@ -12,6 +12,7 @@ export class WalletNotFound extends Error { } export class IncorrectPassword extends Error { + public code = 103 constructor() { super(i18n.t('messages.incorrect-password')) } diff --git a/packages/neuron-wallet/tests-e2e/tests/notification.ts b/packages/neuron-wallet/tests-e2e/tests/notification.ts index ec17256386..6db920368a 100644 --- a/packages/neuron-wallet/tests-e2e/tests/notification.ts +++ b/packages/neuron-wallet/tests-e2e/tests/notification.ts @@ -11,6 +11,7 @@ import { sleep } from '../application/utils' * 6. check the notification, it should have two messages * 1. incorrect PasswordRequest * 2. disconnected to the network + * 7. password-incorrect alerts should be dismissed once a correct one is inputted */ export default (app: Application) => { beforeAll(async () => { @@ -32,7 +33,7 @@ export default (app: Application) => { describe('Test alert message and notification', () => { const messages = { - disconnected: 'connect ECONNREFUSED 127.0.0.1:8114', + disconnected: 'Connection to the node is failed', incorrectPassword: 'Password is incorrect', } @@ -68,5 +69,19 @@ export default (app: Application) => { }) // TODO: dismiss a message + + app.test('Password-incorrect alerts should be dismissed once a correct one is inputted', async () => { + const { client } = app.spectron + await app.clickMenu(['Wallet', 'Delete Current Wallet']) + await app.waitUntilLoaded() + const inputElement = await client.$('input') + await client.elementIdValue(inputElement.value.ELEMENT, 'Azusa2233') + client.click('button[type=submit]') + await app.waitUntilLoaded() + sleep(4000) + const alertComponent = await client.$('.ms-MessageBar-text') + const msg = await client.elementIdText(alertComponent.value.ELEMENT) + expect(msg.value).toBe(messages.disconnected) + }) }) } From 7599cb7b20038b106f94218e39ce5ec04f87b0c7 Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 11 Oct 2019 11:44:26 +0800 Subject: [PATCH 05/16] feat(neuron-ui): update the epoch index --- packages/neuron-ui/src/components/History/hooks.ts | 2 +- packages/neuron-ui/src/components/Overview/index.tsx | 3 ++- packages/neuron-ui/src/utils/{parser.ts => parsers.ts} | 10 +++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) rename packages/neuron-ui/src/utils/{parser.ts => parsers.ts} (76%) diff --git a/packages/neuron-ui/src/components/History/hooks.ts b/packages/neuron-ui/src/components/History/hooks.ts index 327f6ecbb3..952fa20051 100644 --- a/packages/neuron-ui/src/components/History/hooks.ts +++ b/packages/neuron-ui/src/components/History/hooks.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react' import { updateTransactionList } from 'states/stateProvider/actionCreators/transactions' -import { queryParsers } from 'utils/parser' +import { queryParsers } from 'utils/parsers' import { backToTop } from 'utils/animations' export const useSearch = (search: string = '', walletID: string = '', dispatch: React.Dispatch) => { diff --git a/packages/neuron-ui/src/components/Overview/index.tsx b/packages/neuron-ui/src/components/Overview/index.tsx index 7bab7bd724..d17d783a25 100644 --- a/packages/neuron-ui/src/components/Overview/index.tsx +++ b/packages/neuron-ui/src/components/Overview/index.tsx @@ -26,6 +26,7 @@ import { updateTransactionList } from 'states/stateProvider/actionCreators' import { showTransactionDetails } from 'services/remote' import { localNumberFormatter, shannonToCKBFormatter, uniformTimeFormatter as timeFormatter } from 'utils/formatters' +import { epochParser } from 'utils/parsers' import { PAGE_SIZE, Routes, CONFIRMATION_THRESHOLD } from 'utils/const' import { backToTop } from 'utils/animations' @@ -250,7 +251,7 @@ const Overview = ({ }, { label: t('overview.epoch'), - value: epoch, + value: epochParser(epoch).index, }, { label: t('overview.difficulty'), diff --git a/packages/neuron-ui/src/utils/parser.ts b/packages/neuron-ui/src/utils/parsers.ts similarity index 76% rename from packages/neuron-ui/src/utils/parser.ts rename to packages/neuron-ui/src/utils/parsers.ts index dfe5b28bb8..1c254a215d 100644 --- a/packages/neuron-ui/src/utils/parser.ts +++ b/packages/neuron-ui/src/utils/parsers.ts @@ -23,4 +23,12 @@ export const prompt = (search: string) => { } export const queryParsers = { history, prompt } -export default { queryParsers } +/* eslint-disable no-bitwise */ +export const epochParser = (epoch: string) => { + return { + index: +epoch & 0xffff, + } +} +/* eslint-enable no-bitwise */ + +export default { queryParsers, epochParser } From c5c51012e1dd2c5c96e74d789e0aeee5eadfae4c Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 11 Oct 2019 13:34:50 +0800 Subject: [PATCH 06/16] feat(neuron-ui): remove eslint rule of no-bitwise --- packages/neuron-ui/.eslintrc.js | 3 ++- packages/neuron-ui/src/utils/parsers.ts | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/neuron-ui/.eslintrc.js b/packages/neuron-ui/.eslintrc.js index b61ea63ad5..bfb9d8ef29 100644 --- a/packages/neuron-ui/.eslintrc.js +++ b/packages/neuron-ui/.eslintrc.js @@ -72,7 +72,8 @@ module.exports = { "no-alert": [0], "no-console": [2, { "allow": ["info", "warn", "error", "group", "groupEnd"] - }] + }], + "no-bitwise": [0] }, "env": { "jest": true, diff --git a/packages/neuron-ui/src/utils/parsers.ts b/packages/neuron-ui/src/utils/parsers.ts index 1c254a215d..65a76e0f9a 100644 --- a/packages/neuron-ui/src/utils/parsers.ts +++ b/packages/neuron-ui/src/utils/parsers.ts @@ -23,12 +23,10 @@ export const prompt = (search: string) => { } export const queryParsers = { history, prompt } -/* eslint-disable no-bitwise */ export const epochParser = (epoch: string) => { return { index: +epoch & 0xffff, } } -/* eslint-enable no-bitwise */ export default { queryParsers, epochParser } From b1ea6d556757982e110700350d4ee9122916573f Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 11 Oct 2019 14:26:37 +0800 Subject: [PATCH 07/16] feat(neuron-ui): dismiss pinned top alert if it's related to auto-dismissed notifications --- packages/neuron-ui/src/states/stateProvider/reducer.ts | 4 ++++ packages/neuron-wallet/tests-e2e/tests/notification.ts | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index f18446bdda..15f22e5023 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -483,6 +483,10 @@ export const reducer = ( messages: { ...app.messages, }, + showTopAlert: !( + app.notifications[app.notifications.length - 1] && + app.notifications[app.notifications.length - 1].code === payload + ), notifications: app.notifications.filter(({ code }) => code !== payload), }, } diff --git a/packages/neuron-wallet/tests-e2e/tests/notification.ts b/packages/neuron-wallet/tests-e2e/tests/notification.ts index 6db920368a..7577d4d207 100644 --- a/packages/neuron-wallet/tests-e2e/tests/notification.ts +++ b/packages/neuron-wallet/tests-e2e/tests/notification.ts @@ -79,9 +79,8 @@ export default (app: Application) => { client.click('button[type=submit]') await app.waitUntilLoaded() sleep(4000) - const alertComponent = await client.$('.ms-MessageBar-text') - const msg = await client.elementIdText(alertComponent.value.ELEMENT) - expect(msg.value).toBe(messages.disconnected) + const alertComponent = await client.$('.ms-MessageBar--error') + expect(alertComponent.state).toBe('failure') }) }) } From aa34515b543218c46c6844b322b07a5ac687ef0b Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 11 Oct 2019 17:21:48 +0800 Subject: [PATCH 08/16] fix(neuron-ui): fix the type of script.args from string[] to string --- packages/neuron-ui/src/components/Transaction/index.tsx | 2 +- packages/neuron-ui/src/types/App/index.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/neuron-ui/src/components/Transaction/index.tsx b/packages/neuron-ui/src/components/Transaction/index.tsx index 521f65e83b..f7fa80f1e7 100644 --- a/packages/neuron-ui/src/components/Transaction/index.tsx +++ b/packages/neuron-ui/src/components/Transaction/index.tsx @@ -95,7 +95,7 @@ const Transaction = () => { return null } try { - const address = ckbCore.utils.bech32Address(output.lock.args[0], { + const address = ckbCore.utils.bech32Address(output.lock.args, { prefix: addressPrefix, type: ckbCore.utils.AddressType.HashIdx, codeHashIndex: '0x00', diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index a87047d0ba..4899aa4a23 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -14,7 +14,7 @@ declare namespace State { interface DetailedOutput { capacity: string lock: { - args: string[] + args: string codeHash: string } lockHash: string From f1812300afae2e78d220f8862c0883e43c2e1147 Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 11 Oct 2019 14:52:27 +0800 Subject: [PATCH 09/16] refactor(neuron-ui): make the condition of clear notifications of code more clear --- .../neuron-ui/src/states/stateProvider/reducer.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index 15f22e5023..a040a2eeb7 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -476,6 +476,7 @@ export const reducer = ( } } case AppActions.ClearNotificationsOfCode: { + const notifications = app.notifications.filter(({ code }) => code !== payload) return { ...state, app: { @@ -483,11 +484,11 @@ export const reducer = ( messages: { ...app.messages, }, - showTopAlert: !( - app.notifications[app.notifications.length - 1] && - app.notifications[app.notifications.length - 1].code === payload - ), - notifications: app.notifications.filter(({ code }) => code !== payload), + showTopAlert: + app.showTopAlert && + notifications.length > 0 && + !(app.notifications.length > 0 && app.notifications[app.notifications.length - 1].code === payload), + notifications, }, } } From a36abbab611d652121e794984dc58a5e6913c516 Mon Sep 17 00:00:00 2001 From: Keith Date: Sat, 12 Oct 2019 11:24:46 +0800 Subject: [PATCH 10/16] feat(neuron-ui): make the alert of lacking remote module more clear The electron app is required explicitly --- .../services/remote/controllerMethodWrapper.ts | 2 +- packages/neuron-ui/src/services/remote/index.ts | 17 ++++++++++------- packages/neuron-ui/src/services/subjects.ts | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts b/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts index 1ff755309d..7b86622d51 100644 --- a/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts +++ b/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts @@ -17,7 +17,7 @@ export type ControllerResponse = SuccessFromController | FailureFromController export const RemoteNotLoadError = { status: 0 as 0, message: { - content: 'remote is not supported', + content: 'The remote module is not found, please make sure the UI is running inside the Electron App', }, } diff --git a/packages/neuron-ui/src/services/remote/index.ts b/packages/neuron-ui/src/services/remote/index.ts index c7e61bfd36..7acb36bd6f 100644 --- a/packages/neuron-ui/src/services/remote/index.ts +++ b/packages/neuron-ui/src/services/remote/index.ts @@ -4,9 +4,12 @@ export * from './networks' export * from './transactions' export * from './skipDataAndType' +const REMOTE_MODULE_NOT_FOUND = + 'The remote module is not found, please make sure the UI is running inside the Electron App' + export const getLocale = () => { if (!window.remote) { - console.warn('remote is not supported') + console.warn(REMOTE_MODULE_NOT_FOUND) return window.navigator.language } return window.remote.require('electron').app.getLocale() @@ -14,7 +17,7 @@ export const getLocale = () => { export const getWinID = () => { if (!window.remote) { - console.warn('remote is not supported') + console.warn(REMOTE_MODULE_NOT_FOUND) return -1 } return window.remote.getCurrentWindow().id @@ -22,7 +25,7 @@ export const getWinID = () => { export const validateMnemonic = (mnemonic: string): boolean => { if (!window.remote) { - console.warn('remote is not supported') + console.warn(REMOTE_MODULE_NOT_FOUND) return true } const { validateMnemonic: remoteValidateMnemonic } = window.remote.require('./models/keys/mnemonic') @@ -31,7 +34,7 @@ export const validateMnemonic = (mnemonic: string): boolean => { export const generateMnemonic = (): string => { if (!window.remote) { - console.warn('remote is not supported') + console.warn(REMOTE_MODULE_NOT_FOUND) return '' } const { generateMnemonic: remoteGenerateMnemonic } = window.remote.require('./models/keys/key') @@ -40,7 +43,7 @@ export const generateMnemonic = (): string => { export const showMessage = (options: any, callback: Function) => { if (!window.remote) { - console.warn('remote is not supported') + console.warn(REMOTE_MODULE_NOT_FOUND) window.alert(options.message) } else { window.remote.require('electron').dialog.showMessageBox(options, callback) @@ -49,7 +52,7 @@ export const showMessage = (options: any, callback: Function) => { export const showErrorMessage = (title: string, content: string) => { if (!window.remote) { - console.warn('remote is not supported') + console.warn(REMOTE_MODULE_NOT_FOUND) window.alert(`${title}: ${content}`) } else { window.remote.require('electron').dialog.showErrorBox(title, content) @@ -58,7 +61,7 @@ export const showErrorMessage = (title: string, content: string) => { export const showOpenDialog = (opt: { title: string; message?: string; onUpload: Function }) => { if (!window.remote) { - window.alert('remote is not supported') + window.alert(REMOTE_MODULE_NOT_FOUND) } const { onUpload, ...options } = opt return window.remote.require('electron').dialog.showOpenDialog( diff --git a/packages/neuron-ui/src/services/subjects.ts b/packages/neuron-ui/src/services/subjects.ts index f18331f18e..dc9566c05e 100644 --- a/packages/neuron-ui/src/services/subjects.ts +++ b/packages/neuron-ui/src/services/subjects.ts @@ -1,6 +1,6 @@ const FallbackSubject = { subscribe: (args: any) => { - console.warn('remote is not supported') + console.warn('The remote module is not found, please make sure the UI is running inside the Electron App') console.info(JSON.stringify(args)) return { unsubscribe: () => { From be5a01400a0a47eff4b7567810b20d347adc7627 Mon Sep 17 00:00:00 2001 From: classicalliu Date: Sat, 12 Oct 2019 17:30:36 +0800 Subject: [PATCH 11/16] fix: not sync after switch network --- .../neuron-wallet/src/database/address/dao.ts | 10 +- .../neuron-wallet/src/listeners/address.ts | 18 +-- .../neuron-wallet/src/listeners/tx-status.ts | 6 +- .../neuron-wallet/src/models/lock-utils.ts | 58 +++++++--- .../models/subjects/addresses-used-subject.ts | 9 +- .../neuron-wallet/src/services/addresses.ts | 8 +- .../src/services/indexer/queue.ts | 13 ++- .../src/services/sync/check-and-save/index.ts | 6 +- .../src/services/sync/check-and-save/tx.ts | 17 ++- .../src/services/sync/get-blocks.ts | 9 +- .../src/services/tx/transaction-service.ts | 9 +- .../neuron-wallet/src/services/wallets.ts | 5 +- .../src/startup/sync-block-task/create.ts | 31 ++++-- .../src/startup/sync-block-task/genesis.ts | 7 +- .../src/startup/sync-block-task/indexer.ts | 14 +-- .../startup/sync-block-task/init-database.ts | 103 ++++++++++++++---- .../src/startup/sync-block-task/sync.ts | 14 +-- .../src/startup/sync-block-task/task.ts | 11 +- 18 files changed, 253 insertions(+), 95 deletions(-) diff --git a/packages/neuron-wallet/src/database/address/dao.ts b/packages/neuron-wallet/src/database/address/dao.ts index 0d2a72b363..3b6c295b2c 100644 --- a/packages/neuron-wallet/src/database/address/dao.ts +++ b/packages/neuron-wallet/src/database/address/dao.ts @@ -7,6 +7,7 @@ import { TransactionStatus } from 'types/cell-types' import { OutputStatus } from 'services/tx/params' import AddressEntity, { AddressVersion } from './entities/address' import { getConnection } from './ormconfig' +import NodeService from 'services/node' export interface Address { walletId: string @@ -54,7 +55,10 @@ export default class AddressDao { // so the final balance is (liveBalance + sentBalance - pendingBalance) // balance is the balance of the cells those who don't hold data or type script // totalBalance means balance of all cells, including those who hold data and type script - public static updateTxCountAndBalance = async (address: string): Promise => { + public static updateTxCountAndBalance = async ( + address: string, + url: string = NodeService.getInstance().core.rpc.node.url + ): Promise => { const addressEntities = await getConnection() .getRepository(AddressEntity) .find({ @@ -64,12 +68,12 @@ export default class AddressDao { const txCount: number = await TransactionsService.getCountByAddressAndStatus(address, [ TransactionStatus.Pending, TransactionStatus.Success, - ]) + ], url) const entities = await Promise.all( addressEntities.map(async entity => { const addressEntity = entity addressEntity.txCount = txCount - const lockHashes: string[] = await LockUtils.addressToAllLockHashes(addressEntity.address) + const lockHashes: string[] = await LockUtils.addressToAllLockHashes(addressEntity.address, url) addressEntity.liveBalance = await CellsService.getBalance(lockHashes, OutputStatus.Live, true) addressEntity.sentBalance = await CellsService.getBalance(lockHashes, OutputStatus.Sent, true) addressEntity.pendingBalance = await CellsService.getBalance(lockHashes, OutputStatus.Pending, true) diff --git a/packages/neuron-wallet/src/listeners/address.ts b/packages/neuron-wallet/src/listeners/address.ts index 1a1e91424e..6b3994240e 100644 --- a/packages/neuron-wallet/src/listeners/address.ts +++ b/packages/neuron-wallet/src/listeners/address.ts @@ -1,7 +1,7 @@ import { remote } from 'electron' import { ReplaySubject } from 'rxjs' import { bufferTime } from 'rxjs/operators' -import AddressesUsedSubject from 'models/subjects/addresses-used-subject' +import AddressesUsedSubject, { AddressesWithURL } from 'models/subjects/addresses-used-subject' import AddressService from 'services/addresses' import WalletService from 'services/wallets' import { AccountExtendedPublicKey } from 'models/keys/key' @@ -12,17 +12,21 @@ const addressesUsedSubject = isRenderer : AddressesUsedSubject.getSubject() // pipe not working directly -const bridge = new ReplaySubject(1000) -addressesUsedSubject.subscribe((addresses: string[]) => { - bridge.next(addresses) +const bridge = new ReplaySubject(1000) +addressesUsedSubject.subscribe((params: AddressesWithURL) => { + bridge.next(params) }) // update txCount when addresses used export const register = () => { - bridge.pipe(bufferTime(1000)).subscribe(async (addressesList: string[][]) => { - const addresses = addressesList.reduce((acc, val) => acc.concat(val), []) + bridge.pipe(bufferTime(1000)).subscribe(async (addressesList: AddressesWithURL[]) => { + if (addressesList.length === 0) { + return + } + const addresses = addressesList.map(list => list.addresses).reduce((acc, val) => acc.concat(val), []) + const url: string = addressesList[addressesList.length - 1].url const uniqueAddresses = [...new Set(addresses)] - const addrs = await AddressService.updateTxCountAndBalances(uniqueAddresses) + const addrs = await AddressService.updateTxCountAndBalances(uniqueAddresses, url) const walletIds: string[] = addrs.map(addr => addr.walletId).filter((value, idx, a) => a.indexOf(value) === idx) await Promise.all( walletIds.map(async id => { diff --git a/packages/neuron-wallet/src/listeners/tx-status.ts b/packages/neuron-wallet/src/listeners/tx-status.ts index aaf1cb4e28..3f9be3f9e8 100644 --- a/packages/neuron-wallet/src/listeners/tx-status.ts +++ b/packages/neuron-wallet/src/listeners/tx-status.ts @@ -61,7 +61,11 @@ const trackingStatus = async () => { if (failedTxs.length) { const blake160s = await FailedTransaction.updateFailedTxs(failedTxs.map(tx => tx.hash)) const usedAddresses = blake160s.map(blake160 => LockUtils.blake160ToAddress(blake160)) - AddressesUsedSubject.getSubject().next(usedAddresses) + const { core } = nodeService + AddressesUsedSubject.getSubject().next({ + addresses: usedAddresses, + url: core.rpc.node.url, + }) } if (successTxs.length > 0) { diff --git a/packages/neuron-wallet/src/models/lock-utils.ts b/packages/neuron-wallet/src/models/lock-utils.ts index 6ac0094768..13165a5c02 100644 --- a/packages/neuron-wallet/src/models/lock-utils.ts +++ b/packages/neuron-wallet/src/models/lock-utils.ts @@ -1,11 +1,16 @@ -import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils' +import { + scriptToHash, + AddressPrefix, + bech32Address, + AddressType, + parseAddress +} from '@nervosnetwork/ckb-sdk-utils' import NodeService from 'services/node' import { OutPoint, Script, ScriptHashType } from 'types/cell-types' import env from 'env' import ConvertTo from 'types/convert-to' import { SystemScriptSubject } from 'models/subjects/system-script' - -const { core } = NodeService.getInstance() +import Core from '@nervosnetwork/ckb-sdk-core' export interface SystemScript { codeHash: string @@ -28,11 +33,15 @@ export default class LockUtils { @subscribed static systemScriptInfo: SystemScript | undefined - static async systemScript(): Promise { - if (this.systemScriptInfo) { + static previousURL: string | undefined + + static async systemScript(nodeURL: string = NodeService.getInstance().core.rpc.node.url): Promise { + if (this.systemScriptInfo && nodeURL === LockUtils.previousURL) { return this.systemScriptInfo } + const core = new Core(nodeURL) + const systemCell = await core.loadSecp256k1Dep() let { codeHash } = systemCell const { outPoint, hashType } = systemCell @@ -57,6 +66,7 @@ export default class LockUtils { } this.systemScriptInfo = systemScriptInfo + LockUtils.previousURL = nodeURL return systemScriptInfo } @@ -80,8 +90,12 @@ export default class LockUtils { return LockUtils.computeScriptHash(lock) } - static async addressToLockScript(address: string, hashType: ScriptHashType = ScriptHashType.Type): Promise