= {}
+ res.result.forEach(v => {
+ if (hashToPayload[v.hash]) {
+ tmp[hashToPayload[v.hash]] = v.blockStartNumber
+ }
+ })
+ setMultisigSyncProgress(tmp)
+ }
+ })
+ }, [hashToPayload])
useEffect(() => {
const dataUpdateSubscription = MultisigOutputUpdate.subscribe(() => {
getAndSaveMultisigBalances()
+ if (isLightClient) {
+ getAndSaveMultisigSyncProgress()
+ }
})
getAndSaveMultisigBalances()
+ if (isLightClient) {
+ getAndSaveMultisigSyncProgress()
+ }
return () => {
dataUpdateSubscription.unsubscribe()
}
}, [walletId, getAndSaveMultisigBalances])
- return multisigBanlances
+ return { multisigBanlances, multisigSyncProgress }
}
diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx
index 68e95209ea..65eced4cc8 100644
--- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx
+++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx
@@ -20,6 +20,7 @@ import { EditTextField } from 'widgets/TextField'
import { MultisigConfig } from 'services/remote'
import PasswordRequest from 'components/PasswordRequest'
import ApproveMultisigTx from 'components/ApproveMultisigTx'
+import { LIGHT_NETWORK_TYPE } from 'utils/const'
import { useSearch, useConfigManage, useExportConfig, useActions, useSubscription } from './hooks'
import styles from './multisigAddress.module.scss'
@@ -52,6 +53,10 @@ const MultisigAddress = () => {
// eslint-disable-next-line
}, [i18n.language])
const isMainnet = isMainnetUtil(networks, networkID)
+ const isLightClient = useMemo(() => networks.find(n => n.id === networkID)?.type === LIGHT_NETWORK_TYPE, [
+ networks,
+ networkID,
+ ])
const { openDialog, closeDialog, dialogRef, isDialogOpen } = useDialogWrapper()
const {
allConfigs,
@@ -65,7 +70,12 @@ const MultisigAddress = () => {
walletId,
isMainnet,
})
- const multisigBanlances = useSubscription({ walletId, isMainnet, configs: allConfigs })
+ const { multisigBanlances, multisigSyncProgress } = useSubscription({
+ walletId,
+ isMainnet,
+ configs: allConfigs,
+ isLightClient,
+ })
const { deleteAction, infoAction, sendAction, approveAction } = useActions({ deleteConfigById })
const onClickItem = useCallback(
(multisigConfig: MultisigConfig) => (option: { key: string }) => {
@@ -138,8 +148,10 @@ const MultisigAddress = () => {
|
|
- {['address', 'alias', 'type', 'balance'].map(field => (
- {t(`multisig-address.table.${field}`)} |
+ {['address', 'alias', 'type', ...(isLightClient ? ['sync-block'] : []), 'balance'].map(field => (
+
+ {t(`multisig-address.table.${field}`)}
+ |
))}
@@ -173,6 +185,7 @@ const MultisigAddress = () => {
of
{v.n}
+ {isLightClient ? {multisigSyncProgress?.[v.fullPayload] ?? 0} | : null}
{shannonToCKBFormatter(multisigBanlances[v.fullPayload])}
CKB
diff --git a/packages/neuron-ui/src/components/MultisigAddress/multisigAddress.module.scss b/packages/neuron-ui/src/components/MultisigAddress/multisigAddress.module.scss
index adc8839afe..f3a0a2baa9 100644
--- a/packages/neuron-ui/src/components/MultisigAddress/multisigAddress.module.scss
+++ b/packages/neuron-ui/src/components/MultisigAddress/multisigAddress.module.scss
@@ -55,6 +55,10 @@
padding: 8px;
min-width: 40px;
text-align: left;
+
+ &[data-field="sync-block"] {
+ min-width: 70px;
+ }
}
.checkBoxTh {
diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json
index 01aa82a72d..d00e8fa1e0 100644
--- a/packages/neuron-ui/src/locales/en.json
+++ b/packages/neuron-ui/src/locales/en.json
@@ -926,6 +926,7 @@
"type": "type",
"balance": "balance",
"copy-address": "Copy Address",
+ "sync-block": "Synced Block",
"actions": {
"info": "Info",
"send": "Send",
diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json
index d45a4fe26f..fe15fce5ec 100644
--- a/packages/neuron-ui/src/locales/zh-tw.json
+++ b/packages/neuron-ui/src/locales/zh-tw.json
@@ -917,6 +917,7 @@
"type": "類型",
"balance": "余额",
"copy-address": "復製地址",
+ "sync-block": "同步高度",
"actions": {
"info": "詳情",
"send": "轉賬",
diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json
index d03cd64c82..defab499a4 100644
--- a/packages/neuron-ui/src/locales/zh.json
+++ b/packages/neuron-ui/src/locales/zh.json
@@ -918,6 +918,7 @@
"type": "类型",
"balance": "余额",
"copy-address": "复制地址",
+ "sync-block": "同步高度",
"actions": {
"info": "详情",
"send": "转账",
diff --git a/packages/neuron-ui/src/services/remote/multisig.ts b/packages/neuron-ui/src/services/remote/multisig.ts
index 89ac83f02e..5c1839325d 100644
--- a/packages/neuron-ui/src/services/remote/multisig.ts
+++ b/packages/neuron-ui/src/services/remote/multisig.ts
@@ -51,3 +51,6 @@ export const generateMultisigSendAllTx = remoteApi<{
multisigConfig: MultisigConfig
}>('generate-multisig-send-all-tx')
export const loadMultisigTxJson = remoteApi('load-multisig-tx-json')
+export const getMultisigSyncProgress = remoteApi(
+ 'get-sync-progress-by-addresses'
+)
diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts
index 97d54dd7b6..4a942f89be 100644
--- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts
+++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts
@@ -147,6 +147,7 @@ type Action =
| 'load-multisig-tx-json'
| 'get-hold-sudt-cell-capacity'
| 'start-migrate'
+ | 'get-sync-progress-by-addresses'
export const remoteApi = (action: Action) => async (params: P): Promise> => {
const res: SuccessFromController | FailureFromController = await ipcRenderer.invoke(action, params).catch(() => ({
diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts
index f3cd426448..d0ebddc131 100644
--- a/packages/neuron-ui/src/types/App/index.d.ts
+++ b/packages/neuron-ui/src/types/App/index.d.ts
@@ -149,7 +149,7 @@ declare namespace State {
name: string
remote: string
chain: 'ckb' | 'ckb_testnet' | 'ckb_dev' | string
- type: 0 | 1
+ type: 0 | 1 | 2
}
interface Network extends NetworkProperty {
diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts
index 66fdd832f2..99a29d2846 100644
--- a/packages/neuron-ui/src/utils/const.ts
+++ b/packages/neuron-ui/src/utils/const.ts
@@ -65,3 +65,4 @@ export const DEPRECATED_CODE_HASH: Record = {
}
export const LIGHT_CLIENT_TESTNET = 'light_client_testnet'
+export const LIGHT_NETWORK_TYPE = 2
diff --git a/packages/neuron-wallet/src/block-sync-renderer/index.ts b/packages/neuron-wallet/src/block-sync-renderer/index.ts
index 59fa02fc75..47076b1a6a 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/index.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/index.ts
@@ -15,6 +15,10 @@ import logger from 'utils/logger'
import CommonUtils from 'utils/common'
import queueWrapper from 'utils/queue'
import env from 'env'
+import MultisigConfigDbChangedSubject from 'models/subjects/multisig-config-db-changed-subject'
+import Multisig from 'services/multisig'
+import { SyncAddressType } from 'database/chain/entities/sync-progress'
+import { debounceTime } from 'rxjs/operators'
let network: Network | null
let child: ChildProcess | null = null
@@ -193,3 +197,17 @@ export const registerRequest = (c: ChildProcess, msg: Required) =
AddressCreatedSubject.getSubject().subscribe(() => resetSyncTaskQueue.asyncPush(true))
WalletDeletedSubject.getSubject().subscribe(() => resetSyncTaskQueue.asyncPush(true))
+MultisigConfigDbChangedSubject.getSubject()
+ .pipe(debounceTime(1000))
+ .subscribe(async () => {
+ if (!child) {
+ return
+ }
+ const appendScripts = await Multisig.getMultisigConfigForLight()
+ const msg: Required> = { type: 'call', channel: 'append_scripts', id: requestId++, message: appendScripts }
+ return registerRequest(child, msg).catch(err => {
+ logger.error(`Sync:\ffailed to append script to light client`, err)
+ })
+ })
diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/connector.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/connector.ts
index ae45229b73..06e6f5ceb9 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/sync/connector.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/sync/connector.ts
@@ -1,3 +1,4 @@
+import { SyncAddressType } from 'database/chain/entities/sync-progress'
import { Subject } from 'rxjs'
export interface BlockTips {
@@ -33,6 +34,13 @@ export interface LumosCell {
data?: string
}
+export interface AppendScript {
+ walletId: string
+ script: CKBComponents.Script
+ addressType: SyncAddressType
+ scriptType: CKBRPC.ScriptType
+}
+
export abstract class Connector {
abstract blockTipsSubject: Subject
abstract transactionsSubject: Subject<{ txHashes: CKBComponents.Hash[]; params: TransactionsSubjectParam }>
@@ -41,4 +49,7 @@ export abstract class Connector {
abstract notifyCurrentBlockNumberProcessed(param: TransactionsSubjectParam): void
abstract stop(): void
abstract getLiveCellsByScript(query: LumosCellQuery): Promise
+ async appendScript(_scripts: AppendScript[]) {
+ // do nothing
+ }
}
diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
index 798664f17c..61affcacf7 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
@@ -7,10 +7,13 @@ import { Address } from 'models/address'
import AddressMeta from 'database/address/meta'
import { scheduler } from 'timers/promises'
import SyncProgressService from 'services/sync-progress'
-import { BlockTips, LumosCellQuery, Connector } from './connector'
+import { BlockTips, LumosCellQuery, Connector, AppendScript } from './connector'
import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils'
import { LightRPC, LightScriptFilter } from '../../utils/ckb-rpc'
import HexUtils from 'utils/hex'
+import Multisig from 'services/multisig'
+import { SyncAddressType } from 'database/chain/entities/sync-progress'
+import WalletService from 'services/wallets'
interface SyncQueueParam {
script: CKBComponents.Script
@@ -84,7 +87,7 @@ export default class LightConnector extends Connector {
}
private async subscribeSync() {
- const minSyncBlockNumber = await SyncProgressService.getMinBlockNumber()
+ const minSyncBlockNumber = await SyncProgressService.getCurrentWalletMinBlockNumber()
const header = await this.lightRpc.getTipHeader()
this.blockTipsSubject.next({
cacheTipNumber: minSyncBlockNumber,
@@ -92,8 +95,8 @@ export default class LightConnector extends Connector {
})
}
- private async initSyncProgress() {
- if (!this.addressMetas.length) {
+ private async initSyncProgress(appendScripts?: AppendScript[]) {
+ if (!this.addressMetas.length && !appendScripts?.length) {
return
}
const sycnScripts = await this.lightRpc.getScripts()
@@ -101,7 +104,9 @@ export default class LightConnector extends Connector {
sycnScripts.forEach(v => {
existSyncscripts[scriptToHash(v.script)] = v
})
+ const currentWalletId = WalletService.getInstance().getCurrent()?.id
const allScripts = this.addressMetas
+ .filter(v => (currentWalletId ? v.walletId === currentWalletId : true))
.map(addressMeta => {
const lockScripts = [
addressMeta.generateDefaultLockScript(),
@@ -115,18 +120,33 @@ export default class LightConnector extends Connector {
}))
})
.flat()
+ .concat(appendScripts ?? [])
+ const walletMinBlockNumber = await SyncProgressService.getWalletMinBlockNumber()
+ const wallets = await WalletService.getInstance().getAll()
+ const walletStartBlockMap = wallets.reduce>(
+ (pre, cur) => ({ ...pre, [cur.id]: cur.startBlockNumberInLight }),
+ {}
+ )
const setScriptsParams = allScripts.map(v => ({
...v,
- blockNumber: existSyncscripts[scriptToHash(v.script)]?.blockNumber || '0x0'
+ blockNumber:
+ existSyncscripts[scriptToHash(v.script)]?.blockNumber ??
+ walletStartBlockMap[v.walletId] ??
+ `0x${(walletMinBlockNumber?.[v.walletId] ?? 0).toString(16)}`
}))
await this.lightRpc.setScripts(setScriptsParams)
await SyncProgressService.resetSyncProgress(allScripts)
const walletIds = [...new Set(this.addressMetas.map(v => v.walletId))]
await SyncProgressService.removeWalletsByExists(walletIds)
+ await SyncProgressService.removeByHashesAndAddressType(
+ SyncAddressType.Multisig,
+ appendScripts?.map(v => scriptToHash(v.script))
+ )
}
private async initSync() {
- await this.initSyncProgress()
+ const appendScripts = await Multisig.getMultisigConfigForLight()
+ await this.initSyncProgress(appendScripts)
while (this.pollingIndexer) {
await this.synchronize()
await scheduler.wait(5000)
@@ -224,4 +244,11 @@ export default class LightConnector extends Connector {
}
await this.subscribeSync()
}
+
+ async appendScript(scripts: AppendScript[]) {
+ if (!scripts.length) {
+ return
+ }
+ this.initSyncProgress(scripts)
+ }
}
diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts
index e02ce29c21..8d39e238ae 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts
@@ -16,7 +16,7 @@ import IndexerCacheService from './indexer-cache-service'
import logger from 'utils/logger'
import CommonUtils from 'utils/common'
import { ShouldInChildProcess } from 'exceptions'
-import { BlockTips, Connector } from './connector'
+import { AppendScript, BlockTips, Connector } from './connector'
import LightConnector from './light-connector'
import { generateRPC } from 'utils/ckb-rpc'
import { BUNDLED_LIGHT_CKB_URL } from 'utils/const'
@@ -106,6 +106,10 @@ export default class Queue {
}
}
+ async appendLightScript(scripts: AppendScript[]) {
+ await this.#indexerConnector?.appendScript(scripts)
+ }
+
private async fetchTxsWithStatus(txHashes: string[]) {
const rpc = generateRPC(this.#url)
const txsWithStatus = await rpc.createBatchRequest<'getTransaction', string[], CKBComponents.TransactionWithStatus[]>(
diff --git a/packages/neuron-wallet/src/block-sync-renderer/task.ts b/packages/neuron-wallet/src/block-sync-renderer/task.ts
index b56403dfa7..309b62e39f 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/task.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/task.ts
@@ -12,7 +12,7 @@ let syncQueue: SyncQueue | null
export interface WorkerMessage {
type: 'call' | 'response' | 'kill',
id?: number,
- channel: 'start' | 'queryIndexer' | 'unmount' | 'cache-tip-block-updated' | 'tx-db-changed' | 'wallet-deleted' | 'address-created' | 'indexer-error' | 'check-and-save-wallet-address'
+ channel: 'start' | 'queryIndexer' | 'unmount' | 'cache-tip-block-updated' | 'tx-db-changed' | 'wallet-deleted' | 'address-created' | 'indexer-error' | 'check-and-save-wallet-address' | 'append_scripts'
message: T
}
@@ -74,6 +74,12 @@ export const listener = async ({ type, id, channel, message }: WorkerMessage) =>
res = message ? await syncQueue?.getIndexerConnector()?.getLiveCellsByScript(message) : []
break
}
+ case 'append_scripts': {
+ if (Array.isArray(message)) {
+ await syncQueue?.appendLightScript(message)
+ }
+ break
+ }
default: {
// ignore
}
diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts
index 74ec3e1d60..acb8795211 100644
--- a/packages/neuron-wallet/src/controllers/api.ts
+++ b/packages/neuron-wallet/src/controllers/api.ts
@@ -58,6 +58,7 @@ import MultisigConfigModel from 'models/multisig-config'
import startMonitor, { stopMonitor } from 'services/monitor'
import { migrateCkbData } from 'services/ckb-runner'
import NodeService from 'services/node'
+import SyncProgressService from 'services/sync-progress'
export type Command = 'export-xpubkey' | 'import-xpubkey' | 'delete-wallet' | 'backup-wallet' | 'migrate-acp'
// Handle channel messages from renderer process and user actions.
@@ -693,6 +694,14 @@ export default class ApiController {
status: ResponseCode.Success,
}
})
+
+ //light client
+ handle('get-sync-progress-by-addresses', async (_, hashes: string[]) => {
+ return {
+ result: (await SyncProgressService.getSyncProgressByHashes(hashes)),
+ status: ResponseCode.Success,
+ }
+ })
}
// Register handler, warp and serialize API response
diff --git a/packages/neuron-wallet/src/controllers/app/index.ts b/packages/neuron-wallet/src/controllers/app/index.ts
index f00c8e0b7b..b2f938c296 100644
--- a/packages/neuron-wallet/src/controllers/app/index.ts
+++ b/packages/neuron-wallet/src/controllers/app/index.ts
@@ -14,6 +14,7 @@ import { migrate as mecuryMigrate } from 'controllers/mercury'
import SyncApiController from 'controllers/sync-api'
import { SETTINGS_WINDOW_TITLE } from 'utils/const'
import { stopCkbNode } from 'services/ckb-runner'
+import { CKBLightRunner } from 'services/light-runner'
const app = electronApp
@@ -59,6 +60,7 @@ export default class AppController {
return
}
await stopCkbNode()
+ await CKBLightRunner.getInstance().stop()
}
public registerChannels(win: BrowserWindow, channels: string[]) {
diff --git a/packages/neuron-wallet/src/controllers/app/menu.ts b/packages/neuron-wallet/src/controllers/app/menu.ts
index 23fcad9d1d..6c83e50f5f 100644
--- a/packages/neuron-wallet/src/controllers/app/menu.ts
+++ b/packages/neuron-wallet/src/controllers/app/menu.ts
@@ -16,6 +16,8 @@ import { SETTINGS_WINDOW_TITLE, SETTINGS_WINDOW_WIDTH } from 'utils/const'
import { OfflineSignJSON } from 'models/offline-sign'
import NetworksService from 'services/networks'
import { clearCkbNodeCache } from 'services/ckb-runner'
+import { CKBLightRunner } from 'services/light-runner'
+import { NetworkType } from 'models/network'
enum URL {
Settings = '/settings/general',
@@ -116,7 +118,7 @@ const updateApplicationMenu = (mainWindow: BrowserWindow | null) => {
let isMainWindow = mainWindow === currentWindow
const walletsService = WalletsService.getInstance()
- const isMainnet = new NetworksService().getCurrent().chain === 'ckb'
+ const network = new NetworksService().getCurrent()
const wallets = walletsService.getAll().map(({ id, name }) => ({ id, name }))
const currentWallet = walletsService.getCurrent()
const hasCurrentWallet = currentWallet !== undefined
@@ -316,9 +318,9 @@ const updateApplicationMenu = (mainWindow: BrowserWindow | null) => {
`#/multisig-address/${currentWallet!.id}`,
t(`messageBox.multisig-address.title`),
{
- width: 900,
- maxWidth: 900,
- minWidth: 900,
+ width: 1000,
+ maxWidth: 1000,
+ minWidth: 1000,
resizable: true
},
['multisig-output-update']
@@ -327,7 +329,7 @@ const updateApplicationMenu = (mainWindow: BrowserWindow | null) => {
},
{
label: t('application-menu.tools.clear-sync-data'),
- enabled: hasCurrentWallet && isMainnet,
+ enabled: hasCurrentWallet && (network.chain === 'ckb' || NetworkType.Light === network.type),
click: async () => {
const res = await dialog.showMessageBox({
type: 'warning',
@@ -338,7 +340,12 @@ const updateApplicationMenu = (mainWindow: BrowserWindow | null) => {
cancelId: 1
})
if (res.response === 0) {
- await clearCkbNodeCache()
+ const network = new NetworksService().getCurrent()
+ if (network.type === NetworkType.Light) {
+ await CKBLightRunner.getInstance().clearNodeCache()
+ } else {
+ await clearCkbNodeCache()
+ }
}
}
},
diff --git a/packages/neuron-wallet/src/controllers/export-debug.ts b/packages/neuron-wallet/src/controllers/export-debug.ts
index 2212595019..a1722eedd7 100644
--- a/packages/neuron-wallet/src/controllers/export-debug.ts
+++ b/packages/neuron-wallet/src/controllers/export-debug.ts
@@ -11,6 +11,7 @@ import AddressService from 'services/addresses'
import redistCheck from 'utils/redist-check'
import SettingsService from 'services/settings'
import { generateRPC } from 'utils/ckb-rpc'
+import { CKBLightRunner } from 'services/light-runner'
export default class ExportDebugController {
// eslint-disable-next-line prettier/prettier
@@ -37,7 +38,13 @@ export default class ExportDebugController {
return
}
this.archive.pipe(fs.createWriteStream(filePath))
- await Promise.all([this.addStatusFile(), this.addBundledCKBLog(), this.addLogFiles(), this.addHdPublicKeyInfoCsv()])
+ await Promise.all([
+ this.addStatusFile(),
+ this.addBundledCKBLog(),
+ this.addLogFiles(),
+ this.addHdPublicKeyInfoCsv(),
+ this.addBundledCKBLightClientLog()
+ ])
await this.archive.finalize()
dialog.showMessageBox({
type: 'info',
@@ -147,4 +154,10 @@ export default class ExportDebugController {
this.archive.file(path.join(logFile.path, '..', file), { name: file })
})
}
+
+ private addBundledCKBLightClientLog() {
+ const logPath = CKBLightRunner.getInstance().logPath
+ if (!fs.existsSync(logPath)) {return}
+ this.archive.file(logPath, { name: 'bundled-ckb-lignt-client.log' })
+ }
}
diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts
index aa31beafc2..c2cbe6b251 100644
--- a/packages/neuron-wallet/src/controllers/wallets.ts
+++ b/packages/neuron-wallet/src/controllers/wallets.ts
@@ -31,6 +31,10 @@ import HardwareWalletService from 'services/hardware'
import { DeviceInfo, ExtendedPublicKey } from 'services/hardware/common'
import AddressParser from 'models/address-parser'
import MultisigConfigModel from 'models/multisig-config'
+import NodeService from 'services/node'
+import { generateRPC } from 'utils/ckb-rpc'
+import { resetSyncTaskQueue } from 'block-sync-renderer'
+import { NetworkType } from 'models/network'
export default class WalletsController {
public async getAll(): Promise[]>> {
@@ -116,11 +120,13 @@ export default class WalletsController {
)
const walletsService = WalletsService.getInstance()
+ const rpc = generateRPC(NodeService.getInstance().nodeUrl)
const wallet = walletsService.create({
id: '',
name,
extendedKey: accountExtendedPublicKey.serialize(),
- keystore
+ keystore,
+ startBlockNumberInLight: isImporting ? undefined : await rpc.getTipBlockNumber()
})
wallet.checkAndGenerateAddresses(isImporting)
@@ -362,6 +368,10 @@ export default class WalletsController {
if (!currentWallet || id !== currentWallet.id) {
throw new CurrentWalletNotSet()
}
+ const network = NetworksService.getInstance().getCurrent()
+ if (network.type === NetworkType.Light) {
+ resetSyncTaskQueue.asyncPush(true)
+ }
return {
status: ResponseCode.Success,
result: currentWallet.toJSON()
diff --git a/packages/neuron-wallet/src/database/chain/entities/multisig-config.ts b/packages/neuron-wallet/src/database/chain/entities/multisig-config.ts
index aa90c8ab30..8b261c85c9 100644
--- a/packages/neuron-wallet/src/database/chain/entities/multisig-config.ts
+++ b/packages/neuron-wallet/src/database/chain/entities/multisig-config.ts
@@ -53,7 +53,7 @@ export default class MultisigConfig {
this.changed('AfterRemove')
}
- private changed = (event: string) => {
+ private changed = (event: 'AfterInsert' | 'AfterRemove') => {
MultisigConfigDbChangedSubject.getSubject().next(event)
}
}
diff --git a/packages/neuron-wallet/src/database/chain/entities/sync-progress.ts b/packages/neuron-wallet/src/database/chain/entities/sync-progress.ts
index 8ac3378e2a..1d6d2d27dc 100644
--- a/packages/neuron-wallet/src/database/chain/entities/sync-progress.ts
+++ b/packages/neuron-wallet/src/database/chain/entities/sync-progress.ts
@@ -2,6 +2,11 @@ import { HexString } from '@ckb-lumos/base'
import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils'
import { Entity, PrimaryColumn, Column } from 'typeorm'
+export enum SyncAddressType {
+ Default,
+ Multisig
+}
+
@Entity({ name: 'sync_progress' })
export default class SyncProgress {
@PrimaryColumn({ type: 'varchar' })
@@ -34,7 +39,15 @@ export default class SyncProgress {
@Column({ type: 'boolean' })
delete: boolean = false
- static fromObject(obj: { script: CKBComponents.Script; scriptType: CKBRPC.ScriptType; walletId: string }) {
+ @Column()
+ addressType: SyncAddressType = SyncAddressType.Default
+
+ static fromObject(obj: {
+ script: CKBComponents.Script
+ scriptType: CKBRPC.ScriptType
+ walletId: string
+ addressType?: SyncAddressType
+ }) {
const res = new SyncProgress()
res.hash = scriptToHash(obj.script)
res.args = obj.script.args
@@ -43,6 +56,7 @@ export default class SyncProgress {
res.walletId = obj.walletId
res.scriptType = obj.scriptType
res.delete = false
+ res.addressType = obj.addressType ?? SyncAddressType.Default
return res
}
}
diff --git a/packages/neuron-wallet/src/database/chain/index.ts b/packages/neuron-wallet/src/database/chain/index.ts
index 9064392c87..5e29bfbffe 100644
--- a/packages/neuron-wallet/src/database/chain/index.ts
+++ b/packages/neuron-wallet/src/database/chain/index.ts
@@ -6,19 +6,18 @@ import TransactionEntity from './entities/transaction'
import SyncInfoEntity from './entities/sync-info'
import IndexerTxHashCache from './entities/indexer-tx-hash-cache'
import MultisigOutput from './entities/multisig-output'
-import SyncProgressService from 'services/sync-progress'
+import SyncProgress from './entities/sync-progress'
/*
* Clean local sqlite storage
*/
export const clean = async () => {
await Promise.all([
- ...[InputEntity, OutputEntity, TransactionEntity, IndexerTxHashCache, MultisigOutput].map(entity => {
+ ...[InputEntity, OutputEntity, TransactionEntity, IndexerTxHashCache, MultisigOutput, SyncProgress].map(entity => {
return getConnection()
.getRepository(entity)
.clear()
- }),
- SyncProgressService.clear()
+ })
])
MultisigOutputChangedSubject.getSubject().next('reset')
diff --git a/packages/neuron-wallet/src/database/chain/migrations/1681360188494-AddTypeSyncProgress.ts b/packages/neuron-wallet/src/database/chain/migrations/1681360188494-AddTypeSyncProgress.ts
new file mode 100644
index 0000000000..b1732607cd
--- /dev/null
+++ b/packages/neuron-wallet/src/database/chain/migrations/1681360188494-AddTypeSyncProgress.ts
@@ -0,0 +1,31 @@
+import { MigrationInterface, QueryRunner, TableColumn, TableIndex } from "typeorm"
+import Multisig from "models/multisig"
+import { scriptToHash } from "@nervosnetwork/ckb-sdk-utils"
+import { SyncAddressType } from "../entities/sync-progress"
+import MultisigConfig from "../entities/multisig-config"
+
+export class AddTypeSyncProgress1681360188494 implements MigrationInterface {
+ name = 'AddTypeSyncProgress1681360188494'
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.addColumn('sync_progress', new TableColumn({
+ name: 'addressType',
+ type: 'INTEGER',
+ isNullable: false,
+ default: SyncAddressType.Default,
+ }))
+ await queryRunner.createIndex("sync_progress", new TableIndex({ columnNames: ["addressType"] }))
+ const multisigConfigs = await queryRunner.connection
+ .getRepository(MultisigConfig)
+ .createQueryBuilder()
+ .getMany()
+ const scriptHashes = multisigConfigs.map(v => scriptToHash(Multisig.getMultisigScript(v.blake160s, v.r, v.m, v.n)))
+ await queryRunner.query(`UPDATE sync_progress set addressType=1 where hash in (${scriptHashes.map(v => `'${v}'`).join(',')})`)
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.dropColumn('sync_progress', 'addressType')
+ await queryRunner.dropIndex("sync_progress", new TableIndex({ columnNames: ["addressType"] }))
+ }
+
+}
diff --git a/packages/neuron-wallet/src/database/chain/ormconfig.ts b/packages/neuron-wallet/src/database/chain/ormconfig.ts
index 53ce7606e8..87f4cc42d2 100644
--- a/packages/neuron-wallet/src/database/chain/ormconfig.ts
+++ b/packages/neuron-wallet/src/database/chain/ormconfig.ts
@@ -17,6 +17,7 @@ import TxDescription from './entities/tx-description'
import AddressDescription from './entities/address-description'
import MultisigConfig from './entities/multisig-config'
import MultisigOuput from './entities/multisig-output'
+import SyncProgress from './entities/sync-progress'
import { InitMigration1566959757554 } from './migrations/1566959757554-InitMigration'
import { AddTypeAndHasData1567144517514 } from './migrations/1567144517514-AddTypeAndHasData'
@@ -49,7 +50,7 @@ import { RemoveDuplicateBlake160s1656930265386 } from './migrations/165693026538
import { UpdateOutputChequeLockHash1652945662504 } from './migrations/1652945662504-UpdateOutputChequeLockHash'
import { RemoveAddressesMultisigConfig1651820157100 } from './migrations/1651820157100-RemoveAddressesMultisigConfig'
import { AddSyncProgress1676441837373 } from './migrations/1676441837373-AddSyncProgress'
-import SyncProgress from './entities/sync-progress'
+import { AddTypeSyncProgress1681360188494 } from './migrations/1681360188494-AddTypeSyncProgress'
export const CONNECTION_NOT_FOUND_NAME = 'ConnectionNotFoundError'
@@ -115,7 +116,8 @@ const connectOptions = async (genesisBlockHash: string): Promise {
MultisigConfigDbChangedSubject.getSubject()
.pipe(debounceTime(500))
- .subscribe(async (event: string) => {
+ .subscribe(async event => {
try {
if (event === 'AfterInsert') {
await MultisigService.saveLiveMultisigOutput()
diff --git a/packages/neuron-wallet/src/models/subjects/multisig-config-db-changed-subject.ts b/packages/neuron-wallet/src/models/subjects/multisig-config-db-changed-subject.ts
index 70dee07b78..fc8358a5e8 100644
--- a/packages/neuron-wallet/src/models/subjects/multisig-config-db-changed-subject.ts
+++ b/packages/neuron-wallet/src/models/subjects/multisig-config-db-changed-subject.ts
@@ -2,7 +2,7 @@ import { ReplaySubject } from 'rxjs'
// subscribe this Subject to monitor any transaction table changes
export class MultisigConfigDbChangedSubject {
- private static subject = new ReplaySubject(100)
+ private static subject = new ReplaySubject<'AfterInsert' | 'AfterRemove'>(100)
public static getSubject() {
return MultisigConfigDbChangedSubject.subject
diff --git a/packages/neuron-wallet/src/services/ckb-runner.ts b/packages/neuron-wallet/src/services/ckb-runner.ts
index 20c24d05df..8b2c8fbc51 100644
--- a/packages/neuron-wallet/src/services/ckb-runner.ts
+++ b/packages/neuron-wallet/src/services/ckb-runner.ts
@@ -92,7 +92,6 @@ export const startCkbNode = async () => {
ckb.stderr.on('data', data => {
const dataString: string = data.toString()
logger.error('CKB:\trun fail:', dataString)
- ckb = null
if (dataString.includes('CKB wants to migrate the data into new format')) {
MigrateSubject.next({ type: 'need-migrate' })
}
diff --git a/packages/neuron-wallet/src/services/light-runner.ts b/packages/neuron-wallet/src/services/light-runner.ts
index bcdca0def1..f15379b531 100644
--- a/packages/neuron-wallet/src/services/light-runner.ts
+++ b/packages/neuron-wallet/src/services/light-runner.ts
@@ -3,8 +3,8 @@ import path from 'path'
import fs from 'fs'
import { ChildProcess, spawn } from 'child_process'
import logger from 'utils/logger'
-import { resetSyncTaskQueue } from 'block-sync-renderer'
import SettingsService from 'services/settings'
+import { clean } from 'database/chain'
const { app } = env
@@ -50,7 +50,6 @@ abstract class NodeRunner {
async stop() {
return new Promise(resolve => {
- resetSyncTaskQueue.push(false)
if (this.runnerProcess) {
logger.info('Runner:\tkilling node')
this.runnerProcess.once('close', () => resolve())
@@ -66,6 +65,7 @@ abstract class NodeRunner {
export class CKBLightRunner extends NodeRunner {
protected networkType: NetworkType = NetworkType.Light
protected binaryName: string = 'ckb-light-client'
+ protected logStream?: fs.WriteStream
static getInstance(): CKBLightRunner {
if (!CKBLightRunner.instance) {
@@ -126,16 +126,27 @@ export class CKBLightRunner extends NodeRunner {
this.initConfig()
const options = ['run', '--config-file', this.configFile]
- const runnerProcess = spawn(this.binary, options, { stdio: ['ignore', 'pipe', 'pipe'] })
+ const runnerProcess = spawn(this.binary, options, {
+ stdio: ['ignore', 'pipe', 'pipe'],
+ env: { RUST_LOG: 'info', ckb_light_client: 'info' }
+ })
this.runnerProcess = runnerProcess
+ if (!this.logStream) {
+ this.logStream = fs.createWriteStream(this.logPath)
+ }
+
runnerProcess.stderr &&
runnerProcess.stderr.on('data', data => {
const dataString: string = data.toString()
logger.error('CKB Light Runner:\trun fail:', dataString)
- this.runnerProcess = undefined
+ this.logStream?.write(data)
})
+ runnerProcess.stdout &&
+ runnerProcess.stdout.on('data', data => {
+ this.logStream?.write(data)
+ })
runnerProcess.on('error', error => {
logger.error('CKB Light Runner:\trun fail:', error)
this.runnerProcess = undefined
@@ -145,6 +156,16 @@ export class CKBLightRunner extends NodeRunner {
logger.info('CKB Light Runner:\tprocess closed')
this.runnerProcess = undefined
})
- resetSyncTaskQueue.push(true)
+ }
+
+ get logPath() {
+ return path.join(logger.transports.file.getFile().path, '..', 'light_client_run.log')
+ }
+
+ async clearNodeCache(): Promise {
+ await this.stop()
+ fs.rmSync(SettingsService.getInstance().testnetLightDataPath, { recursive: true, force: true })
+ await clean()
+ await this.start()
}
}
diff --git a/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts b/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts
index eb3fccee85..97c931e6bc 100644
--- a/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts
+++ b/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts
@@ -1,4 +1,5 @@
import { stopCkbNode } from 'services/ckb-runner'
+import { CKBLightRunner } from 'services/light-runner'
import NodeService from '../node'
import BaseMonitor from './base'
@@ -13,6 +14,7 @@ export default class CkbMonitor extends BaseMonitor {
async stop(): Promise {
await stopCkbNode()
+ await CKBLightRunner.getInstance().stop()
}
name: string = 'ckb'
diff --git a/packages/neuron-wallet/src/services/multisig.ts b/packages/neuron-wallet/src/services/multisig.ts
index b2a2d73de8..89012ff0ea 100644
--- a/packages/neuron-wallet/src/services/multisig.ts
+++ b/packages/neuron-wallet/src/services/multisig.ts
@@ -9,6 +9,8 @@ import Transaction from 'models/chain/transaction'
import { OutputStatus } from 'models/chain/output'
import NetworksService from './networks'
import Multisig from 'models/multisig'
+import SyncProgress, { SyncAddressType } from 'database/chain/entities/sync-progress'
+import { NetworkType } from 'models/network'
const max64Int = '0x' + 'f'.repeat(16)
export default class MultisigService {
@@ -248,14 +250,32 @@ export default class MultisigService {
MultisigOutputChangedSubject.getSubject().next('delete')
}
- static async syncMultisigOutput(lastestBlockNumber: string) {
- try {
- const multisigConfigs = await getConnection()
- .getRepository(MultisigConfig)
+ static async saveMultisigSyncBlockNumber(multisigConfigs: MultisigConfig[], lastestBlockNumber: string) {
+ const network = await NetworksService.getInstance().getCurrent()
+ if (network.type === NetworkType.Light) {
+ const multisigScriptHashList = multisigConfigs.map(v =>
+ scriptToHash(Multisig.getMultisigScript(v.blake160s, v.r, v.m, v.n))
+ )
+ const syncBlockNumbers = await getConnection()
+ .getRepository(SyncProgress)
.createQueryBuilder()
+ .where({ hash: In(multisigScriptHashList) })
.getMany()
- await MultisigService.saveLiveMultisigOutput()
- await MultisigService.deleteDeadMultisigOutput(multisigConfigs)
+ const syncBlockNumbersMap: Record = syncBlockNumbers.reduce(
+ (pre, cur) => ({ ...pre, [cur.hash]: cur.blockStartNumber }),
+ {}
+ )
+ await getConnection()
+ .getRepository(MultisigConfig)
+ .save(
+ multisigConfigs.map(v => {
+ const blockNumber =
+ syncBlockNumbersMap[scriptToHash(Multisig.getMultisigScript(v.blake160s, v.r, v.m, v.n))]
+ v.lastestBlockNumber = `0x${BigInt(blockNumber).toString(16)}`
+ return v
+ })
+ )
+ } else {
await getConnection()
.getRepository(MultisigConfig)
.save(
@@ -264,6 +284,19 @@ export default class MultisigService {
lastestBlockNumber
}))
)
+ }
+ }
+
+ static async syncMultisigOutput(lastestBlockNumber: string) {
+ try {
+ const multisigConfigs = await getConnection()
+ .getRepository(MultisigConfig)
+ .createQueryBuilder()
+ .getMany()
+ await MultisigService.saveLiveMultisigOutput()
+ await MultisigService.deleteDeadMultisigOutput(multisigConfigs)
+ await MultisigService.saveMultisigSyncBlockNumber(multisigConfigs, lastestBlockNumber)
+ MultisigOutputChangedSubject.getSubject().next('update')
} catch (error) {
// ignore error, if lastestBlockNumber not update, it will try next time
}
@@ -299,4 +332,17 @@ export default class MultisigService {
.execute()
MultisigOutputChangedSubject.getSubject().next('update')
}
+
+ static async getMultisigConfigForLight() {
+ const multisigConfigs = await getConnection()
+ .getRepository(MultisigConfig)
+ .createQueryBuilder()
+ .getMany()
+ return multisigConfigs.map(v => ({
+ walletId: v.walletId,
+ script: Multisig.getMultisigScript(v.blake160s, v.r, v.m, v.n),
+ addressType: SyncAddressType.Multisig,
+ scriptType: 'lock' as CKBRPC.ScriptType
+ }))
+ }
}
diff --git a/packages/neuron-wallet/src/services/sync-progress.ts b/packages/neuron-wallet/src/services/sync-progress.ts
index 95573f28f5..31d839de37 100644
--- a/packages/neuron-wallet/src/services/sync-progress.ts
+++ b/packages/neuron-wallet/src/services/sync-progress.ts
@@ -1,11 +1,17 @@
import { getConnection, In, Not } from 'typeorm'
-import SyncProgress from 'database/chain/entities/sync-progress'
+import SyncProgress, { SyncAddressType } from 'database/chain/entities/sync-progress'
import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils'
import { HexString } from '@ckb-lumos/base'
+import WalletService from './wallets'
export default class SyncProgressService {
static async resetSyncProgress(
- params: { script: CKBComponents.Script; scriptType: CKBRPC.ScriptType; walletId: string }[]
+ params: {
+ script: CKBComponents.Script
+ scriptType: CKBRPC.ScriptType
+ walletId: string
+ addressType?: SyncAddressType
+ }[]
) {
await getConnection()
.getRepository(SyncProgress)
@@ -25,6 +31,15 @@ export default class SyncProgressService {
.execute()
}
+ static async removeByHashesAndAddressType(addressType: SyncAddressType, existHashes?: string[]) {
+ await getConnection()
+ .createQueryBuilder()
+ .update(SyncProgress)
+ .set({ delete: true })
+ .where({ ...(existHashes?.length ? { hash: Not(In(existHashes)) } : {}), addressType })
+ .execute()
+ }
+
static async updateBlockNumber(blake160s: string[], blockNumber: number) {
await getConnection()
.createQueryBuilder()
@@ -64,20 +79,33 @@ export default class SyncProgressService {
await getConnection().manager.update(SyncProgress, { hash }, { blockStartNumber, blockEndNumber, cursor })
}
- static async getMinBlockNumber() {
+ static async getCurrentWalletMinBlockNumber() {
+ const currentWallet = WalletService.getInstance().getCurrent()
const item = await getConnection()
.getRepository(SyncProgress)
.createQueryBuilder()
+ .where({ delete: false, ...(currentWallet ? { walletId: currentWallet.id } : {}) })
.orderBy('blockEndNumber', 'ASC')
.getOne()
return item?.blockEndNumber || 0
}
- static async clear() {
- await getConnection()
+ static async getWalletMinBlockNumber() {
+ const items = await getConnection()
+ .getRepository(SyncProgress)
.createQueryBuilder()
- .update(SyncProgress)
- .set({ blockStartNumber: 0, blockEndNumber: 0 })
- .execute()
+ .select('MIN(blockStartNumber) as blockStartNumber, walletId')
+ .where({ addressType: SyncAddressType.Default })
+ .groupBy('walletId')
+ .getMany()
+ return items.reduce>((pre, cur) => ({ ...pre, [cur.walletId]: cur.blockStartNumber }), {})
+ }
+
+ static async getSyncProgressByHashes(hashes: string[]) {
+ return await getConnection()
+ .getRepository(SyncProgress)
+ .createQueryBuilder()
+ .where({ hash: In(hashes) })
+ .getMany()
}
}
diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts
index 1fa67d0362..00aa64e20c 100644
--- a/packages/neuron-wallet/src/services/wallets.ts
+++ b/packages/neuron-wallet/src/services/wallets.ts
@@ -24,6 +24,7 @@ export interface WalletProperties {
isHDWallet?: boolean
device?: DeviceInfo
keystore?: Keystore
+ startBlockNumberInLight?: string
}
export abstract class Wallet {
@@ -32,9 +33,10 @@ export abstract class Wallet {
public device?: DeviceInfo
protected extendedKey: string = ''
protected isHD: boolean
+ protected startBlockNumberInLight?: string
constructor(props: WalletProperties) {
- const { id, name, extendedKey, device, isHDWallet } = props
+ const { id, name, extendedKey, device, isHDWallet, startBlockNumberInLight } = props
if (id === undefined) {
throw new IsRequired('ID')
@@ -52,6 +54,7 @@ export abstract class Wallet {
this.extendedKey = extendedKey
this.device = device
this.isHD = isHDWallet ?? true
+ this.startBlockNumberInLight = startBlockNumberInLight
}
public toJSON = () => ({
@@ -138,7 +141,8 @@ export class FileKeystoreWallet extends Wallet {
name: this.name,
extendedKey: this.extendedKey,
device: this.device,
- isHD: this.isHD
+ isHD: this.isHD,
+ startBlockNumberInLight: this.startBlockNumberInLight
}
}
diff --git a/packages/neuron-wallet/src/utils/const.ts b/packages/neuron-wallet/src/utils/const.ts
index 1d6c292832..97401fb976 100644
--- a/packages/neuron-wallet/src/utils/const.ts
+++ b/packages/neuron-wallet/src/utils/const.ts
@@ -1,7 +1,7 @@
export const MIN_PASSWORD_LENGTH = 8
export const MAX_PASSWORD_LENGTH = 50
export const BUNDLED_CKB_URL = 'http://localhost:8114'
-export const BUNDLED_LIGHT_CKB_URL = 'http://localhost:9000'
+export const BUNDLED_LIGHT_CKB_URL = 'http://127.0.0.1:9000'
export const LIGHT_CLIENT_TESTNET = 'light_client_testnet'
export const SETTINGS_WINDOW_TITLE = process.platform === 'darwin' ? 'settings.title.mac' : 'settings.title.normal'
export const SETTINGS_WINDOW_WIDTH = 900
diff --git a/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts
index f693277c90..e4ab7fe851 100644
--- a/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts
+++ b/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts
@@ -5,11 +5,13 @@ import HexUtils from '../../src/utils/hex'
import AddressMeta from '../../src/database/address/meta'
const getSyncStatusMock = jest.fn()
-const getMinBlockNumberMock = jest.fn()
+const getCurrentWalletMinBlockNumberMock = jest.fn()
const getAllSyncStatusToMapMock = jest.fn()
const resetSyncProgressMock = jest.fn()
const updateSyncStatusMock = jest.fn()
const removeWalletsByExistsMock = jest.fn()
+const getWalletMinBlockNumberMock = jest.fn()
+const removeByHashesAndAddressType = jest.fn()
const setScriptsMock = jest.fn()
const getScriptsMock = jest.fn()
@@ -17,13 +19,17 @@ const getTipHeaderMock = jest.fn()
const getTransactionsMock = jest.fn()
const schedulerWaitMock = jest.fn()
+const getMultisigConfigForLightMock = jest.fn()
+const walletGetCurrentMock = jest.fn()
+const walletGetAllMock = jest.fn()
function mockReset() {
getSyncStatusMock.mockReset()
- getMinBlockNumberMock.mockReset()
+ getCurrentWalletMinBlockNumberMock.mockReset()
getAllSyncStatusToMapMock.mockReset()
resetSyncProgressMock.mockReset()
updateSyncStatusMock.mockReset()
+ getWalletMinBlockNumberMock.mockReset()
setScriptsMock.mockReset()
getScriptsMock.mockReset()
@@ -31,16 +37,22 @@ function mockReset() {
getTransactionsMock.mockReset()
schedulerWaitMock.mockReset()
+ getMultisigConfigForLightMock.mockReset()
+ removeByHashesAndAddressType.mockReset()
+ walletGetCurrentMock.mockReset()
+ walletGetAllMock.mockReset()
}
jest.mock('../../src/services/sync-progress', () => {
return class {
static getSyncStatus: any = () => getSyncStatusMock()
- static getMinBlockNumber: any = () => getMinBlockNumberMock()
+ static getCurrentWalletMinBlockNumber: any = () => getCurrentWalletMinBlockNumberMock()
static getAllSyncStatusToMap: any = () => getAllSyncStatusToMapMock()
static resetSyncProgress: any = (arg: any) => resetSyncProgressMock(arg)
static updateSyncStatus: any = (hash: string, update: any) => updateSyncStatusMock(hash, update)
static removeWalletsByExists: any = (walletIds: string[]) => removeWalletsByExistsMock(walletIds)
+ static getWalletMinBlockNumber: any = () => getWalletMinBlockNumberMock()
+ static removeByHashesAndAddressType: any = (type: number, scripts: CKBComponents.Script[]) => removeByHashesAndAddressType(type, scripts)
}
})
@@ -55,6 +67,19 @@ jest.mock('../../src/utils/ckb-rpc', () => ({
}
}))
+jest.mock('../../src/services/multisig', () => ({
+ getMultisigConfigForLight: () => getMultisigConfigForLightMock()
+}))
+
+jest.mock('../../src/services/wallets', () => ({
+ getInstance() {
+ return {
+ getCurrent: walletGetCurrentMock,
+ getAll: walletGetAllMock,
+ }
+ }
+}))
+
jest.mock('timers/promises', () => ({
scheduler: {
wait: (delay: number) => schedulerWaitMock(delay),
@@ -70,6 +95,9 @@ const scriptHash = scriptToHash(script)
const address = 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2q8ux5aqem92xnwfmj5cl6e233phlwlysqhjx5w'
describe('test light connector', () => {
+ beforeEach(() => {
+ walletGetAllMock.mockReturnValue([])
+ })
afterEach(() => {
mockReset()
})
@@ -207,7 +235,7 @@ describe('test light connector', () => {
describe('test subscribeSync', () => {
it('run success', async () => {
- getMinBlockNumberMock.mockResolvedValue(100)
+ getCurrentWalletMinBlockNumberMock.mockResolvedValue(100)
getTipHeaderMock.mockResolvedValue({ number: '0xaa' })
const connector = new LightConnector([], '')
// @ts-ignore: private-method
@@ -229,6 +257,16 @@ describe('test light connector', () => {
await connect.initSyncProgress()
expect(getScriptsMock).toBeCalledTimes(0)
})
+ it('append multisig script', async () => {
+ getScriptsMock.mockResolvedValue([])
+ const connect = new LightConnector([], '')
+ //@ts-ignore
+ await connect.initSyncProgress([{ walletId: 'walletId', script, addressType: 1, scriptType: 'lock' }])
+ expect(getScriptsMock).toBeCalledTimes(1)
+ expect(setScriptsMock).toBeCalledWith([
+ { script, scriptType: 'lock', walletId: 'walletId', blockNumber: '0x0', addressType: 1, },
+ ])
+ })
it('there is not exist sync scripts with light client', async () => {
getScriptsMock.mockResolvedValue([{ script, blockNumber: '0xaa' }])
const addressMeta = AddressMeta.fromObject({ walletId: 'walletId', address, path: '', addressIndex: 10, addressType: 0, blake160: script.args })
@@ -247,6 +285,33 @@ describe('test light connector', () => {
])
expect(removeWalletsByExistsMock).toBeCalledWith(['walletId'])
})
+ it('set new script with the synced min block number', async () => {
+ getScriptsMock.mockResolvedValue([])
+ const addressMeta = AddressMeta.fromObject({ walletId: 'walletId', address, path: '', addressIndex: 10, addressType: 0, blake160: script.args })
+ getWalletMinBlockNumberMock.mockResolvedValue({ 'walletId': 170})
+ const connect = new LightConnector([addressMeta], '')
+ //@ts-ignore
+ await connect.initSyncProgress()
+ expect(setScriptsMock).toBeCalledWith([
+ { script: addressMeta.generateDefaultLockScript().toSDK(), scriptType: 'lock', walletId: 'walletId', blockNumber: '0xaa' },
+ { script: addressMeta.generateACPLockScript().toSDK(), scriptType: 'lock', walletId: 'walletId', blockNumber: '0xaa' },
+ { script: addressMeta.generateLegacyACPLockScript().toSDK(), scriptType: 'lock', walletId: 'walletId', blockNumber: '0xaa' },
+ ])
+ })
+ it('set new script with start block number in wallet', async () => {
+ getScriptsMock.mockResolvedValue([])
+ const addressMeta = AddressMeta.fromObject({ walletId: 'walletId', address, path: '', addressIndex: 10, addressType: 0, blake160: script.args })
+ getWalletMinBlockNumberMock.mockResolvedValue({})
+ walletGetAllMock.mockReturnValue([{ id: 'walletId', startBlockNumberInLight: '0xaa' }])
+ const connect = new LightConnector([addressMeta], '')
+ //@ts-ignore
+ await connect.initSyncProgress()
+ expect(setScriptsMock).toBeCalledWith([
+ { script: addressMeta.generateDefaultLockScript().toSDK(), scriptType: 'lock', walletId: 'walletId', blockNumber: '0xaa' },
+ { script: addressMeta.generateACPLockScript().toSDK(), scriptType: 'lock', walletId: 'walletId', blockNumber: '0xaa' },
+ { script: addressMeta.generateLegacyACPLockScript().toSDK(), scriptType: 'lock', walletId: 'walletId', blockNumber: '0xaa' },
+ ])
+ })
})
describe('test initSync', () => {
diff --git a/packages/neuron-wallet/tests/controllers/export-debug.test.ts b/packages/neuron-wallet/tests/controllers/export-debug.test.ts
index 9ab2b6b6d6..585a347659 100644
--- a/packages/neuron-wallet/tests/controllers/export-debug.test.ts
+++ b/packages/neuron-wallet/tests/controllers/export-debug.test.ts
@@ -73,6 +73,18 @@ jest.mock('../../src/services/settings', () => {
}
})
+jest.mock('../../src/services/light-runner', () => {
+ return {
+ CKBLightRunner: {
+ getInstance() {
+ return {
+ logPath: ''
+ }
+ }
+ }
+ }
+})
+
import { dialog } from 'electron'
import logger from '../../src/utils/logger'
import ExportDebugController from '../../src/controllers/export-debug'
@@ -86,6 +98,7 @@ describe('Test ExportDebugController', () => {
let showErrorBoxMock: any
// controller methods
let addBundledCKBLogMock: any
+ let addBundledCKBLightClientLogMock: any
let addLogFilesMock: any
let addStatusFileMock: any
let archiveAppendMock: any
@@ -95,10 +108,11 @@ describe('Test ExportDebugController', () => {
showMessageBoxMock = jest.spyOn(dialog, 'showMessageBox')
showErrorBoxMock = jest.spyOn(dialog, 'showErrorBox')
addBundledCKBLogMock = jest.spyOn(exportDebugController, 'addBundledCKBLog')
+ addBundledCKBLightClientLogMock = jest.spyOn(exportDebugController, 'addBundledCKBLightClientLog')
addLogFilesMock = jest.spyOn(exportDebugController, 'addLogFiles')
addStatusFileMock = jest.spyOn(exportDebugController, 'addStatusFile')
archiveAppendMock = jest.spyOn(exportDebugController.archive, 'append')
- jest.spyOn(exportDebugController.archive, 'file')
+ jest.spyOn(exportDebugController.archive, 'file').mockReturnValue(undefined)
jest.spyOn(exportDebugController.archive, 'pipe').mockImplementation(() => {})
jest.spyOn(logger, 'error')
})
@@ -118,10 +132,11 @@ describe('Test ExportDebugController', () => {
})
it('should call required methods', () => {
- expect.assertions(8)
+ expect.assertions(9)
expect(showSaveDialogMock).toHaveBeenCalled()
expect(addBundledCKBLogMock).toHaveBeenCalled()
+ expect(addBundledCKBLightClientLogMock).toHaveBeenCalled()
expect(addLogFilesMock).toHaveBeenCalled()
expect(addStatusFileMock).toHaveBeenCalled()
@@ -141,11 +156,12 @@ describe('Test ExportDebugController', () => {
})
it('should not call required methods', () => {
- expect.assertions(8)
+ expect.assertions(9)
expect(showSaveDialogMock).toHaveBeenCalled()
expect(addBundledCKBLogMock).not.toHaveBeenCalled()
+ expect(addBundledCKBLightClientLogMock).not.toHaveBeenCalled()
expect(addLogFilesMock).not.toHaveBeenCalled()
expect(addStatusFileMock).not.toHaveBeenCalled()
expect(archiveAppendMock).not.toHaveBeenCalled()
diff --git a/packages/neuron-wallet/tests/services/light-runner.test.ts b/packages/neuron-wallet/tests/services/light-runner.test.ts
index 20fe748070..9f8c886ae7 100644
--- a/packages/neuron-wallet/tests/services/light-runner.test.ts
+++ b/packages/neuron-wallet/tests/services/light-runner.test.ts
@@ -9,15 +9,16 @@ const platformMock = jest.fn()
const joinMock = jest.fn()
const dirnameMock = jest.fn()
const resolveMock = jest.fn()
-const resetSyncTaskQueueMock = jest.fn()
const lightDataPathMock = jest.fn()
const existsSyncMock = jest.fn()
const readFileSyncMock = jest.fn()
const mkdirSyncMock = jest.fn()
const writeFileSyncMock = jest.fn()
+const createWriteStreamMock = jest.fn()
const spawnMock = jest.fn()
const loggerErrorMock = jest.fn()
const loggerInfoMock = jest.fn()
+const transportsGetFileMock = jest.fn()
function resetMock() {
mockFn.mockReset()
@@ -27,15 +28,16 @@ function resetMock() {
joinMock.mockReset()
dirnameMock.mockReset()
resolveMock.mockReset()
- resetSyncTaskQueueMock.mockReset()
lightDataPathMock.mockReset()
existsSyncMock.mockReset()
readFileSyncMock.mockReset()
mkdirSyncMock.mockReset()
writeFileSyncMock.mockReset()
+ createWriteStreamMock.mockReset()
spawnMock.mockReset()
loggerErrorMock.mockReset()
loggerInfoMock.mockReset()
+ transportsGetFileMock.mockReset()
}
jest.doMock('../../src/env', () => ({
@@ -50,11 +52,10 @@ jest.doMock('../../src/env', () => ({
jest.doMock('../../src/utils/logger', () => ({
info: loggerInfoMock,
error: loggerErrorMock,
-}))
-
-jest.doMock('../../src/block-sync-renderer', () => ({
- resetSyncTaskQueue: {
- push: resetSyncTaskQueueMock
+ transports: {
+ file: {
+ getFile: transportsGetFileMock
+ }
}
}))
@@ -83,6 +84,7 @@ jest.doMock('fs', () => ({
readFileSync: readFileSyncMock,
mkdirSync: mkdirSyncMock,
writeFileSync: writeFileSyncMock,
+ createWriteStream: createWriteStreamMock
}))
jest.doMock('child_process', () => ({
@@ -92,6 +94,10 @@ jest.doMock('child_process', () => ({
const { CKBLightRunner } = require('../../src/services/light-runner')
describe('test light runner', () => {
+ beforeEach(() => {
+ transportsGetFileMock.mockReturnValue({ path: ''})
+ createWriteStreamMock.mockReturnValue({ write() {} })
+ })
afterEach(() => {
resetMock()
})
@@ -207,7 +213,6 @@ describe('test light runner', () => {
existsSyncMock.mockReturnValue(true)
await CKBLightRunner.getInstance().start()
expect(mockFn).toBeCalledTimes(1)
- expect(resetSyncTaskQueueMock).toHaveBeenLastCalledWith(true)
CKBLightRunner.getInstance().stop = tmp
})
it('when runnerProcess is undefined', async () => {
@@ -219,7 +224,6 @@ describe('test light runner', () => {
existsSyncMock.mockReturnValue(true)
await CKBLightRunner.getInstance().start()
expect(mockFn).toBeCalledTimes(0)
- expect(resetSyncTaskQueueMock).toHaveBeenLastCalledWith(true)
CKBLightRunner.getInstance().stop = tmp
})
it('when runnerProcess is undefined and on error', async () => {
@@ -230,7 +234,6 @@ describe('test light runner', () => {
await CKBLightRunner.getInstance().start()
expect(CKBLightRunner.getInstance().runnerProcess).toBeDefined()
expect(mockFn).toBeCalledTimes(0)
- expect(resetSyncTaskQueueMock).toHaveBeenLastCalledWith(true)
eventEmitter.emit('error', 'errorInfo')
expect(loggerErrorMock).toBeCalledWith('CKB Light Runner:\trun fail:', 'errorInfo')
expect(CKBLightRunner.getInstance().runnerProcess).toBeUndefined()
@@ -243,7 +246,6 @@ describe('test light runner', () => {
await CKBLightRunner.getInstance().start()
expect(CKBLightRunner.getInstance().runnerProcess).toBeDefined()
expect(mockFn).toBeCalledTimes(0)
- expect(resetSyncTaskQueueMock).toHaveBeenLastCalledWith(true)
eventEmitter.emit('close', 'closeInfo')
expect(loggerInfoMock).toBeCalledWith('CKB Light Runner:\tprocess closed')
expect(CKBLightRunner.getInstance().runnerProcess).toBeUndefined()
@@ -257,10 +259,9 @@ describe('test light runner', () => {
await CKBLightRunner.getInstance().start()
expect(CKBLightRunner.getInstance().runnerProcess).toBeDefined()
expect(mockFn).toBeCalledTimes(0)
- expect(resetSyncTaskQueueMock).toHaveBeenLastCalledWith(true)
eventEmitter.stderr.emit('data', 'error-data')
expect(loggerErrorMock).toBeCalledWith('CKB Light Runner:\trun fail:', 'error-data')
- expect(CKBLightRunner.getInstance().runnerProcess).toBeUndefined()
+ expect(CKBLightRunner.getInstance().runnerProcess).toBeDefined()
})
})
@@ -268,7 +269,6 @@ describe('test light runner', () => {
it('runnerProcess is undefined', async () => {
CKBLightRunner.getInstance().runnerProcess = undefined
await CKBLightRunner.getInstance().stop()
- expect(resetSyncTaskQueueMock).toBeCalledWith(false)
})
it('runnerProcess is defined', async () => {
const emitter: EventEmitter & { kill?: Function } = new EventEmitter()
From 085659bd9b45ff4858686738fde8866b6f7eac67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com>
Date: Fri, 21 Apr 2023 09:50:48 +0800
Subject: [PATCH 07/17] fix: Fix some bugs, details are in description. (#2641)
* fix: Fix some bugs, details are in description.
1. Fetch some contract blocks when starting the sync
2. Use `getHeader` to replace `getHeaderByNumber`
3. Get live cells from local SQLITE when generating transactions.
4. Remove `getBlockchainInfo` from UI because chainInfo is not used in UI.
5. When call setCurrent should restart queue when network is light client.
---
.../src/components/NervosDAO/hooks.ts | 90 ++++++++++++-------
.../src/components/NervosDAO/index.tsx | 21 +++--
.../src/components/NervosDAORecord/hooks.ts | 12 +--
.../src/components/NervosDAORecord/index.tsx | 4 +-
.../src/components/WithdrawDialog/index.tsx | 34 ++++---
.../neuron-ui/src/containers/Main/hooks.ts | 12 ++-
packages/neuron-ui/src/services/chain.ts | 7 +-
packages/neuron-ui/src/states/init/app.ts | 3 -
.../src/stories/NervosDAO.stories.tsx | 2 +
.../src/stories/NetworkSetting.stories.tsx | 3 +
.../src/stories/NetworkStatus.stories.tsx | 2 +
.../src/stories/Overview.stories.tsx | 2 +
.../src/stories/WithdrawDialog.stories.tsx | 1 -
.../src/tests/is/isMainnet/fixtures.ts | 31 ++++++-
packages/neuron-ui/src/types/App/index.d.ts | 5 +-
packages/neuron-wallet/package.json | 1 +
.../sync/light-connector.ts | 57 +++++++++++-
.../neuron-wallet/src/controllers/wallets.ts | 6 --
.../neuron-wallet/src/services/rpc-service.ts | 40 ---------
.../src/services/sync-progress.ts | 2 +-
.../src/services/transaction-sender.ts | 56 ++++++------
.../neuron-wallet/src/services/wallets.ts | 8 ++
packages/neuron-wallet/src/utils/ckb-rpc.ts | 36 ++++++--
packages/neuron-wallet/src/utils/common.ts | 15 +++-
.../services/tx/transaction-sender.test.ts | 13 ++-
yarn.lock | 14 +++
26 files changed, 311 insertions(+), 166 deletions(-)
diff --git a/packages/neuron-ui/src/components/NervosDAO/hooks.ts b/packages/neuron-ui/src/components/NervosDAO/hooks.ts
index 0f417e346b..9bac17de3d 100644
--- a/packages/neuron-ui/src/components/NervosDAO/hooks.ts
+++ b/packages/neuron-ui/src/components/NervosDAO/hooks.ts
@@ -20,7 +20,7 @@ import {
generateDaoDepositTx,
generateDaoClaimTx,
} from 'services/remote'
-import { ckbCore, getHeaderByNumber } from 'services/chain'
+import { ckbCore, getHeader } from 'services/chain'
import { isErrorWithI18n } from 'exceptions'
import { calculateMaximumWithdraw } from '@nervosnetwork/ckb-sdk-utils'
@@ -91,12 +91,14 @@ export const useInitData = ({
updateDepositValue,
wallet,
setGenesisBlockTimestamp,
+ genesisBlockHash,
}: {
clearGeneratedTx: () => void
dispatch: React.Dispatch
updateDepositValue: (value: string) => void
wallet: State.Wallet
setGenesisBlockTimestamp: React.Dispatch>
+ genesisBlockHash?: string
}) =>
useEffect(() => {
updateNervosDaoData({ walletID: wallet.id })(dispatch)
@@ -110,9 +112,11 @@ export const useInitData = ({
: BigInt(0)
}`
)
- getHeaderByNumber('0x0')
- .then(header => setGenesisBlockTimestamp(+header.timestamp))
- .catch(err => console.error(err))
+ if (genesisBlockHash) {
+ getHeader(genesisBlockHash)
+ .then(header => setGenesisBlockTimestamp(+header.timestamp))
+ .catch(err => console.error(err))
+ }
return () => {
clearInterval(intervalId)
clearNervosDaoData()(dispatch)
@@ -444,15 +448,15 @@ export const useOnSlide = ({
export const useUpdateWithdrawList = ({
records,
- tipBlockHash,
+ tipDao,
setWithdrawList,
}: {
records: Readonly
- tipBlockHash: string
+ tipDao?: string
setWithdrawList: React.Dispatch>>
}) =>
useEffect(() => {
- if (!tipBlockHash) {
+ if (!tipDao) {
setWithdrawList(new Map())
return
}
@@ -467,7 +471,6 @@ export const useUpdateWithdrawList = ({
const blockHashes = [
...(committedTx.map(v => v.txStatus.blockHash).filter(v => !!v) as string[]),
...(records.map(v => (v.depositOutPoint ? v.blockHash : null)).filter(v => !!v) as string[]),
- tipBlockHash,
]
return ckbCore.rpc
.createBatchRequest<'getHeader', string[], CKBComponents.BlockHeader[]>(
@@ -488,7 +491,7 @@ export const useUpdateWithdrawList = ({
const withdrawList = new Map()
records.forEach(record => {
const key = getRecordKey(record)
- const withdrawBlockHash = record.depositOutPoint ? record.blockHash : tipBlockHash
+ const withdrawBlockHash = record.depositOutPoint ? record.blockHash : undefined
const formattedDepositOutPoint = record.depositOutPoint
? {
txHash: record.depositOutPoint.txHash,
@@ -503,7 +506,7 @@ export const useUpdateWithdrawList = ({
return
}
const depositDAO = hashHeaderMap.get(tx.txStatus.blockHash!)
- const withdrawDAO = hashHeaderMap.get(withdrawBlockHash)
+ const withdrawDAO = withdrawBlockHash ? hashHeaderMap.get(withdrawBlockHash) : tipDao
if (!depositDAO || !withdrawDAO) {
return
}
@@ -523,7 +526,23 @@ export const useUpdateWithdrawList = ({
.catch(() => {
setWithdrawList(new Map())
})
- }, [records, tipBlockHash, setWithdrawList])
+ }, [records, tipDao, setWithdrawList])
+
+const getBlockHashes = (txHashes: string[]) => {
+ const batchParams: ['getTransaction', string][] = txHashes.map(v => ['getTransaction', v])
+ return ckbCore.rpc
+ .createBatchRequest<'getTransaction', [string], CKBComponents.TransactionWithStatus[]>(batchParams)
+ .exec()
+ .then(res => {
+ return res.map((v, idx) => ({
+ txHash: txHashes[idx],
+ blockHash: v.txStatus.blockHash,
+ }))
+ })
+ .catch(() => {
+ return []
+ })
+}
export const useUpdateDepositEpochList = ({
records,
@@ -536,28 +555,35 @@ export const useUpdateDepositEpochList = ({
}) =>
useEffect(() => {
if (connectionStatus === 'online') {
- const recordKeyIdxMap = new Map()
- const batchParams: ['getHeaderByNumber', bigint][] = []
- records.forEach((record, idx) => {
- const depositBlockNumber = record.depositOutPoint
- ? ckbCore.utils.toUint64Le(record.daoData)
- : record.blockNumber
- if (depositBlockNumber) {
- batchParams.push(['getHeaderByNumber', BigInt(depositBlockNumber)])
- recordKeyIdxMap.set(getRecordKey(record), idx)
- }
- })
- ckbCore.rpc
- .createBatchRequest<'getHeaderByNumber', any, CKBComponents.BlockHeader[]>(batchParams)
- .exec()
- .then(res => {
- const epochList = new Map()
- records.forEach(record => {
- const key = getRecordKey(record)
- epochList.set(key, recordKeyIdxMap.get(key) !== undefined ? res[recordKeyIdxMap.get(key)!]?.epoch : null)
+ getBlockHashes(records.map(v => v.depositOutPoint?.txHash).filter(v => !!v) as string[]).then(
+ depositBlockHashes => {
+ const recordKeyIdxMap = new Map()
+ const batchParams: ['getHeader', string][] = []
+ records.forEach((record, idx) => {
+ if (!record.depositOutPoint && record.blockHash) {
+ batchParams.push(['getHeader', record.blockHash])
+ recordKeyIdxMap.set(record.outPoint.txHash, idx)
+ }
})
- setDepositEpochList(epochList)
- })
+ depositBlockHashes.forEach((v, idx) => {
+ if (v.blockHash) {
+ batchParams.push(['getHeader', v.blockHash])
+ recordKeyIdxMap.set(v.txHash, idx)
+ }
+ })
+ ckbCore.rpc
+ .createBatchRequest<'getHeader', any, CKBComponents.BlockHeader[]>(batchParams)
+ .exec()
+ .then(res => {
+ const epochList = new Map()
+ records.forEach(record => {
+ const key = record.depositOutPoint ? record.depositOutPoint.txHash : record.outPoint.txHash
+ epochList.set(key, recordKeyIdxMap.has(key) ? res[recordKeyIdxMap.get(key)!]?.epoch : null)
+ })
+ setDepositEpochList(epochList)
+ })
+ }
+ )
}
}, [records, setDepositEpochList, connectionStatus])
diff --git a/packages/neuron-ui/src/components/NervosDAO/index.tsx b/packages/neuron-ui/src/components/NervosDAO/index.tsx
index 3bb9cc88cd..7cb1f45cba 100644
--- a/packages/neuron-ui/src/components/NervosDAO/index.tsx
+++ b/packages/neuron-ui/src/components/NervosDAO/index.tsx
@@ -44,7 +44,7 @@ const NervosDAO = () => {
app: {
send = appState.send,
loadings: { sending = false },
- tipBlockHash,
+ tipDao,
tipBlockTimestamp,
epoch,
},
@@ -110,7 +110,15 @@ const NervosDAO = () => {
isBalanceReserved,
suggestFeeRate,
})
- hooks.useInitData({ clearGeneratedTx, dispatch, updateDepositValue, wallet, setGenesisBlockTimestamp })
+ const genesisBlockHash = useMemo(() => networks.find(v => v.id === networkID)?.genesisHash, [networkID, networks])
+ hooks.useInitData({
+ clearGeneratedTx,
+ dispatch,
+ updateDepositValue,
+ wallet,
+ setGenesisBlockTimestamp,
+ genesisBlockHash,
+ })
hooks.useUpdateGlobalAPC({ bestKnownBlockTimestamp, genesisBlockTimestamp, setGlobalAPC })
const onWithdrawDialogSubmit = hooks.useOnWithdrawDialogSubmit({
activeRecord,
@@ -146,7 +154,7 @@ const NervosDAO = () => {
)} CKB`
hooks.useUpdateWithdrawList({
records,
- tipBlockHash,
+ tipDao,
setWithdrawList,
})
@@ -198,13 +206,14 @@ const NervosDAO = () => {
const key = record.depositOutPoint
? `${record.depositOutPoint.txHash}-${record.depositOutPoint.index}`
: `${record.outPoint.txHash}-${record.outPoint.index}`
+ const txHash = record.depositOutPoint ? record.depositOutPoint.txHash : record.outPoint.txHash
const props: DAORecordProps = {
...record,
tipBlockTimestamp,
withdrawCapacity: withdrawList.get(key) || null,
onClick: onActionClick,
- depositEpoch: depositEpochList.get(key) || '',
+ depositEpoch: depositEpochList.get(txHash) || '',
currentEpoch: epoch,
genesisBlockTimestamp,
connectionStatus,
@@ -321,11 +330,11 @@ const NervosDAO = () => {
record={activeRecord}
onDismiss={onWithdrawDialogDismiss}
onSubmit={onWithdrawDialogSubmit}
- tipBlockHash={tipBlockHash}
+ tipDao={tipDao}
currentEpoch={epoch}
/>
) : null
- }, [activeRecord, onWithdrawDialogDismiss, onWithdrawDialogSubmit, tipBlockHash, epoch])
+ }, [activeRecord, onWithdrawDialogDismiss, onWithdrawDialogSubmit, tipDao, epoch])
const free = BigInt(wallet.balance)
const locked = records
diff --git a/packages/neuron-ui/src/components/NervosDAORecord/hooks.ts b/packages/neuron-ui/src/components/NervosDAORecord/hooks.ts
index d57ef27cf0..b28b447836 100644
--- a/packages/neuron-ui/src/components/NervosDAORecord/hooks.ts
+++ b/packages/neuron-ui/src/components/NervosDAORecord/hooks.ts
@@ -1,24 +1,24 @@
import { useEffect, useCallback } from 'react'
+import { getHeader } from 'services/chain'
import { showTransactionDetails } from 'services/remote'
-import { getHeaderByNumber } from 'services/chain'
import { calculateAPC, CONSTANTS } from 'utils'
const { MILLISECONDS_IN_YEAR } = CONSTANTS
export const useUpdateWithdrawEpochs = ({
isWithdrawn,
- blockNumber,
+ blockHash,
setWithdrawEpoch,
setWithdrawTimestamp,
}: {
isWithdrawn: boolean
- blockNumber: CKBComponents.BlockNumber | null
+ blockHash: CKBComponents.BlockHeader['hash'] | null
setWithdrawEpoch: React.Dispatch
setWithdrawTimestamp: React.Dispatch
}) => {
useEffect(() => {
- if (isWithdrawn && blockNumber) {
- getHeaderByNumber(BigInt(blockNumber))
+ if (isWithdrawn && blockHash) {
+ getHeader(blockHash)
.then(header => {
setWithdrawEpoch(header.epoch)
setWithdrawTimestamp(header.timestamp)
@@ -27,7 +27,7 @@ export const useUpdateWithdrawEpochs = ({
console.error(err)
})
}
- }, [isWithdrawn, blockNumber, setWithdrawEpoch, setWithdrawTimestamp])
+ }, [isWithdrawn, blockHash, setWithdrawEpoch, setWithdrawTimestamp])
}
export const useUpdateApc = ({
diff --git a/packages/neuron-ui/src/components/NervosDAORecord/index.tsx b/packages/neuron-ui/src/components/NervosDAORecord/index.tsx
index ed41668389..952ebbf9c4 100644
--- a/packages/neuron-ui/src/components/NervosDAORecord/index.tsx
+++ b/packages/neuron-ui/src/components/NervosDAORecord/index.tsx
@@ -41,7 +41,7 @@ export interface DAORecordProps extends State.NervosDAORecord {
}
export const DAORecord = ({
- blockNumber,
+ blockHash,
tipBlockTimestamp,
capacity,
outPoint: { txHash, index },
@@ -73,7 +73,7 @@ export const DAORecord = ({
setApc,
})
- hooks.useUpdateWithdrawEpochs({ isWithdrawn, blockNumber, setWithdrawEpoch, setWithdrawTimestamp })
+ hooks.useUpdateWithdrawEpochs({ isWithdrawn, blockHash, setWithdrawEpoch, setWithdrawTimestamp })
const onTxRecordClick = hooks.useOnTxRecordClick()
const currentEpochValue = epochParser(currentEpoch).value
diff --git a/packages/neuron-ui/src/components/WithdrawDialog/index.tsx b/packages/neuron-ui/src/components/WithdrawDialog/index.tsx
index 9b8f180691..f887a19f54 100644
--- a/packages/neuron-ui/src/components/WithdrawDialog/index.tsx
+++ b/packages/neuron-ui/src/components/WithdrawDialog/index.tsx
@@ -3,8 +3,9 @@ import { useTranslation } from 'react-i18next'
import Button from 'widgets/Button'
import { CONSTANTS, shannonToCKBFormatter, localNumberFormatter, useCalculateEpochs, useDialog } from 'utils'
-import { calculateDaoMaximumWithdraw, getHeader } from 'services/chain'
+import { getTransaction, getHeader } from 'services/chain'
+import { calculateMaximumWithdraw } from '@nervosnetwork/ckb-sdk-utils'
import styles from './withdrawDialog.module.scss'
const { WITHDRAW_EPOCHS } = CONSTANTS
@@ -13,13 +14,13 @@ const WithdrawDialog = ({
onDismiss,
onSubmit,
record,
- tipBlockHash,
+ tipDao,
currentEpoch,
}: {
onDismiss: () => void
onSubmit: () => void
record: State.NervosDAORecord
- tipBlockHash: string
+ tipDao?: string
currentEpoch: string
}) => {
const [t] = useTranslation()
@@ -42,24 +43,29 @@ const WithdrawDialog = ({
}, [record])
useEffect(() => {
- if (!record || !tipBlockHash) {
+ if (!record || !tipDao) {
return
}
- calculateDaoMaximumWithdraw(
- {
- txHash: record.outPoint.txHash,
- index: `0x${BigInt(record.outPoint.index).toString(16)}`,
- },
- tipBlockHash
- )
- .then((res: string) => {
- setWithdrawValue(res)
+ getTransaction(record.outPoint.txHash)
+ .then(tx => {
+ if (tx.txStatus.blockHash) {
+ getHeader(tx.txStatus.blockHash).then(header => {
+ setWithdrawValue(
+ calculateMaximumWithdraw(
+ tx.transaction.outputs[+record.outPoint.index],
+ tx.transaction.outputsData[+record.outPoint.index],
+ header.dao,
+ tipDao
+ )
+ )
+ })
+ }
})
.catch((err: Error) => {
console.error(err)
})
- }, [record, tipBlockHash])
+ }, [record, tipDao])
const { currentEpochInfo, targetEpochValue } = useCalculateEpochs({ depositEpoch, currentEpoch })
diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts
index 25d96d94ee..a0714bb07f 100644
--- a/packages/neuron-ui/src/containers/Main/hooks.ts
+++ b/packages/neuron-ui/src/containers/Main/hooks.ts
@@ -18,7 +18,7 @@ import {
SyncState as SyncStateSubject,
Command as CommandSubject,
} from 'services/subjects'
-import { ckbCore, getBlockchainInfo, getTipHeader } from 'services/chain'
+import { ckbCore, getTipHeader } from 'services/chain'
import { networks as networksCache, currentNetworkID as currentNetworkIDCache } from 'services/localCache'
import { WalletWizardPath } from 'components/WalletWizard'
import { ErrorCode, RoutePath, getConnectionStatus } from 'utils'
@@ -38,18 +38,16 @@ export const useSyncChainData = ({ chainURL, dispatch }: { chainURL: string; dis
useEffect(() => {
let timer: NodeJS.Timeout
const syncBlockchainInfo = () => {
- Promise.all([getTipHeader(), getBlockchainInfo()])
- .then(([header, chainInfo]) => {
+ getTipHeader()
+ .then(header => {
if (isCurrentUrl(chainURL)) {
dispatch({
type: AppActions.UpdateChainInfo,
payload: {
tipBlockNumber: `${BigInt(header.number)}`,
- tipBlockHash: header.hash,
+ tipDao: header.dao,
tipBlockTimestamp: +header.timestamp,
- chain: chainInfo.chain,
- difficulty: BigInt(chainInfo.difficulty),
- epoch: chainInfo.epoch,
+ epoch: header.epoch,
},
})
diff --git a/packages/neuron-ui/src/services/chain.ts b/packages/neuron-ui/src/services/chain.ts
index 0ddb6a7491..463b53868e 100644
--- a/packages/neuron-ui/src/services/chain.ts
+++ b/packages/neuron-ui/src/services/chain.ts
@@ -1,18 +1,17 @@
import CKBCore from '@nervosnetwork/ckb-sdk-core'
export const ckbCore = new CKBCore('')
-export const { getHeader, getBlockchainInfo, getTipHeader, getHeaderByNumber, getFeeRateStats } = ckbCore.rpc
+export const { getHeader, getBlockchainInfo, getTipHeader, getHeaderByNumber, getFeeRateStats, getTransaction } = ckbCore.rpc
export const { calculateDaoMaximumWithdraw } = ckbCore
export const { toUint64Le, parseEpoch } = ckbCore.utils
+
export default {
ckbCore,
- getBlockchainInfo,
getHeader,
getTipHeader,
- getHeaderByNumber,
- calculateDaoMaximumWithdraw,
+ getTransaction,
toUint64Le,
getFeeRateStats,
}
diff --git a/packages/neuron-ui/src/states/init/app.ts b/packages/neuron-ui/src/states/init/app.ts
index 35a433466a..4183fd81cd 100644
--- a/packages/neuron-ui/src/states/init/app.ts
+++ b/packages/neuron-ui/src/states/init/app.ts
@@ -12,10 +12,7 @@ const initNotifications: Array = [
export const appState: State.App = {
tipBlockNumber: '',
- tipBlockHash: '',
tipBlockTimestamp: 0,
- chain: '',
- difficulty: BigInt(0),
epoch: '',
send: {
txID: '',
diff --git a/packages/neuron-ui/src/stories/NervosDAO.stories.tsx b/packages/neuron-ui/src/stories/NervosDAO.stories.tsx
index f3b25c014b..8ab78818bc 100644
--- a/packages/neuron-ui/src/stories/NervosDAO.stories.tsx
+++ b/packages/neuron-ui/src/stories/NervosDAO.stories.tsx
@@ -40,6 +40,7 @@ const stateTemplate = {
remote: 'http://testnet.nervos.com',
chain: 'ckb_testnet',
type: 1 as 0 | 1,
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
},
],
},
@@ -158,6 +159,7 @@ stories.addDecorator(withKnobs).add('With knobs', () => {
remote: text('Network Address', 'http://testnet.nervos.com'),
chain: text('Chain', 'ckb_testnet'),
type: 1 as 0 | 1,
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
},
],
},
diff --git a/packages/neuron-ui/src/stories/NetworkSetting.stories.tsx b/packages/neuron-ui/src/stories/NetworkSetting.stories.tsx
index ce7cda1263..75f24be4db 100644
--- a/packages/neuron-ui/src/stories/NetworkSetting.stories.tsx
+++ b/packages/neuron-ui/src/stories/NetworkSetting.stories.tsx
@@ -13,6 +13,7 @@ const states: { [title: string]: State.Network[] } = {
remote: 'http://localhost:8114',
chain: 'ckb',
type: 0,
+ genesisHash: '0x92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5',
},
{
id: 'Testnet',
@@ -20,6 +21,7 @@ const states: { [title: string]: State.Network[] } = {
remote: 'http://localhost:8114',
chain: 'ckb_testnet',
type: 1,
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
},
{
id: 'Local',
@@ -27,6 +29,7 @@ const states: { [title: string]: State.Network[] } = {
remote: 'http://localhost:8114',
chain: 'ckb_devnet',
type: 1,
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
},
],
}
diff --git a/packages/neuron-ui/src/stories/NetworkStatus.stories.tsx b/packages/neuron-ui/src/stories/NetworkStatus.stories.tsx
index 48d16ef422..78655db6e2 100644
--- a/packages/neuron-ui/src/stories/NetworkStatus.stories.tsx
+++ b/packages/neuron-ui/src/stories/NetworkStatus.stories.tsx
@@ -10,6 +10,7 @@ const defaultProps: Omit {},
isLookingValidTarget: false,
@@ -71,6 +72,7 @@ stories.add('With knobs', () => {
type: select('Type', [0, 1], 0) as any,
id: text('id', 'd'),
chain: select('Chain', ['ckb', 'ckb_testnet', 'ckb_dev'], 'ckb'),
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
},
syncPercents: number('Sync Percents', 1),
syncBlockNumbers: text('Sync Block Number', '1/100'),
diff --git a/packages/neuron-ui/src/stories/Overview.stories.tsx b/packages/neuron-ui/src/stories/Overview.stories.tsx
index c73c96b726..e35e89fbdd 100644
--- a/packages/neuron-ui/src/stories/Overview.stories.tsx
+++ b/packages/neuron-ui/src/stories/Overview.stories.tsx
@@ -41,6 +41,7 @@ const stateTemplate = {
remote: 'http://testnet.nervos.com',
chain: 'ckb_testnet',
type: 1 as 0 | 1,
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
},
],
},
@@ -116,6 +117,7 @@ stories.addDecorator(withKnobs).add('With knobs', () => {
remote: text('Network Address', 'http://testnet.nervos.com'),
chain: text('Chain', 'ckb_testnet'),
type: 1 as 0 | 1,
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
},
],
},
diff --git a/packages/neuron-ui/src/stories/WithdrawDialog.stories.tsx b/packages/neuron-ui/src/stories/WithdrawDialog.stories.tsx
index c4e992bd2e..c6925ae0c6 100644
--- a/packages/neuron-ui/src/stories/WithdrawDialog.stories.tsx
+++ b/packages/neuron-ui/src/stories/WithdrawDialog.stories.tsx
@@ -36,7 +36,6 @@ const props = {
timestamp: Date.now().toString(),
depositTimestamp: Date.now().toString(),
},
- tipBlockHash: '0x70abeeaa2ed08b7d7659341a122b9a2f2ede99bb6bd0df7398d7ffe488beab61',
currentEpoch: '0x00000000',
}
diff --git a/packages/neuron-ui/src/tests/is/isMainnet/fixtures.ts b/packages/neuron-ui/src/tests/is/isMainnet/fixtures.ts
index 739207c1dd..d9fbc65adf 100644
--- a/packages/neuron-ui/src/tests/is/isMainnet/fixtures.ts
+++ b/packages/neuron-ui/src/tests/is/isMainnet/fixtures.ts
@@ -16,7 +16,16 @@ const fixtures = {
'Should return false when network id cannot be found in network list': {
params: {
networkID: 'testnet',
- networks: [{ id: 'mainnet', chain: 'ckb', type: 0 as 0 | 1, name: 'Mainnet', remote: 'http://localhost:8114' }],
+ networks: [
+ {
+ id: 'mainnet',
+ chain: 'ckb',
+ type: 0 as 0 | 1,
+ name: 'Mainnet',
+ remote: 'http://localhost:8114',
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
+ },
+ ],
},
expected: false,
},
@@ -24,7 +33,14 @@ const fixtures = {
params: {
networkID: 'testnet',
networks: [
- { id: 'testnet', chain: 'ckb_testnet', type: 0 as 0 | 1, name: 'Mainnet', remote: 'http://localhost:8114' },
+ {
+ id: 'testnet',
+ chain: 'ckb_testnet',
+ type: 0 as 0 | 1,
+ name: 'Mainnet',
+ remote: 'http://localhost:8114',
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
+ },
],
},
expected: false,
@@ -32,7 +48,16 @@ const fixtures = {
"Should return true when network id can be found in network list and it's Mainnet": {
params: {
networkID: 'mainnet',
- networks: [{ id: 'mainnet', chain: 'ckb', type: 0 as 0 | 1, name: 'Mainnet', remote: 'http://localhost:8114' }],
+ networks: [
+ {
+ id: 'mainnet',
+ chain: 'ckb',
+ type: 0 as 0 | 1,
+ name: 'Mainnet',
+ remote: 'http://localhost:8114',
+ genesisHash: '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606',
+ },
+ ],
},
expected: true,
},
diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts
index d0ebddc131..9348f8433d 100644
--- a/packages/neuron-ui/src/types/App/index.d.ts
+++ b/packages/neuron-ui/src/types/App/index.d.ts
@@ -125,10 +125,8 @@ declare namespace State {
interface App {
tipBlockNumber: string
- tipBlockHash: string
+ tipDao?: string
tipBlockTimestamp: number
- chain: string
- difficulty: bigint
epoch: string
send: Send
passwordRequest: PasswordRequest
@@ -150,6 +148,7 @@ declare namespace State {
remote: string
chain: 'ckb' | 'ckb_testnet' | 'ckb_dev' | string
type: 0 | 1 | 2
+ genesisHash: string
}
interface Network extends NetworkProperty {
diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json
index 937be7321e..687b03a8d8 100644
--- a/packages/neuron-wallet/package.json
+++ b/packages/neuron-wallet/package.json
@@ -37,6 +37,7 @@
},
"dependencies": {
"@ckb-lumos/base": "0.18.0-rc2",
+ "@ckb-lumos/codec": "0.19.0",
"@ckb-lumos/rpc": "0.18.0-rc2",
"@iarna/toml": "2.2.5",
"@ledgerhq/hw-transport-node-hid": "6.27.13",
diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
index 61affcacf7..40d13300a6 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
@@ -14,6 +14,9 @@ import HexUtils from 'utils/hex'
import Multisig from 'services/multisig'
import { SyncAddressType } from 'database/chain/entities/sync-progress'
import WalletService from 'services/wallets'
+import AssetAccountInfo from 'models/asset-account-info'
+import { DepType } from 'models/chain/cell-dep'
+import { molecule, number } from '@ckb-lumos/codec'
interface SyncQueueParam {
script: CKBComponents.Script
@@ -22,6 +25,16 @@ interface SyncQueueParam {
cursor?: HexString
}
+const unpackGroup = molecule.vector(
+ molecule.struct(
+ {
+ tx_hash: number.Uint256BE,
+ index: number.Uint32LE
+ },
+ ['tx_hash', 'index']
+ )
+)
+
export default class LightConnector extends Connector {
private lightRpc: LightRPC
private indexer: CkbIndexer
@@ -42,8 +55,50 @@ export default class LightConnector extends Connector {
this.indexer = new CkbIndexer(nodeUrl, nodeUrl)
this.lightRpc = new LightRPC(nodeUrl)
this.addressMetas = addresses.map(address => AddressMeta.fromObject(address))
+ this.indexerQueryQueue = queue(this.collectLiveCellsByScript.bind(this))
+
+ // fetch some dep cell
+ this.fetchDepCell()
+ }
- this.indexerQueryQueue = queue(this.collectLiveCellsByScript)
+ private async fetchDepCell() {
+ const assetAccountInfo = new AssetAccountInfo()
+ const fetchCellDeps = [
+ assetAccountInfo.anyoneCanPayCellDep,
+ assetAccountInfo.sudtCellDep,
+ assetAccountInfo.getNftClassInfo().cellDep,
+ assetAccountInfo.getNftInfo().cellDep,
+ assetAccountInfo.getNftIssuerInfo().cellDep,
+ assetAccountInfo.getLegacyAnyoneCanPayInfo().cellDep,
+ assetAccountInfo.getChequeInfo().cellDep
+ ]
+ const fetchTxHashes = fetchCellDeps
+ .map(v => v.outPoint.txHash)
+ .map<[string, string]>(v => ['fetchTransaction', v])
+ let txs = await this.lightRpc
+ .createBatchRequest(fetchTxHashes)
+ .exec()
+ if (txs.some(v => !v)) {
+ // if some txs fetch to added, then fetch the actural txs
+ txs = await this.lightRpc
+ .createBatchRequest(fetchTxHashes)
+ .exec()
+ }
+ const depGroupOutputsData: string[] = fetchCellDeps
+ .map((v, idx) => {
+ if (v.depType === DepType.DepGroup) {
+ return txs[idx]?.transaction?.outputsData?.[+v.outPoint.index]
+ }
+ })
+ .filter((v): v is string => !!v)
+ const depGroupTxHashes = [
+ ...new Set(depGroupOutputsData.map(v => unpackGroup.unpack(v).map(v => v.tx_hash.toHexString())).flat())
+ ]
+ await this.lightRpc
+ .createBatchRequest(
+ depGroupTxHashes.map(v => ['fetchTransaction', v])
+ )
+ .exec()
}
private async synchronize() {
diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts
index c2cbe6b251..7208a31440 100644
--- a/packages/neuron-wallet/src/controllers/wallets.ts
+++ b/packages/neuron-wallet/src/controllers/wallets.ts
@@ -33,8 +33,6 @@ import AddressParser from 'models/address-parser'
import MultisigConfigModel from 'models/multisig-config'
import NodeService from 'services/node'
import { generateRPC } from 'utils/ckb-rpc'
-import { resetSyncTaskQueue } from 'block-sync-renderer'
-import { NetworkType } from 'models/network'
export default class WalletsController {
public async getAll(): Promise[]>> {
@@ -368,10 +366,6 @@ export default class WalletsController {
if (!currentWallet || id !== currentWallet.id) {
throw new CurrentWalletNotSet()
}
- const network = NetworksService.getInstance().getCurrent()
- if (network.type === NetworkType.Light) {
- resetSyncTaskQueue.asyncPush(true)
- }
return {
status: ResponseCode.Success,
result: currentWallet.toJSON()
diff --git a/packages/neuron-wallet/src/services/rpc-service.ts b/packages/neuron-wallet/src/services/rpc-service.ts
index ca892dae56..a20e3035f5 100644
--- a/packages/neuron-wallet/src/services/rpc-service.ts
+++ b/packages/neuron-wallet/src/services/rpc-service.ts
@@ -1,10 +1,7 @@
-import HexUtils from 'utils/hex'
import CommonUtils from 'utils/common'
import Block from 'models/chain/block'
import BlockHeader from 'models/chain/block-header'
import TransactionWithStatus from 'models/chain/transaction-with-status'
-import OutPoint from 'models/chain/out-point'
-import CellWithStatus from 'models/chain/cell-with-status'
import logger from 'utils/logger'
import { generateRPC } from 'utils/ckb-rpc'
@@ -19,16 +16,6 @@ export default class RpcService {
this.rpc = generateRPC(url)
}
- public async getRangeBlockHeaders(blockNumbers: string[]): Promise {
- const headers: BlockHeader[] = await Promise.all(
- blockNumbers.map(async num => {
- return (await this.retryGetBlockHeader(num))!
- })
- )
-
- return headers
- }
-
public async getTipBlockNumber(): Promise {
return this.rpc.getTipBlockNumber()
}
@@ -38,12 +25,6 @@ export default class RpcService {
return BlockHeader.fromSDK(result)
}
- public async retryGetBlockHeader(num: string): Promise {
- return this.retry(async () => {
- return this.getBlockHeaderByNumber(num)
- })
- }
-
/**
* TODO: rejected tx should be handled
* {
@@ -62,11 +43,6 @@ export default class RpcService {
return undefined
}
- public async getLiveCell(outPoint: OutPoint, withData: boolean = false): Promise {
- const result = await this.rpc.getLiveCell(outPoint.toSDK(), withData)
- return CellWithStatus.fromSDK(result)
- }
-
public async getHeader(hash: string): Promise {
const result = await this.rpc.getHeader(hash)
if (result) {
@@ -75,14 +51,6 @@ export default class RpcService {
return undefined
}
- public async getHeaderByNumber(num: string): Promise {
- const result = await this.rpc.getHeaderByNumber(HexUtils.toHex(num))
- if (result) {
- return BlockHeader.fromSDK(result)
- }
- return undefined
- }
-
public async getGenesisBlock(): Promise {
const block = await this.rpc.getGenesisBlock()
if (block) {
@@ -91,14 +59,6 @@ export default class RpcService {
return undefined
}
- public async getBlockHeaderByNumber(num: string): Promise {
- const header = await this.rpc.getHeaderByNumber(HexUtils.toHex(num))
- if (header) {
- return BlockHeader.fromSDK(header)
- }
- return undefined
- }
-
public async genesisBlockHash(): Promise {
return this.retry(async () => {
return this.rpc.getGenesisBlockHash()
diff --git a/packages/neuron-wallet/src/services/sync-progress.ts b/packages/neuron-wallet/src/services/sync-progress.ts
index 31d839de37..98598d4c25 100644
--- a/packages/neuron-wallet/src/services/sync-progress.ts
+++ b/packages/neuron-wallet/src/services/sync-progress.ts
@@ -97,7 +97,7 @@ export default class SyncProgressService {
.select('MIN(blockStartNumber) as blockStartNumber, walletId')
.where({ addressType: SyncAddressType.Default })
.groupBy('walletId')
- .getMany()
+ .getRawMany<{ blockStartNumber: number; walletId: string }>()
return items.reduce>((pre, cur) => ({ ...pre, [cur.walletId]: cur.blockStartNumber }), {})
}
diff --git a/packages/neuron-wallet/src/services/transaction-sender.ts b/packages/neuron-wallet/src/services/transaction-sender.ts
index 92ffc81680..8312e0df6b 100644
--- a/packages/neuron-wallet/src/services/transaction-sender.ts
+++ b/packages/neuron-wallet/src/services/transaction-sender.ts
@@ -18,7 +18,6 @@ import Output from 'models/chain/output'
import RpcService from 'services/rpc-service'
import WitnessArgs from 'models/chain/witness-args'
import Transaction from 'models/chain/transaction'
-import BlockHeader from 'models/chain/block-header'
import Script from 'models/chain/script'
import Multisig from 'models/multisig'
import Blake2b from 'models/blake2b'
@@ -43,6 +42,7 @@ import { SignStatus } from 'models/offline-sign'
import NetworksService from './networks'
import { generateRPC } from 'utils/ckb-rpc'
import CKB from '@nervosnetwork/ckb-sdk-core'
+import CellsService from './cells'
interface SignInfo {
witnessArgs: WitnessArgs
@@ -535,19 +535,15 @@ export default class TransactionSender {
feeRate: string = '0'
): Promise => {
const changeAddress: string = await this.getChangeAddress()
- const url: string = NodeService.getInstance().nodeUrl
- const rpcService = new RpcService(url)
- // for some reason with data won't work
- const cellWithStatus = await rpcService.getLiveCell(new OutPoint(outPoint.txHash, outPoint.index), true)
- const prevOutput = cellWithStatus.cell!.output
- if (!cellWithStatus.isLive()) {
+ const nftCellOutput = await CellsService.getLiveCell(new OutPoint(outPoint.txHash, outPoint.index))
+ if (!nftCellOutput) {
throw new CellIsNotYetLive()
}
const tx = await TransactionGenerator.generateTransferNftTx(
walletId,
outPoint,
- prevOutput,
+ nftCellOutput,
receiveAddress,
changeAddress,
fee,
@@ -592,8 +588,8 @@ export default class TransactionSender {
const url: string = NodeService.getInstance().nodeUrl
const rpcService = new RpcService(url)
- const cellWithStatus = await rpcService.getLiveCell(outPoint, false)
- if (!cellWithStatus.isLive()) {
+ const depositeOutput = await CellsService.getLiveCell(outPoint)
+ if (!depositeOutput) {
throw new CellIsNotYetLive()
}
const prevTx = await rpcService.getTransaction(outPoint.txHash)
@@ -605,11 +601,10 @@ export default class TransactionSender {
const wallet = WalletService.getInstance().get(walletID)
const changeAddress = await wallet.getNextChangeAddress()
- const prevOutput = cellWithStatus.cell!.output
const tx: Transaction = await TransactionGenerator.startWithdrawFromDao(
walletID,
outPoint,
- prevOutput,
+ depositeOutput,
depositBlockHeader!.number,
depositBlockHeader!.hash,
changeAddress!.address,
@@ -636,8 +631,8 @@ export default class TransactionSender {
const url: string = NodeService.getInstance().nodeUrl
const rpcService = new RpcService(url)
- const cellStatus = await rpcService.getLiveCell(withdrawingOutPoint, true)
- if (!cellStatus.isLive()) {
+ const withdrawOutput = await CellsService.getLiveCell(withdrawingOutPoint)
+ if (!withdrawOutput) {
throw new CellIsNotYetLive()
}
const prevTx = (await rpcService.getTransaction(withdrawingOutPoint.txHash))!
@@ -648,12 +643,23 @@ export default class TransactionSender {
const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep()
const daoCellDep = await SystemScriptInfo.getInstance().getDaoCellDep()
- const content = cellStatus.cell!.data!.content
- const buf = Buffer.from(content.slice(2), 'hex')
- const depositBlockNumber: bigint = buf.readBigUInt64LE()
- const depositBlockHeader: BlockHeader = (await rpcService.getHeaderByNumber(depositBlockNumber.toString()))!
+ const content = withdrawOutput.daoData
+ if (!content) {
+ throw new Error(`Withdraw output cell is not a dao cell, ${withdrawOutput.outPoint?.txHash}`)
+ }
+ if (!withdrawOutput.depositOutPoint) {
+ throw new Error('DAO has not finish step first withdraw')
+ }
+ const depositeTx = await rpcService.getTransaction(withdrawOutput.depositOutPoint.txHash)
+ if (!depositeTx?.txStatus.blockHash) {
+ throw new Error(`Get deposite block hash failed with tx hash ${withdrawOutput.depositOutPoint.txHash}`)
+ }
+ const depositBlockHeader = await rpcService.getHeader(depositeTx.txStatus.blockHash)
+ if (!depositBlockHeader) {
+ throw new Error(`Get Header failed with blockHash ${depositeTx.txStatus.blockHash}`)
+ }
const depositEpoch = this.parseEpoch(BigInt(depositBlockHeader.epoch))
- const depositCapacity: bigint = BigInt(cellStatus.cell!.output.capacity)
+ const depositCapacity: bigint = BigInt(withdrawOutput.capacity)
const withdrawBlockHeader = (await rpcService.getHeader(prevTx.txStatus.blockHash!))!
const withdrawEpoch = this.parseEpoch(BigInt(withdrawBlockHeader.epoch))
@@ -687,12 +693,11 @@ export default class TransactionSender {
const outputs: Output[] = [output]
- const previousOutput = cellStatus.cell!.output
const input: Input = new Input(
withdrawingOutPoint,
minimalSince.toString(),
- previousOutput.capacity,
- previousOutput.lock
+ withdrawOutput.capacity,
+ withdrawOutput.lock
)
const withdrawWitnessArgs: WitnessArgs = new WitnessArgs(WitnessArgs.EMPTY_LOCK, '0x0000000000000000')
@@ -754,8 +759,8 @@ export default class TransactionSender {
const url: string = NodeService.getInstance().nodeUrl
const rpcService = new RpcService(url)
- const cellWithStatus = await rpcService.getLiveCell(outPoint, false)
- if (!cellWithStatus.isLive()) {
+ const locktimeOutput = await CellsService.getLiveCell(outPoint)
+ if (!locktimeOutput) {
throw new CellIsNotYetLive()
}
const prevTx = await rpcService.getTransaction(outPoint.txHash)
@@ -767,10 +772,9 @@ export default class TransactionSender {
const receivingAddressInfo = await wallet.getNextAddress()
const receivingAddress = receivingAddressInfo!.address
- const prevOutput = cellWithStatus.cell!.output
const tx: Transaction = await TransactionGenerator.generateWithdrawMultiSignTx(
outPoint,
- prevOutput,
+ locktimeOutput,
receivingAddress,
fee,
feeRate
diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts
index 00aa64e20c..1156010563 100644
--- a/packages/neuron-wallet/src/services/wallets.ts
+++ b/packages/neuron-wallet/src/services/wallets.ts
@@ -12,6 +12,9 @@ import AddressService from './addresses'
import { DeviceInfo } from './hardware/common'
import HdPublicKeyInfo from 'database/chain/entities/hd-public-key-info'
import { getConnection, In, Not } from 'typeorm'
+import NetworksService from './networks'
+import { NetworkType } from 'models/network'
+import { resetSyncTaskQueue } from 'block-sync-renderer'
const fileService = FileService.getInstance()
@@ -446,6 +449,11 @@ export default class WalletService {
}
}
+ const network = NetworksService.getInstance().getCurrent()
+ if (network.type === NetworkType.Light) {
+ resetSyncTaskQueue.asyncPush(true)
+ }
+
this.listStore.writeSync(this.currentWalletKey, id)
}
diff --git a/packages/neuron-wallet/src/utils/ckb-rpc.ts b/packages/neuron-wallet/src/utils/ckb-rpc.ts
index 5f7361a12e..e0f6acc08f 100644
--- a/packages/neuron-wallet/src/utils/ckb-rpc.ts
+++ b/packages/neuron-wallet/src/utils/ckb-rpc.ts
@@ -9,6 +9,7 @@ import https from 'https'
import http from 'http'
import { request } from 'undici'
import { BUNDLED_LIGHT_CKB_URL, LIGHT_CLIENT_TESTNET } from './const'
+import CommonUtils from './common'
export interface LightScriptFilter {
script: CKBComponents.Script
@@ -85,6 +86,14 @@ const lightRPCProperties: Record[0]
paramsFormatters: [paramsFormatter.toRawTransaction],
resultFormatters: resultFormatter.toHash,
},
+ fetchTransaction: {
+ method: 'fetch_transaction',
+ paramsFormatters: [paramsFormatter.toHash],
+ resultFormatters: (result: { status: 'fetched' | 'fetching' | 'added' | 'not_found', data?: RPC.TransactionWithStatus }) => {
+ if (result.status === 'fetched' && result.data) {return resultFormatter.toTransactionWithStatus(result.data)}
+ return null
+ }
+ }
}
export class FullCKBRPC extends CKBRPC {
@@ -107,10 +116,12 @@ export class LightRPC extends Base {
afterCursor: HexString
) => Promise<{ lastCursor: HexString, txs: { txHash: HexString, txIndex: HexString, blockNumber: CKBComponents.BlockNumber }[]}>
- getGenesisBlock: () => Promise
- exceptionMethods = ['getCurrentEpoch', 'getEpochByNumber', 'getBlockHash']
- coverMethods = ['getTipBlockNumber', 'syncState', 'getBlockchainInfo', 'sendTransaction']
+ getTransactionInLight: Base['getTransaction']
+ fetchTransaction: (hash: string) => Promise
+ getGenesisBlock: () => Promise
+ exceptionMethods = ['getCurrentEpoch', 'getEpochByNumber', 'getBlockHash', 'getLiveCell']
+ coverMethods = ['getTipBlockNumber', 'syncState', 'getBlockchainInfo', 'sendTransaction', 'getTransaction']
constructor(url: string) {
super()
@@ -131,6 +142,21 @@ export class LightRPC extends Base {
this.getGenesisBlock = new Method(this.node, { name: 'getGenesisBlock', ...lightRPCProperties['getGenesisBlock'] }).call
const sendTransactionMethod = new Method(this.node, { name: 'sendTransaction', ...lightRPCProperties['sendTransaction'] })
this.sendTransaction = (tx: CKBComponents.RawTransaction) => sendTransactionMethod.call(tx)
+ this.getTransactionInLight = new Method(this.node, { name: 'getTransaction', ...this.rpcProperties['getTransaction'] }).call
+ this.fetchTransaction = new Method(this.node, { name: 'fetchTransaction', ...lightRPCProperties['fetchTransaction'] }).call
+ }
+
+ getTransaction = async (hash: string): Promise => {
+ let tx = await this.getTransactionInLight(hash)
+ if (!tx?.transaction) {
+ tx = await CommonUtils.retry(3, 100, async () => {
+ const tmp = await this.fetchTransaction(hash)
+ if (tmp === null) {throw new Error('Not fetch the transaction current')}
+ return tmp
+ })
+ if (!tx) {throw new Error(`Fetch transaction tx failed, please try it later: ${hash}`)}
+ }
+ return tx
}
getTipBlockNumber = async () => {
@@ -238,7 +264,7 @@ export class LightRPC extends Base {
async value() {
const payload = proxied.map(([f, ...p], i) => {
try {
- const method = new Method(ctx.node, { ...ctx.rpcProperties[f], name: f })
+ const method = new Method(ctx.node, { ...({ ...ctx.rpcProperties, ...lightRPCProperties }[f]), name: f })
return method.getPayload(...p)
} catch (err) {
throw new PayloadInBatchException(i, err.message)
@@ -256,7 +282,7 @@ export class LightRPC extends Base {
if (res.id !== payload[i].id) {
return new IdNotMatchedInBatchException(i, payload[i].id, res.id)
}
- return ctx.rpcProperties[proxied[i][0]].resultFormatters?.(res.result) ?? res.result
+ return ({ ...ctx.rpcProperties, ...lightRPCProperties })[proxied[i][0]].resultFormatters?.(res.result) ?? res.result
})
},
},
diff --git a/packages/neuron-wallet/src/utils/common.ts b/packages/neuron-wallet/src/utils/common.ts
index b0a7cd13ed..81f45eaa05 100644
--- a/packages/neuron-wallet/src/utils/common.ts
+++ b/packages/neuron-wallet/src/utils/common.ts
@@ -1,22 +1,31 @@
import logger from 'utils/logger'
+//TODO remove it after typescript upgrade to above 4.5
+type Awaited = T extends null | undefined
+ ? T // special case for `null | undefined` when not in `--strictNullChecks` mode
+ : T extends object & { then(onfulfilled: infer F, ...args: infer _): any } // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
+ ? F extends (value: infer V, ...args: infer _) => any // if the argument to `then` is callable, extracts the first argument
+ ? Awaited // recursively unwrap the value
+ : never // the argument to `then` was not callable
+ : T // non-object or non-thenable
+
export default class CommonUtils {
public static sleep = (ms: number): Promise => {
return new Promise(resolve => setTimeout(resolve, ms))
}
- public static async retry(times: number, interval: number, callback: () => T): Promise {
+ public static async retry(times: number, interval: number, callback: () => T): Promise> {
let retryTime = 0
while (++retryTime < times) {
try {
- return await callback()
+ return await (callback() as Awaited)
} catch (err) {
logger.warn(`function call error: ${err}, retry ${retryTime + 1} ...`)
await CommonUtils.sleep(interval)
}
}
- return await callback()
+ return await (callback() as Awaited)
}
public static timeout(time: number, promise: Promise, value: T): Promise {
diff --git a/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts
index 3bce649f07..42a6c32de7 100644
--- a/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts
+++ b/packages/neuron-wallet/tests/services/tx/transaction-sender.test.ts
@@ -146,6 +146,10 @@ jest.doMock('utils/ckb-rpc.ts', () => ({
}
}))
+jest.doMock('services/cells', () => ({
+ getLiveCell: stubbedGetLiveCell
+}))
+
import Transaction from '../../../src/models/chain/transaction'
import TxStatus from '../../../src/models/chain/tx-status'
import CellDep, { DepType } from '../../../src/models/chain/cell-dep'
@@ -681,7 +685,7 @@ describe('TransactionSender Test', () => {
const feeRate = '10'
const fakeDepositOutPoint = OutPoint.fromObject({ txHash: '0x' + '0'.repeat(64), index: '0x0' })
beforeEach(async () => {
- stubbedGetLiveCell.mockResolvedValue(fakeCellWithStatus)
+ stubbedGetLiveCell.mockResolvedValue(fakeCellWithStatus.cell!.output)
stubbedGetTransaction.mockResolvedValue(fakeTx1)
stubbedGetNextAddress.mockResolvedValue({ address: fakeAddress1 })
await transactionSender.generateWithdrawMultiSignTx(fakeWallet.id, fakeDepositOutPoint, fee, feeRate)
@@ -710,7 +714,7 @@ describe('TransactionSender Test', () => {
const fee = '1'
const feeRate = '10'
beforeEach(async () => {
- stubbedGetLiveCell.mockResolvedValue(fakeCellWithStatus)
+ stubbedGetLiveCell.mockResolvedValue(fakeCellWithStatus.cell!.output)
stubbedGetTransaction.mockResolvedValue(fakeTx1)
stubbedGetHeader.mockResolvedValue(fakeDepositBlockHeader)
stubbedGetNextChangeAddress.mockReturnValue({
@@ -736,7 +740,10 @@ describe('TransactionSender Test', () => {
let tx: any
beforeEach(async () => {
- stubbedGetLiveCell.mockResolvedValue(fakeCellWithStatus)
+ const output = fakeCellWithStatus.cell!.output
+ output.daoData = '0x6400000000000000'
+ output.setDepositOutPoint(new OutPoint(`0x${'0'.repeat(64)}`, '0x0'))
+ stubbedGetLiveCell.mockResolvedValue(output)
stubbedGetTransaction.mockResolvedValue(fakeTx1)
stubbedGetBlockByNumber.mockResolvedValue({
header: { hash: '0x92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5' },
diff --git a/yarn.lock b/yarn.lock
index 72745e06c0..d975b02f9d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1279,6 +1279,20 @@
dependencies:
jsbi "^4.1.0"
+"@ckb-lumos/bi@0.19.0":
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/@ckb-lumos/bi/-/bi-0.19.0.tgz#ccd7e9e32e58eec7effc78cd171eb79f0f858d4a"
+ integrity sha512-+EFkUOqCtIwilAfrd680wEeMTXME9Wjjyl436+0x32jlbfo4sa+iNqwPaTjUQ2/SDwsuqK3eB+DntYxxFxoEtw==
+ dependencies:
+ jsbi "^4.1.0"
+
+"@ckb-lumos/codec@0.19.0":
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/@ckb-lumos/codec/-/codec-0.19.0.tgz#c9cda5e37fd5591abeaac9d5ed9577cfad206c09"
+ integrity sha512-MAts5rm5xLcvPslW6HwU/TlRoJ3jTtgsdtyakIa7qQo+0DlHdV6BOxTNDPtP3yqp8p5YFef7uCi/rcpi/KOGwA==
+ dependencies:
+ "@ckb-lumos/bi" "^0.19.0"
+
"@ckb-lumos/rpc@0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@ckb-lumos/rpc/-/rpc-0.18.0.tgz#3858b5eaa4b06a90d4a0931cc3168fab835f4a0c"
From e440f1b34e1e485a5151c52199ba1f7102c9d52d Mon Sep 17 00:00:00 2001
From: yanguoyu <841185308@qq.com>
Date: Fri, 21 Apr 2023 10:11:56 +0800
Subject: [PATCH 08/17] fix: When use light client, ignore show method not
found error.
---
packages/neuron-ui/src/utils/const.ts | 1 +
.../src/utils/hooks/useGetCountDownAndFeeRateStats.ts | 10 +++++++++-
packages/neuron-wallet/src/services/node.ts | 2 +-
3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts
index 99a29d2846..ea00407a7d 100644
--- a/packages/neuron-ui/src/utils/const.ts
+++ b/packages/neuron-ui/src/utils/const.ts
@@ -66,3 +66,4 @@ export const DEPRECATED_CODE_HASH: Record = {
export const LIGHT_CLIENT_TESTNET = 'light_client_testnet'
export const LIGHT_NETWORK_TYPE = 2
+export const METHOD_NOT_FOUND = -32601
diff --git a/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts b/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts
index 43d6b15f0f..badeeecc20 100644
--- a/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts
+++ b/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts
@@ -1,7 +1,7 @@
import { useState, useEffect, useCallback } from 'react'
import { getFeeRateStats } from 'services/chain'
import { AppActions, StateDispatch, useDispatch } from 'states'
-import { MEDIUM_FEE_RATE } from 'utils/const'
+import { MEDIUM_FEE_RATE, METHOD_NOT_FOUND } from 'utils/const'
type CountdownOptions = {
seconds?: number
@@ -27,6 +27,14 @@ const useGetCountDownAndFeeRateStats = ({ seconds = 30, interval = 1000 }: Count
setFeeFatestatsData(states => ({ ...states, ...res, suggestFeeRate: suggested }))
})
.catch((err: Error) => {
+ try {
+ const errMsg = JSON.parse(err.message)
+ if (errMsg?.code === METHOD_NOT_FOUND) {
+ return
+ }
+ } catch (error) {
+ //ignore, show error default
+ }
stateDispatch({
type: AppActions.AddNotification,
payload: {
diff --git a/packages/neuron-wallet/src/services/node.ts b/packages/neuron-wallet/src/services/node.ts
index 5dd1239e26..fb4725696e 100644
--- a/packages/neuron-wallet/src/services/node.ts
+++ b/packages/neuron-wallet/src/services/node.ts
@@ -222,7 +222,7 @@ class NodeService {
private async verifyNodeVersion() {
const network = NetworksService.getInstance().getCurrent()
- const localNodeInfo = await new RpcService(network.remote).getLocalNodeInfo()
+ const localNodeInfo = await new RpcService(network.remote).localNodeInfo()
const internalNodeVersion = this.getInternalNodeVersion()
const [internalMajor, internalMinor] = internalNodeVersion?.split('.') ?? []
const [externalMajor, externalMinor] = localNodeInfo.version?.split('.') ?? []
From d6be73c031faaaf83ba1de1ea28a49ddd8a0ae05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com>
Date: Wed, 26 Apr 2023 07:02:25 +0800
Subject: [PATCH 09/17] fix: Fix test case (#2650)
* fix: Fix test case
* fix: Merge test case fix into a branch.
---
.../light-connector.test.ts | 26 ++++++++++++-------
.../tests/services/indexer.test.ts | 2 +-
.../neuron-wallet/tests/services/node.test.ts | 10 ++++---
3 files changed, 24 insertions(+), 14 deletions(-)
diff --git a/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts
index e4ab7fe851..5ebffb5634 100644
--- a/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts
+++ b/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts
@@ -17,6 +17,7 @@ const setScriptsMock = jest.fn()
const getScriptsMock = jest.fn()
const getTipHeaderMock = jest.fn()
const getTransactionsMock = jest.fn()
+const createBatchRequestMock = jest.fn()
const schedulerWaitMock = jest.fn()
const getMultisigConfigForLightMock = jest.fn()
@@ -35,6 +36,7 @@ function mockReset() {
getScriptsMock.mockReset()
getTipHeaderMock.mockReset()
getTransactionsMock.mockReset()
+ createBatchRequestMock.mockReset()
schedulerWaitMock.mockReset()
getMultisigConfigForLightMock.mockReset()
@@ -63,6 +65,7 @@ jest.mock('../../src/utils/ckb-rpc', () => ({
getScripts: getScriptsMock,
getTipHeader: getTipHeaderMock,
getTransactions: getTransactionsMock,
+ createBatchRequest: () => ({ exec: createBatchRequestMock }),
}
}
}))
@@ -97,6 +100,7 @@ const address = 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2q8ux
describe('test light connector', () => {
beforeEach(() => {
walletGetAllMock.mockReturnValue([])
+ createBatchRequestMock.mockResolvedValue([])
})
afterEach(() => {
mockReset()
@@ -401,29 +405,31 @@ describe('test light connector', () => {
beforeEach(() => {
mockFn.mockReset()
})
- it ('connect success', async () => {
+ it('connect success', async () => {
const connect = new LightConnector([], '')
//@ts-ignore
connect.initSync = mockFn
await connect.connect()
expect(mockFn).toBeCalledTimes(1)
})
- it ('connect failed', async () => {
+ it('connect failed', async () => {
const connect = new LightConnector([], '')
//@ts-ignore
connect.initSync = mockFn
- mockFn.mockRejectedValue('error')
- expect(connect.connect()).rejects.toThrowError('error')
+ mockFn.mockImplementation(() => { throw new Error('error') })
+ expect(connect.connect()).rejects.toThrowError(new Error('error'))
})
})
describe('test stop', () => {
- const connect = new LightConnector([], '')
- //@ts-ignore
- connect.pollingIndexer = true
- connect.stop()
- //@ts-ignore
- expect(connect.pollingIndexer).toBeFalsy()
+ it('test stop', () => {
+ const connect = new LightConnector([], '')
+ //@ts-ignore
+ connect.pollingIndexer = true
+ connect.stop()
+ //@ts-ignore
+ expect(connect.pollingIndexer).toBeFalsy()
+ })
})
describe('test notifyCurrentBlockNumberProcessed', () => {
diff --git a/packages/neuron-wallet/tests/services/indexer.test.ts b/packages/neuron-wallet/tests/services/indexer.test.ts
index 5673648fab..2165537543 100644
--- a/packages/neuron-wallet/tests/services/indexer.test.ts
+++ b/packages/neuron-wallet/tests/services/indexer.test.ts
@@ -40,7 +40,7 @@ jest.mock('../../src/models/synced-block-number', () => ({
}))
jest.mock('../../src/database/chain', () => ({
-
+ clean: () => jest.fn()
}))
jest.mock('../../src/services/monitor', () => {
diff --git a/packages/neuron-wallet/tests/services/node.test.ts b/packages/neuron-wallet/tests/services/node.test.ts
index 5eac0ca5e8..4c80e11d9d 100644
--- a/packages/neuron-wallet/tests/services/node.test.ts
+++ b/packages/neuron-wallet/tests/services/node.test.ts
@@ -145,7 +145,7 @@ describe('NodeService', () => {
return function() {
return {
getChain: getChainMock,
- getLocalNodeInfo: getLocalNodeInfoMock
+ localNodeInfo: getLocalNodeInfoMock
}
}
})
@@ -231,7 +231,7 @@ describe('NodeService', () => {
describe('when node starts', () => {
beforeEach(async () => {
stubbedStartCKBNode.mockResolvedValue(true)
- await nodeService.tryStartNodeOnDefaultURI()
+ await nodeService.startNode()
jest.advanceTimersByTime(1000)
});
@@ -279,7 +279,7 @@ describe('NodeService', () => {
describe('when node failed to start', () => {
beforeEach(async () => {
stubbedStartCKBNode.mockRejectedValue(new Error())
- await nodeService.tryStartNodeOnDefaultURI()
+ await nodeService.startNode()
});
it('logs error', () => {
expect(stubbedLoggerInfo).toHaveBeenCalledWith('CKB: fail to start bundled CKB with error:')
@@ -295,6 +295,9 @@ describe('NodeService', () => {
})
});
describe('start light node', () => {
+ beforeEach(() => {
+ stubbedNetworsServiceGet.mockReset()
+ })
it('start light node', async () => {
stubbedNetworsServiceGet.mockReturnValueOnce({type: NetworkType.Light})
await nodeService.startNode()
@@ -333,6 +336,7 @@ describe('NodeService', () => {
})
stubbedStartCKBNode.mockResolvedValue(true)
stubbedNetworsServiceGet.mockReturnValue({remote: bundledNodeUrl})
+ getLocalNodeInfoMock.mockRejectedValue('not start')
await nodeService.tryStartNodeOnDefaultURI()
await eventCallback({currentNetworkID: 'network1'})
From 97c9bc0b0f1eb4be308c1b3a47ab29e8cdb17e77 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com>
Date: Fri, 28 Apr 2023 17:21:46 +0800
Subject: [PATCH 10/17] fix: Show some tips about light client. (#2642)
---
.../src/components/SUDTSend/index.tsx | 17 ++++++++++++-
.../components/SUDTSend/sUDTSend.module.scss | 14 +++++++++++
.../neuron-ui/src/components/Send/index.tsx | 1 +
.../src/components/SendFieldset/index.tsx | 4 ++-
.../SendFromMultisigDialog/index.tsx | 1 +
packages/neuron-ui/src/locales/en.json | 4 ++-
packages/neuron-ui/src/locales/zh-tw.json | 4 ++-
packages/neuron-ui/src/locales/zh.json | 4 ++-
.../src/stories/SendFieldset.stories.tsx | 1 +
.../hooks/useGetCountDownAndFeeRateStats.ts | 10 +++++---
packages/neuron-wallet/electron-builder.yml | 2 +-
.../neuron-wallet/src/controllers/app/menu.ts | 25 +++++++++++++------
.../neuron-wallet/src/controllers/wallets.ts | 10 +++++++-
.../src/exceptions/anyone-can-pay.ts | 6 +++++
packages/neuron-wallet/src/locales/en.ts | 6 +++--
packages/neuron-wallet/src/locales/zh-tw.ts | 6 +++--
packages/neuron-wallet/src/locales/zh.ts | 6 +++--
.../src/services/anyone-can-pay.ts | 7 +++++-
18 files changed, 102 insertions(+), 26 deletions(-)
diff --git a/packages/neuron-ui/src/components/SUDTSend/index.tsx b/packages/neuron-ui/src/components/SUDTSend/index.tsx
index 34afa7c3ec..02a2b378b0 100644
--- a/packages/neuron-ui/src/components/SUDTSend/index.tsx
+++ b/packages/neuron-ui/src/components/SUDTSend/index.tsx
@@ -11,6 +11,7 @@ import Button from 'widgets/Button'
import Spinner from 'widgets/Spinner'
import { ReactComponent as TooltipIcon } from 'widgets/Icons/Tooltip.svg'
import { ReactComponent as Attention } from 'widgets/Icons/Attention.svg'
+import { ReactComponent as WarningAttention } from 'widgets/Icons/ExperimentalAttention.svg'
import { getSUDTAccount, destoryAssetAccount } from 'services/remote'
import { useState as useGlobalState, useDispatch, AppActions } from 'states'
import {
@@ -29,7 +30,15 @@ import {
} from 'utils'
import { AmountNotEnoughException, isErrorWithI18n } from 'exceptions'
import { UANTokenName, UANTonkenSymbol } from 'components/UANDisplay'
-import { AddressLockType, getGenerator, useAddressLockType, useOnSumbit, useOptions, useSendType } from './hooks'
+import {
+ AddressLockType,
+ SendType,
+ getGenerator,
+ useAddressLockType,
+ useOnSumbit,
+ useOptions,
+ useSendType,
+} from './hooks'
import styles from './sUDTSend.module.scss'
const { INIT_SEND_PRICE, DEFAULT_SUDT_FIELDS } = CONSTANTS
@@ -405,6 +414,12 @@ const SUDTSend = () => {
) : null}
+ {(v.key === SendType.secp256Cheque && !isMainnet) ? (
+
+
+ {t('messages.light-client-cheque-warning')}
+
+ ) : null}
))}
{!isOptionCorrect && {t('s-udt.send.select-option')} }
diff --git a/packages/neuron-ui/src/components/SUDTSend/sUDTSend.module.scss b/packages/neuron-ui/src/components/SUDTSend/sUDTSend.module.scss
index 75db9cfa1d..4a09dd1132 100644
--- a/packages/neuron-ui/src/components/SUDTSend/sUDTSend.module.scss
+++ b/packages/neuron-ui/src/components/SUDTSend/sUDTSend.module.scss
@@ -178,6 +178,20 @@
}
}
}
+
+ .chequeWarning {
+ padding: 0 8px;
+ margin-left: 8px;
+ background-color: rgb(255, 244, 206);
+ color: rgb(50, 49, 48);
+
+ & > svg {
+ width: 14px;
+ height: 14px;
+ margin-right: 4px;
+ transform: translateY(20%);
+ }
+ }
}
.cheque {
grid-area: option-0;
diff --git a/packages/neuron-ui/src/components/Send/index.tsx b/packages/neuron-ui/src/components/Send/index.tsx
index 8127d2ae24..b0bf55e914 100644
--- a/packages/neuron-ui/src/components/Send/index.tsx
+++ b/packages/neuron-ui/src/components/Send/index.tsx
@@ -177,6 +177,7 @@ const Send = () => {
onSendMaxClick={handleSendMaxClick}
onLocktimeClick={handleLocktimeClick}
isTimeLockable={!device}
+ isMainnet={isMainnet}
/>
)
}}
diff --git a/packages/neuron-ui/src/components/SendFieldset/index.tsx b/packages/neuron-ui/src/components/SendFieldset/index.tsx
index a7e85df255..6f937ee9af 100644
--- a/packages/neuron-ui/src/components/SendFieldset/index.tsx
+++ b/packages/neuron-ui/src/components/SendFieldset/index.tsx
@@ -36,6 +36,7 @@ interface SendSubformProps {
onLocktimeClick?: React.EventHandler>
onSendMaxClick?: React.EventHandler>
onItemChange: React.EventHandler>
+ isMainnet: boolean
}
const SendFieldset = ({
@@ -54,6 +55,7 @@ const SendFieldset = ({
onSendMaxClick,
onItemChange,
isTimeLockable = true,
+ isMainnet,
}: SendSubformProps) => {
const [t] = useTranslation()
@@ -162,7 +164,7 @@ const SendFieldset = ({
{item.date && (
- {t('send.locktime-warning')}
+ {t('send.locktime-warning', { extraNote: isMainnet ? null : t('messages.light-client-locktime-warning') })}
)}
diff --git a/packages/neuron-ui/src/components/SendFromMultisigDialog/index.tsx b/packages/neuron-ui/src/components/SendFromMultisigDialog/index.tsx
index c682178a75..8722c071d9 100644
--- a/packages/neuron-ui/src/components/SendFromMultisigDialog/index.tsx
+++ b/packages/neuron-ui/src/components/SendFromMultisigDialog/index.tsx
@@ -102,6 +102,7 @@ const SendFromMultisigDialog = ({
onOutputRemove={deleteSendInfo}
onItemChange={onSendInfoChange}
onSendMaxClick={onSendMaxClick}
+ isMainnet={isMainnet}
/>
))}
diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json
index 5f69b570b7..e86364e470 100644
--- a/packages/neuron-ui/src/locales/en.json
+++ b/packages/neuron-ui/src/locales/en.json
@@ -217,7 +217,7 @@
"set-locktime": "Set Locktime",
"locktime-notice-content": "According to the actual running block height, there may be some time variances in locktime.",
"release-on": "Release on",
- "locktime-warning": "Please ensure that receiver's wallet can support expiration unlocking. In general, exchanges do not support expiration unlocking, please use with caution!"
+ "locktime-warning": "Please ensure that receiver's wallet can support expiration unlocking. (Note: 1.Exchanges generally do not support expiration unlocking {{extraNote}})"
},
"receive": {
"title": "Receive",
@@ -510,6 +510,8 @@
"migrate-warning": "Warning: The migration process may fail for unknown reasons resulting in resynchronization, please back up manually and start the migration!",
"migrate": "Migrate",
"secp256k1/blake160-address-required": "Secp256k1/blake160 address is required",
+ "light-client-locktime-warning": "2.Light client mode doesn't support showing lock-time CKBytes.",
+ "light-client-cheque-warning": "Warning: Light client mode doesn't support showing Cheque assets.",
"fields": {
"wallet": "Wallet",
"name": "Name",
diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json
index 94ff67282b..e5007ab0e0 100644
--- a/packages/neuron-ui/src/locales/zh-tw.json
+++ b/packages/neuron-ui/src/locales/zh-tw.json
@@ -210,7 +210,7 @@
"set-locktime": "設置鎖定時間",
"locktime-notice-content": "鎖定時間根據區塊鏈實際運行情況會有一定的誤差。",
"release-on": "鎖定至",
- "locktime-warning": "請確保對方錢包能夠支持到期解鎖功能。一般情況下,交易所不支持到期解鎖功能,請謹慎使用!"
+ "locktime-warning": "請確保對方錢包支持到期解鎖功能。(註:1.交易所壹般不支持到期解鎖功能 {{extraNote}})"
},
"receive": {
"title": "收款",
@@ -502,6 +502,8 @@
"migrate-warning": "註意:遷移過程中可能由於未知原因失敗導致需要重新同步,請備份完成後開始遷移!",
"migrate": "遷移",
"secp256k1/blake160-address-required": "請輸入 secp256k1/blake160 地址",
+ "light-client-locktime-warning": "2.輕節點模式不支持展示到期解鎖的 CKBytes。",
+ "light-client-cheque-warning": "註意: 輕節點模式不支持展示 Cheque 資產。",
"fields": {
"wallet": "錢包",
"name": "名稱",
diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json
index f16bd564e6..48d946ff24 100644
--- a/packages/neuron-ui/src/locales/zh.json
+++ b/packages/neuron-ui/src/locales/zh.json
@@ -210,7 +210,7 @@
"set-locktime": "设置锁定时间",
"locktime-notice-content": "锁定时间根据区块链实际运行情况会有一定的误差。",
"release-on": "锁定至",
- "locktime-warning": "请确保对方钱包能够支持到期解锁功能。一般情况下,交易所不支持到期解锁功能,请谨慎使用!"
+ "locktime-warning": "请确保对方钱包支持到期解锁功能。(注:1.交易所一般不支持到期解锁功能 {{extraNote}})"
},
"receive": {
"title": "收款",
@@ -503,6 +503,8 @@
"migrate-warning": "注意:迁移过程中可能由于未知原因失败导致需要重新同步,请备份完成后开始迁移!",
"migrate": "迁移",
"secp256k1/blake160-address-required": "请输入 secp256k1/blake160 地址",
+ "light-client-locktime-warning": "2.轻节点模式不支持展示到期解锁的 CKBytes。",
+ "light-client-cheque-warning": "注意: 轻节点模式不支持展示 Cheque 资产。",
"fields": {
"wallet": "钱包",
"name": "名称",
diff --git a/packages/neuron-ui/src/stories/SendFieldset.stories.tsx b/packages/neuron-ui/src/stories/SendFieldset.stories.tsx
index 290d745393..8f19d4e767 100644
--- a/packages/neuron-ui/src/stories/SendFieldset.stories.tsx
+++ b/packages/neuron-ui/src/stories/SendFieldset.stories.tsx
@@ -30,6 +30,7 @@ stories.add('Common', () => {
onItemChange: (e: any) => action('Item Change')(JSON.stringify(e.target.dataset), e.target.value),
onScan: () => action('Scan'),
onSendMaxClick: (e: any) => action('Click Send Max button')(JSON.stringify(e.target.dataset)),
+ isMainnet: false
}
return
})
diff --git a/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts b/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts
index badeeecc20..5d93e9dcd8 100644
--- a/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts
+++ b/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts
@@ -21,10 +21,12 @@ const useGetCountDownAndFeeRateStats = ({ seconds = 30, interval = 1000 }: Count
(stateDispatch: StateDispatch) => {
getFeeRateStats()
.then(res => {
- const { mean, median } = res
- const suggested = mean && median ? Math.max(1000, Number(mean), Number(median)) : MEDIUM_FEE_RATE
-
- setFeeFatestatsData(states => ({ ...states, ...res, suggestFeeRate: suggested }))
+ if (res) {
+ const { mean, median } = res
+ const suggested = mean && median ? Math.max(1000, Number(mean), Number(median)) : MEDIUM_FEE_RATE
+
+ setFeeFatestatsData(states => ({ ...states, ...res, suggestFeeRate: suggested }))
+ }
})
.catch((err: Error) => {
try {
diff --git a/packages/neuron-wallet/electron-builder.yml b/packages/neuron-wallet/electron-builder.yml
index d369c6f539..6cc88df861 100644
--- a/packages/neuron-wallet/electron-builder.yml
+++ b/packages/neuron-wallet/electron-builder.yml
@@ -13,7 +13,7 @@ afterSign: scripts/notarize.js
files:
- from: "../.."
to: "."
- filter: ["!**/*", ".ckb-version", "ormconfig.json"]
+ filter: ["!**/*", ".ckb-version", ".ckb-light-version", "ormconfig.json"]
- package.json
- dist
- ".env"
diff --git a/packages/neuron-wallet/src/controllers/app/menu.ts b/packages/neuron-wallet/src/controllers/app/menu.ts
index 6c83e50f5f..43966829ed 100644
--- a/packages/neuron-wallet/src/controllers/app/menu.ts
+++ b/packages/neuron-wallet/src/controllers/app/menu.ts
@@ -40,19 +40,28 @@ const separator: MenuItemConstructorOptions = {
type: 'separator'
}
-const showAbout = () => {
- let applicationVersion = t('about.app-version', { name: app.name, version: app.getVersion() })
-
- const appPath = app.isPackaged ? app.getAppPath() : path.join(__dirname, '../../../../..')
- const ckbVersionPath = path.join(appPath, '.ckb-version')
- if (fs.existsSync(ckbVersionPath)) {
+const getVerionFromFile = (filePath: string) => {
+ if (fs.existsSync(filePath)) {
try {
- const ckbVersion = fs.readFileSync(ckbVersionPath, 'utf8')
- applicationVersion += `\n${t('about.ckb-client-version', { version: ckbVersion })}`
+ return fs.readFileSync(filePath, 'utf8')
} catch (err) {
logger.error(`[Menu]: `, err)
}
}
+}
+
+const showAbout = () => {
+ let applicationVersion = t('about.app-version', { name: app.name, version: app.getVersion() })
+
+ const appPath = app.isPackaged ? app.getAppPath() : path.join(__dirname, '../../../../..')
+ const ckbVersion = getVerionFromFile(path.join(appPath, '.ckb-version'))
+ if (ckbVersion) {
+ applicationVersion += `\n${t('about.ckb-client-version', { version: ckbVersion })}`
+ }
+ const ckbLightClientVersion = getVerionFromFile(path.join(appPath, '.ckb-light-version'))
+ if (ckbLightClientVersion) {
+ applicationVersion += `${t('about.ckb-light-client-version', { version: ckbLightClientVersion })}`
+ }
const isWin = process.platform === 'win32'
diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts
index 7208a31440..fcb68a2cdc 100644
--- a/packages/neuron-wallet/src/controllers/wallets.ts
+++ b/packages/neuron-wallet/src/controllers/wallets.ts
@@ -119,12 +119,20 @@ export default class WalletsController {
const walletsService = WalletsService.getInstance()
const rpc = generateRPC(NodeService.getInstance().nodeUrl)
+ let startBlockNumberInLight: string | undefined = undefined
+ if (!isImporting) {
+ try {
+ startBlockNumberInLight = await rpc.getTipBlockNumber()
+ } catch (error) {
+ startBlockNumberInLight = undefined
+ }
+ }
const wallet = walletsService.create({
id: '',
name,
extendedKey: accountExtendedPublicKey.serialize(),
keystore,
- startBlockNumberInLight: isImporting ? undefined : await rpc.getTipBlockNumber()
+ startBlockNumberInLight,
})
wallet.checkAndGenerateAddresses(isImporting)
diff --git a/packages/neuron-wallet/src/exceptions/anyone-can-pay.ts b/packages/neuron-wallet/src/exceptions/anyone-can-pay.ts
index d4e26c7d47..db0dce4527 100644
--- a/packages/neuron-wallet/src/exceptions/anyone-can-pay.ts
+++ b/packages/neuron-wallet/src/exceptions/anyone-can-pay.ts
@@ -30,6 +30,12 @@ export class SudtAcpHaveDataError extends Error {
}
}
+export class LightClientNotSupportSendToACPError extends Error {
+ constructor() {
+ super(t('messages.light-client-sudt-acp-error'))
+ }
+}
+
export default {
TargetOutputNotFoundError,
AcpSendSameAccountError
diff --git a/packages/neuron-wallet/src/locales/en.ts b/packages/neuron-wallet/src/locales/en.ts
index d7ab1af0ce..323a349674 100644
--- a/packages/neuron-wallet/src/locales/en.ts
+++ b/packages/neuron-wallet/src/locales/en.ts
@@ -134,7 +134,8 @@ export default {
'sudt-acp-have-data': 'The destroying sUDT acp account have amount',
'no-match-address-for-sign': 'Not found matched address',
'target-lock-error': 'CKB asset account can only transfer to sepe256k1 or acp address',
- 'no-exist-ckb-node-data': '{{path}} has no CKB Node config and storage, press ok to synchronize from scratch'
+ 'no-exist-ckb-node-data': '{{path}} has no CKB Node config and storage, press ok to synchronize from scratch',
+ 'light-client-sudt-acp-error': "Light client mode doesn't support sending assets to other's asset account"
},
messageBox: {
button: {
@@ -246,7 +247,8 @@ export default {
},
about: {
'app-version': '{{name}} Version: {{version}}',
- 'ckb-client-version': 'CKB Client Version: {{version}}'
+ 'ckb-client-version': 'CKB Client Version: {{version}}',
+ 'ckb-light-client-version': 'CKB Light Client Version: {{version}}'
},
settings: {
title: {
diff --git a/packages/neuron-wallet/src/locales/zh-tw.ts b/packages/neuron-wallet/src/locales/zh-tw.ts
index f2d0a7f90f..64f7ca11ed 100644
--- a/packages/neuron-wallet/src/locales/zh-tw.ts
+++ b/packages/neuron-wallet/src/locales/zh-tw.ts
@@ -124,7 +124,8 @@ export default {
'sudt-acp-have-data': '待銷毀的 sUDT 賬戶資產不為 0',
'no-match-address-for-sign': '没有找到匹配的地址',
'target-lock-error': 'CKB 資產只能轉賬到 secp256k1 或者 acp 地址',
- 'no-exist-ckb-node-data': '{{path}} 目錄下沒有找到 CKB Node 配置和數據, 點擊繼續重新同步'
+ 'no-exist-ckb-node-data': '{{path}} 目錄下沒有找到 CKB Node 配置和數據, 點擊繼續重新同步',
+ 'light-client-sudt-acp-error': '輕節點模式不支持發送資產給其他用戶的資產賬戶'
},
messageBox: {
button: {
@@ -234,7 +235,8 @@ export default {
},
about: {
'app-version': '{{name}} 版本: {{version}}',
- 'ckb-client-version': 'CKB 節點版本: {{version}}'
+ 'ckb-client-version': 'CKB 節點版本: {{version}}',
+ 'ckb-light-client-version': 'CKB 輕節點版本: {{version}}'
},
settings: {
title: {
diff --git a/packages/neuron-wallet/src/locales/zh.ts b/packages/neuron-wallet/src/locales/zh.ts
index 708e03bfa8..289e2fc1cf 100644
--- a/packages/neuron-wallet/src/locales/zh.ts
+++ b/packages/neuron-wallet/src/locales/zh.ts
@@ -125,7 +125,8 @@ export default {
'sudt-acp-have-data': '待销毁的 sUDT 账户资产不为 0',
'no-match-address-for-sign': '没有找到匹配的地址',
'target-lock-error': 'CKB 资产只能转账到 secp256k1 或者 acp 地址',
- 'no-exist-ckb-node-data': '{{path}} 目录下没有找到 CKB Node 配置和数据, 点击继续重新同步'
+ 'no-exist-ckb-node-data': '{{path}} 目录下没有找到 CKB Node 配置和数据, 点击继续重新同步',
+ 'light-client-sudt-acp-error': '轻节点模式不支持发送资产给其他用户的资产账户'
},
messageBox: {
button: {
@@ -235,7 +236,8 @@ export default {
},
about: {
'app-version': '{{name}} 版本: {{version}}',
- 'ckb-client-version': 'CKB 节点版本: {{version}}'
+ 'ckb-client-version': 'CKB 节点版本: {{version}}',
+ 'ckb-light-client-version': 'CKB 轻节点版本: {{version}}'
},
settings: {
title: {
diff --git a/packages/neuron-wallet/src/services/anyone-can-pay.ts b/packages/neuron-wallet/src/services/anyone-can-pay.ts
index a0ab6cf10d..ca19735758 100644
--- a/packages/neuron-wallet/src/services/anyone-can-pay.ts
+++ b/packages/neuron-wallet/src/services/anyone-can-pay.ts
@@ -6,7 +6,7 @@ import Output from 'models/chain/output'
import LiveCell from 'models/chain/live-cell'
import Transaction from 'models/chain/transaction'
import AssetAccountEntity from 'database/chain/entities/asset-account'
-import { TargetLockError, TargetOutputNotFoundError } from 'exceptions'
+import { LightClientNotSupportSendToACPError, TargetLockError, TargetOutputNotFoundError } from 'exceptions'
import { AcpSendSameAccountError } from 'exceptions'
import Script from 'models/chain/script'
import OutPoint from 'models/chain/out-point'
@@ -15,6 +15,8 @@ import WalletService from './wallets'
import SystemScriptInfo from 'models/system-script-info'
import CellsService from './cells'
import { MIN_SUDT_CAPACITY } from 'utils/const'
+import NetworksService from './networks'
+import { NetworkType } from 'models/network'
export default class AnyoneCanPayService {
public static async generateAnyoneCanPayTx(
@@ -90,6 +92,9 @@ export default class AnyoneCanPayService {
)
if (new AssetAccountInfo().isAnyoneCanPayScript(lockScript)) {
if (!targetOutputLiveCell) {
+ if (NetworksService.getInstance().getCurrent().type === NetworkType.Light) {
+ throw new LightClientNotSupportSendToACPError()
+ }
throw new TargetOutputNotFoundError()
}
return Output.fromObject({
From beea565872a3852a23a40086234bd3c7abebac9e Mon Sep 17 00:00:00 2001
From: yanguoyu <841185308@qq.com>
Date: Fri, 5 May 2023 11:20:30 +0800
Subject: [PATCH 11/17] fix: Update light client version. Disabled input when
migrate sudt.
---
.ckb-light-version | 2 +-
.../components/SUDTMigrateToExistAccountDialog/index.tsx | 3 +++
.../neuron-ui/src/components/SpecialAssetList/index.tsx | 6 ++++++
packages/neuron-ui/src/widgets/InputSelect/index.tsx | 5 +++--
4 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/.ckb-light-version b/.ckb-light-version
index f0cfd3bb67..576b77719a 100644
--- a/.ckb-light-version
+++ b/.ckb-light-version
@@ -1 +1 @@
-v0.2.2
+v0.2.3
diff --git a/packages/neuron-ui/src/components/SUDTMigrateToExistAccountDialog/index.tsx b/packages/neuron-ui/src/components/SUDTMigrateToExistAccountDialog/index.tsx
index 06082136ca..2b27e7a47d 100644
--- a/packages/neuron-ui/src/components/SUDTMigrateToExistAccountDialog/index.tsx
+++ b/packages/neuron-ui/src/components/SUDTMigrateToExistAccountDialog/index.tsx
@@ -17,6 +17,7 @@ const SUDTMigrateToExistAccountDialog = ({
sUDTAccounts,
isMainnet,
walletID,
+ isLightClient,
}: {
cell: SpecialAssetCell
closeDialog: () => void
@@ -24,6 +25,7 @@ const SUDTMigrateToExistAccountDialog = ({
sUDTAccounts: State.SUDTAccount[]
isMainnet: boolean
walletID: string
+ isLightClient: boolean
}) => {
const [t] = useTranslation()
const [address, setAddress] = useState('')
@@ -92,6 +94,7 @@ const SUDTMigrateToExistAccountDialog = ({
onChange={onAddressChange}
value={address}
className={styles.addressInputSelect}
+ inputDisabeld={isLightClient}
/>
{addressError && {addressError} }
diff --git a/packages/neuron-ui/src/components/SpecialAssetList/index.tsx b/packages/neuron-ui/src/components/SpecialAssetList/index.tsx
index 43e170ac89..0d23056d66 100644
--- a/packages/neuron-ui/src/components/SpecialAssetList/index.tsx
+++ b/packages/neuron-ui/src/components/SpecialAssetList/index.tsx
@@ -36,6 +36,7 @@ import {
useGetAssetAccounts,
} from './hooks'
import styles from './specialAssetList.module.scss'
+import { LIGHT_NETWORK_TYPE } from 'utils/const'
const { PAGE_SIZE } = CONSTANTS
@@ -113,6 +114,10 @@ const SpecialAssetList = () => {
} = useGlobalState()
const { suggestFeeRate } = useGetCountDownAndFeeRateStats()
const isMainnet = isMainnetUtil(networks, networkID)
+ const isLightClient = useMemo(() => networks.find(n => n.id === networkID)?.type === LIGHT_NETWORK_TYPE, [
+ networkID,
+ networks,
+ ])
const foundTokenInfo = tokenInfoList.find(token => token.tokenID === accountToClaim?.account.tokenID)
const accountNames = useMemo(() => sUDTAccounts.filter(v => !!v.accountName).map(v => v.accountName!), [sUDTAccounts])
const updateAccountDialogProps: SUDTUpdateDialogProps | undefined = accountToClaim?.account
@@ -384,6 +389,7 @@ const SpecialAssetList = () => {
sUDTAccounts={sUDTAccounts}
isMainnet={isMainnet}
walletID={id}
+ isLightClient={isLightClient}
/>
)}
diff --git a/packages/neuron-ui/src/widgets/InputSelect/index.tsx b/packages/neuron-ui/src/widgets/InputSelect/index.tsx
index bc7728a98e..6341b30953 100644
--- a/packages/neuron-ui/src/widgets/InputSelect/index.tsx
+++ b/packages/neuron-ui/src/widgets/InputSelect/index.tsx
@@ -16,6 +16,7 @@ export interface InputSelectProps {
onChange?: (value: string, arg?: SelectOptions) => void
value?: string
placeholder?: string
+ inputDisabeld?: boolean
}
function parseValue(value: string, options: SelectOptions[]) {
@@ -23,7 +24,7 @@ function parseValue(value: string, options: SelectOptions[]) {
return option?.value || value
}
-const Select = ({ value, options, placeholder, disabled, onChange, className }: InputSelectProps) => {
+const Select = ({ value, options, placeholder, disabled, onChange, className, inputDisabeld }: InputSelectProps) => {
const mounted = useRef(true)
const root = useRef(null)
const openRef = useRef(false)
@@ -121,7 +122,7 @@ const Select = ({ value, options, placeholder, disabled, onChange, className }:
tabIndex={0}
data-open={openRef.current}
>
-
+
{openRef.current ? (
From d8012b20d2a499f92e08886c98dd236ce3cc2079 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com>
Date: Mon, 15 May 2023 13:42:41 +0800
Subject: [PATCH 12/17] fix: fix some bugs (#2659)
---
.../sync/light-connector.ts | 3 ---
.../src/block-sync-renderer/sync/queue.ts | 8 +++++--
.../neuron-wallet/src/database/chain/index.ts | 7 ++++---
.../neuron-wallet/src/services/indexer.ts | 2 +-
.../neuron-wallet/src/services/multisig.ts | 5 +++++
.../src/services/sync-progress.ts | 7 +++++++
packages/neuron-wallet/src/utils/ckb-rpc.ts | 8 ++++---
.../tests/block-sync-renderer/queue.test.ts | 21 ++++++++++++++-----
.../tests/controllers/multisig.test.ts | 9 ++++++++
.../tests/controllers/sync-api.test.ts | 3 +++
.../tests/services/light-runner.test.ts | 6 ++++++
11 files changed, 62 insertions(+), 17 deletions(-)
diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
index 40d13300a6..125b49e124 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
@@ -301,9 +301,6 @@ export default class LightConnector extends Connector {
}
async appendScript(scripts: AppendScript[]) {
- if (!scripts.length) {
- return
- }
this.initSyncProgress(scripts)
}
}
diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts
index 8d39e238ae..e08667a452 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts
@@ -10,6 +10,7 @@ import AssetAccountInfo from 'models/asset-account-info'
import { Address as AddressInterface } from "models/address"
import AddressParser from 'models/address-parser'
import Multisig from 'models/multisig'
+import BlockHeader from 'models/chain/block-header'
import TxAddressFinder from './tx-address-finder'
import IndexerConnector from './indexer-connector'
import IndexerCacheService from './indexer-cache-service'
@@ -134,8 +135,11 @@ export default class Queue {
blockHashes.map(v => ['getHeader', v])
).exec()
headers.forEach((blockHeader, idx) => {
- txs[idx].timestamp = blockHeader!.timestamp
- txs[idx].blockNumber = blockHeader!.number
+ if (blockHeader) {
+ const header = BlockHeader.fromSDK(blockHeader)
+ txs[idx].timestamp = header.timestamp
+ txs[idx].blockNumber = header.number
+ }
})
return txs
}
diff --git a/packages/neuron-wallet/src/database/chain/index.ts b/packages/neuron-wallet/src/database/chain/index.ts
index 5e29bfbffe..a0bde9834a 100644
--- a/packages/neuron-wallet/src/database/chain/index.ts
+++ b/packages/neuron-wallet/src/database/chain/index.ts
@@ -1,23 +1,24 @@
import { getConnection } from 'typeorm'
import MultisigOutputChangedSubject from 'models/subjects/multisig-output-db-changed-subject'
+import SyncProgressService from 'services/sync-progress'
import InputEntity from './entities/input'
import OutputEntity from './entities/output'
import TransactionEntity from './entities/transaction'
import SyncInfoEntity from './entities/sync-info'
import IndexerTxHashCache from './entities/indexer-tx-hash-cache'
import MultisigOutput from './entities/multisig-output'
-import SyncProgress from './entities/sync-progress'
/*
* Clean local sqlite storage
*/
export const clean = async () => {
await Promise.all([
- ...[InputEntity, OutputEntity, TransactionEntity, IndexerTxHashCache, MultisigOutput, SyncProgress].map(entity => {
+ ...[InputEntity, OutputEntity, TransactionEntity, IndexerTxHashCache, MultisigOutput].map(entity => {
return getConnection()
.getRepository(entity)
.clear()
- })
+ }),
+ SyncProgressService.clearCurrentWalletProgress()
])
MultisigOutputChangedSubject.getSubject().next('reset')
diff --git a/packages/neuron-wallet/src/services/indexer.ts b/packages/neuron-wallet/src/services/indexer.ts
index 7d70189f63..4f2f01837e 100644
--- a/packages/neuron-wallet/src/services/indexer.ts
+++ b/packages/neuron-wallet/src/services/indexer.ts
@@ -30,7 +30,7 @@ export default class IndexerService {
}
if (!NodeService.getInstance().isCkbNodeExternal) {
- await startMonitor('ckb')
+ await startMonitor('ckb', true)
}
}
diff --git a/packages/neuron-wallet/src/services/multisig.ts b/packages/neuron-wallet/src/services/multisig.ts
index 89012ff0ea..c45543df38 100644
--- a/packages/neuron-wallet/src/services/multisig.ts
+++ b/packages/neuron-wallet/src/services/multisig.ts
@@ -11,6 +11,7 @@ import NetworksService from './networks'
import Multisig from 'models/multisig'
import SyncProgress, { SyncAddressType } from 'database/chain/entities/sync-progress'
import { NetworkType } from 'models/network'
+import WalletService from './wallets'
const max64Int = '0x' + 'f'.repeat(16)
export default class MultisigService {
@@ -334,9 +335,13 @@ export default class MultisigService {
}
static async getMultisigConfigForLight() {
+ const currentWallet = WalletService.getInstance().getCurrent()
const multisigConfigs = await getConnection()
.getRepository(MultisigConfig)
.createQueryBuilder()
+ .where({
+ walletId: currentWallet?.id
+ })
.getMany()
return multisigConfigs.map(v => ({
walletId: v.walletId,
diff --git a/packages/neuron-wallet/src/services/sync-progress.ts b/packages/neuron-wallet/src/services/sync-progress.ts
index 98598d4c25..100a9458fe 100644
--- a/packages/neuron-wallet/src/services/sync-progress.ts
+++ b/packages/neuron-wallet/src/services/sync-progress.ts
@@ -108,4 +108,11 @@ export default class SyncProgressService {
.where({ hash: In(hashes) })
.getMany()
}
+
+ static async clearCurrentWalletProgress() {
+ const currentWallet = WalletService.getInstance().getCurrent()
+ await getConnection()
+ .getRepository(SyncProgress)
+ .delete({ walletId: currentWallet?.id })
+ }
}
diff --git a/packages/neuron-wallet/src/utils/ckb-rpc.ts b/packages/neuron-wallet/src/utils/ckb-rpc.ts
index e0f6acc08f..5e460d9a4e 100644
--- a/packages/neuron-wallet/src/utils/ckb-rpc.ts
+++ b/packages/neuron-wallet/src/utils/ckb-rpc.ts
@@ -91,7 +91,7 @@ const lightRPCProperties: Record[0]
paramsFormatters: [paramsFormatter.toHash],
resultFormatters: (result: { status: 'fetched' | 'fetching' | 'added' | 'not_found', data?: RPC.TransactionWithStatus }) => {
if (result.status === 'fetched' && result.data) {return resultFormatter.toTransactionWithStatus(result.data)}
- return null
+ return result
}
}
}
@@ -117,7 +117,7 @@ export class LightRPC extends Base {
) => Promise<{ lastCursor: HexString, txs: { txHash: HexString, txIndex: HexString, blockNumber: CKBComponents.BlockNumber }[]}>
getTransactionInLight: Base['getTransaction']
- fetchTransaction: (hash: string) => Promise
+ fetchTransaction: (hash: string) => Promise
getGenesisBlock: () => Promise
exceptionMethods = ['getCurrentEpoch', 'getEpochByNumber', 'getBlockHash', 'getLiveCell']
@@ -151,7 +151,9 @@ export class LightRPC extends Base {
if (!tx?.transaction) {
tx = await CommonUtils.retry(3, 100, async () => {
const tmp = await this.fetchTransaction(hash)
- if (tmp === null) {throw new Error('Not fetch the transaction current')}
+ if ('status' in tmp) {
+ throw new Error(`transaction ${hash} status: ${tmp.status}`)
+ }
return tmp
})
if (!tx) {throw new Error(`Fetch transaction tx failed, please try it later: ${hash}`)}
diff --git a/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts
index eb964cdee8..9ca3175cab 100644
--- a/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts
+++ b/packages/neuron-wallet/tests/block-sync-renderer/queue.test.ts
@@ -73,7 +73,8 @@ const generateFakeTx = (id: string, publicKeyHash: string = '0x') => {
lock: Script.fromObject({ hashType: ScriptHashType.Type, codeHash: '0x' + id.repeat(64), args: publicKeyHash })
})
]
- fakeTx.blockNumber = '1'
+ fakeTx.blockNumber = '0x1'
+ fakeTx.timestamp = '0x1880a3fa5bc'
const fakeTxWithStatus = {
transaction: fakeTx,
txStatus: new TxStatus('0x' + id.repeat(64), TxStatusType.Committed)
@@ -81,6 +82,14 @@ const generateFakeTx = (id: string, publicKeyHash: string = '0x') => {
return fakeTxWithStatus
}
+const fakeBlockHeader = {
+ version: '0x0',
+ epoch: '0x0',
+ hash: `0x${'0'.repeat(64)}`,
+ parentHash: `0x${'0'.repeat(64)}`,
+ timestamp: '0x0',
+ number: '0x0',
+}
describe('queue', () => {
let queue: Queue
const fakeNodeUrl = 'http://fakenode:8114'
@@ -216,7 +225,7 @@ describe('queue', () => {
stubbedGetTransactionFn.mockResolvedValue(fakeTxWithStatus1)
stubbedRPCCreateBatchRequestExecFn
.mockResolvedValueOnce(fakeTxs)
- .mockResolvedValueOnce(fakeTxs.map(v => ({ timestamp: v.transaction.timestamp, number: v.transaction.blockNumber })))
+ .mockResolvedValueOnce(fakeTxs.map(v => ({ ...fakeBlockHeader, timestamp: v.transaction.timestamp, number: v.transaction.blockNumber })))
stubbedTransactionsSubject.next({ txHashes: fakeTxs.map(v => v.transaction.hash), params: fakeTxs[0].transaction.blockNumber })
})
describe('when saving transactions is succeeded', () => {
@@ -226,7 +235,8 @@ describe('queue', () => {
const lockHashes = ['0x1f2615a8dde4e28ca736ff763c2078aff990043f4cbf09eb4b3a58a140a0862d']
const tx = Transaction.fromSDK(fakeTxWithStatus2.transaction.toSDK())
tx.blockHash = fakeTxWithStatus2.txStatus.blockHash!
- tx.blockNumber = fakeTxWithStatus2.transaction.blockNumber
+ tx.blockNumber = BigInt(fakeTxWithStatus2.transaction.blockNumber!).toString()
+ tx.timestamp = BigInt(fakeTxWithStatus2.transaction.timestamp!).toString()
expect(stubbedTxAddressFinderConstructor).toHaveBeenCalledWith(
lockHashes,
[new AssetAccountInfo().generateAnyoneCanPayScript(addressInfo.blake160).computeHash()],
@@ -238,7 +248,8 @@ describe('queue', () => {
for (const { transaction } of fakeTxs) {
const tx = Transaction.fromSDK(transaction.toSDK())
tx.blockHash = fakeTxWithStatus2.txStatus.blockHash!
- tx.blockNumber = fakeTxWithStatus2.transaction.blockNumber
+ tx.blockNumber = BigInt(fakeTxWithStatus2.transaction.blockNumber!).toString()
+ tx.timestamp = BigInt(fakeTxWithStatus2.transaction.timestamp!).toString()
expect(stubbedSaveFetchFn).toHaveBeenCalledWith(tx)
}
})
@@ -260,7 +271,7 @@ describe('queue', () => {
stubbedSaveFetchFn.mockRejectedValueOnce(err)
stubbedRPCCreateBatchRequestExecFn
.mockResolvedValueOnce(fakeTxs)
- .mockResolvedValueOnce(fakeTxs.map(v => ({ timestamp: v.transaction.timestamp, number: v.transaction.blockNumber })))
+ .mockResolvedValueOnce(fakeTxs.map(v => ({ ...fakeBlockHeader, timestamp: v.transaction.timestamp, number: v.transaction.blockNumber })))
stubbedTransactionsSubject.next({ txHashes: fakeTxs.map(v => v.transaction.hash), params: fakeTxs[0].transaction.blockNumber })
await flushPromises()
})
diff --git a/packages/neuron-wallet/tests/controllers/multisig.test.ts b/packages/neuron-wallet/tests/controllers/multisig.test.ts
index 6a135ee333..f83962734c 100644
--- a/packages/neuron-wallet/tests/controllers/multisig.test.ts
+++ b/packages/neuron-wallet/tests/controllers/multisig.test.ts
@@ -18,6 +18,15 @@ jest.mock('electron', () => ({
getFocusedWindow: jest.fn()
}
}))
+jest.mock('services/wallets', () => ({
+ getInstance() {
+ return {
+ getCurrent() {
+ return jest.fn()
+ }
+ }
+ }
+}))
jest.mock('../../src/services/multisig')
const MultiSigServiceMock = MultisigService as jest.MockedClass
diff --git a/packages/neuron-wallet/tests/controllers/sync-api.test.ts b/packages/neuron-wallet/tests/controllers/sync-api.test.ts
index 53d10e68a8..fcb367eefa 100644
--- a/packages/neuron-wallet/tests/controllers/sync-api.test.ts
+++ b/packages/neuron-wallet/tests/controllers/sync-api.test.ts
@@ -47,6 +47,9 @@ jest.doMock('services/ckb-runner', () => ({
jest.mock('undici', () => ({
request: () => jest.fn()()
}))
+jest.mock('services/multisig', () => ({
+ syncMultisigOutput: () => jest.fn()
+}))
describe('SyncApiController', () => {
const emitter = new Emitter()
diff --git a/packages/neuron-wallet/tests/services/light-runner.test.ts b/packages/neuron-wallet/tests/services/light-runner.test.ts
index 9f8c886ae7..11b3d85557 100644
--- a/packages/neuron-wallet/tests/services/light-runner.test.ts
+++ b/packages/neuron-wallet/tests/services/light-runner.test.ts
@@ -19,6 +19,7 @@ const spawnMock = jest.fn()
const loggerErrorMock = jest.fn()
const loggerInfoMock = jest.fn()
const transportsGetFileMock = jest.fn()
+const cleanMock = jest.fn()
function resetMock() {
mockFn.mockReset()
@@ -38,6 +39,7 @@ function resetMock() {
loggerErrorMock.mockReset()
loggerInfoMock.mockReset()
transportsGetFileMock.mockReset()
+ cleanMock.mockReset()
}
jest.doMock('../../src/env', () => ({
@@ -67,6 +69,10 @@ jest.doMock('../../src/services/settings', () => ({
}
}))
+jest.doMock('../../src/database/chain', () => ({
+ clean: cleanMock
+}))
+
jest.doMock('process', () => ({
get platform() {
return platformMock()
From e29efd9c93fdd2d72f0259db312cfa07c41f1070 Mon Sep 17 00:00:00 2001
From: Keith
Date: Thu, 18 May 2023 17:14:49 +0800
Subject: [PATCH 13/17] fix: set default fee rate if fee rate api throws
---
.../hooks/useGetCountDownAndFeeRateStats.ts | 50 ++++++++-----------
1 file changed, 22 insertions(+), 28 deletions(-)
diff --git a/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts b/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts
index a1a97221fa..9caf6cf076 100644
--- a/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts
+++ b/packages/neuron-ui/src/utils/hooks/useGetCountDownAndFeeRateStats.ts
@@ -1,7 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { getFeeRateStats } from 'services/chain'
-import { AppActions, StateDispatch, useDispatch } from 'states'
-import { MEDIUM_FEE_RATE } from 'utils/const'
+import { MEDIUM_FEE_RATE, METHOD_NOT_FOUND } from 'utils/const'
type CountdownOptions = {
seconds?: number
@@ -15,34 +14,29 @@ const useGetCountDownAndFeeRateStats = ({ seconds = 30, interval = 1000 }: Count
median?: string
suggestFeeRate: number
}>({ suggestFeeRate: MEDIUM_FEE_RATE })
- const dispatch = useDispatch()
- const handleGetFeeRateStatis = useCallback(
- (stateDispatch: StateDispatch) => {
- getFeeRateStats()
- .then(res => {
- const { mean, median } = res ?? {}
- const suggested = mean && median ? Math.max(1000, Number(mean), Number(median)) : MEDIUM_FEE_RATE
+ const handleGetFeeRateStatis = useCallback(() => {
+ getFeeRateStats()
+ .then(res => {
+ const { mean, median } = res ?? {}
+ const suggested = mean && median ? Math.max(1000, Number(mean), Number(median)) : MEDIUM_FEE_RATE
- setFeeFatestatsData(states => ({ ...states, ...res, suggestFeeRate: suggested }))
- })
- .catch((err: Error & { response?: { status: number } }) => {
+ setFeeFatestatsData(states => ({ ...states, ...res, suggestFeeRate: suggested }))
+ })
+ .catch((err: Error & { response?: { status: number } }) => {
+ try {
if (err?.response?.status === 404) {
- setFeeFatestatsData(states => ({ ...states, suggestFeeRate: MEDIUM_FEE_RATE }))
- } else {
- stateDispatch({
- type: AppActions.AddNotification,
- payload: {
- type: 'alert',
- timestamp: +new Date(),
- content: err.message,
- },
- })
+ throw new Error('method not found')
}
- })
- },
- [getFeeRateStats, setFeeFatestatsData]
- )
+ const errMsg = JSON.parse(err.message)
+ if (errMsg?.code === METHOD_NOT_FOUND) {
+ throw new Error('method not found')
+ }
+ } catch (error) {
+ setFeeFatestatsData(states => ({ ...states, suggestFeeRate: MEDIUM_FEE_RATE }))
+ }
+ })
+ }, [])
useEffect(() => {
const countInterval = setInterval(() => {
@@ -56,9 +50,9 @@ const useGetCountDownAndFeeRateStats = ({ seconds = 30, interval = 1000 }: Count
useEffect(() => {
if (countDown === seconds) {
- handleGetFeeRateStatis(dispatch)
+ handleGetFeeRateStatis()
}
- }, [countDown, seconds, dispatch])
+ }, [countDown, seconds])
return { countDown, ...feeFatestatsData }
}
From fed05033af35b2afbf47d1e29f1a1f39cd512750 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com>
Date: Mon, 22 May 2023 11:47:00 +0800
Subject: [PATCH 14/17] fix: Fix some bugs. (#2675)
---
.../sync/light-connector.ts | 42 ++++++++------
.../src/controllers/app/index.ts | 6 +-
.../src/controllers/networks/index.ts | 2 +-
.../neuron-wallet/src/services/ckb-runner.ts | 56 +++++++++----------
.../neuron-wallet/src/services/indexer.ts | 11 ++--
.../src/services/light-runner.ts | 4 +-
.../src/services/monitor/ckb-monitor.ts | 6 +-
packages/neuron-wallet/src/utils/ckb-rpc.ts | 14 +++--
packages/neuron-wallet/src/utils/common.ts | 9 ---
.../tests/services/ckb-runner.test.ts | 34 +++++++----
.../tests/services/indexer.test.ts | 45 +++++++++++++--
.../tests/services/light-runner.test.ts | 10 +++-
12 files changed, 150 insertions(+), 89 deletions(-)
diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
index 1a562e3de0..a55ce16778 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
@@ -9,7 +9,7 @@ import { scheduler } from 'timers/promises'
import SyncProgressService from '../../services/sync-progress'
import { BlockTips, LumosCellQuery, Connector, AppendScript } from './connector'
import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils'
-import { LightRPC, LightScriptFilter } from '../../utils/ckb-rpc'
+import { FetchTransactionReturnType, LightRPC, LightScriptFilter } from '../../utils/ckb-rpc'
import HexUtils from '../../utils/hex'
import Multisig from '../../services/multisig'
import { SyncAddressType } from '../../database/chain/entities/sync-progress'
@@ -61,7 +61,7 @@ export default class LightConnector extends Connector {
this.fetchDepCell()
}
- private async fetchDepCell() {
+ private async getDepTxs(): Promise {
const assetAccountInfo = new AssetAccountInfo()
const fetchCellDeps = [
assetAccountInfo.anyoneCanPayCellDep,
@@ -75,30 +75,36 @@ export default class LightConnector extends Connector {
const fetchTxHashes = fetchCellDeps
.map(v => v.outPoint.txHash)
.map<[string, string]>(v => ['fetchTransaction', v])
- let txs = await this.lightRpc
- .createBatchRequest(fetchTxHashes)
+ const txs = await this.lightRpc
+ .createBatchRequest(fetchTxHashes)
.exec()
- if (txs.some(v => !v)) {
- // if some txs fetch to added, then fetch the actural txs
- txs = await this.lightRpc
- .createBatchRequest(fetchTxHashes)
- .exec()
+ if (txs.some(v => !v.txWithStatus)) {
+ // wait for light client sync the dep cell
+ await scheduler.wait(10000)
+ return await this.getDepTxs()
}
- const depGroupOutputsData: string[] = fetchCellDeps
+ return fetchCellDeps
.map((v, idx) => {
if (v.depType === DepType.DepGroup) {
- return txs[idx]?.transaction?.outputsData?.[+v.outPoint.index]
+ const tx = txs[idx]
+ return tx.txWithStatus ? tx?.txWithStatus?.transaction?.outputsData?.[+v.outPoint.index] : undefined
}
})
.filter((v): v is string => !!v)
+ }
+
+ private async fetchDepCell() {
+ const depGroupOutputsData: string[] = await this.getDepTxs()
const depGroupTxHashes = [
...new Set(depGroupOutputsData.map(v => unpackGroup.unpack(v).map(v => v.tx_hash.toHexString())).flat())
]
- await this.lightRpc
- .createBatchRequest(
- depGroupTxHashes.map(v => ['fetchTransaction', v])
- )
- .exec()
+ if (depGroupTxHashes.length) {
+ await this.lightRpc
+ .createBatchRequest(
+ depGroupTxHashes.map(v => ['fetchTransaction', v])
+ )
+ .exec()
+ }
}
private async synchronize() {
@@ -154,9 +160,9 @@ export default class LightConnector extends Connector {
if (!this.addressMetas.length && !appendScripts?.length) {
return
}
- const sycnScripts = await this.lightRpc.getScripts()
+ const syncScripts = await this.lightRpc.getScripts()
const existSyncscripts: Record = {}
- sycnScripts.forEach(v => {
+ syncScripts.forEach(v => {
existSyncscripts[scriptToHash(v.script)] = v
})
const currentWalletId = WalletService.getInstance().getCurrent()?.id
diff --git a/packages/neuron-wallet/src/controllers/app/index.ts b/packages/neuron-wallet/src/controllers/app/index.ts
index 3bef8c0080..80b6fa4c8c 100644
--- a/packages/neuron-wallet/src/controllers/app/index.ts
+++ b/packages/neuron-wallet/src/controllers/app/index.ts
@@ -59,8 +59,10 @@ export default class AppController {
if (env.isTestMode) {
return
}
- await stopCkbNode()
- await CKBLightRunner.getInstance().stop()
+ await Promise.all([
+ stopCkbNode(),
+ CKBLightRunner.getInstance().stop(),
+ ])
}
public registerChannels(win: BrowserWindow, channels: string[]) {
diff --git a/packages/neuron-wallet/src/controllers/networks/index.ts b/packages/neuron-wallet/src/controllers/networks/index.ts
index b25d31e06c..2ff247281f 100644
--- a/packages/neuron-wallet/src/controllers/networks/index.ts
+++ b/packages/neuron-wallet/src/controllers/networks/index.ts
@@ -26,7 +26,7 @@ export default class NetworksController {
await this.connectToNetwork(true)
} else {
logger.debug('Network:\tconnection dropped')
- resetSyncTaskQueue.push(false)
+ resetSyncTaskQueue.asyncPush(false)
}
})
diff --git a/packages/neuron-wallet/src/services/ckb-runner.ts b/packages/neuron-wallet/src/services/ckb-runner.ts
index 340cbd804f..422a39bf98 100644
--- a/packages/neuron-wallet/src/services/ckb-runner.ts
+++ b/packages/neuron-wallet/src/services/ckb-runner.ts
@@ -1,12 +1,13 @@
import env from '../env'
import path from 'path'
import fs from 'fs'
-import { ChildProcess, spawn } from 'child_process'
+import { ChildProcess, StdioNull, StdioPipe, spawn } from 'child_process'
import process from 'process'
import logger from '../utils/logger'
import SettingsService from './settings'
import MigrateSubject from '../models/subjects/migrate-subject'
import IndexerService from './indexer'
+import { resetSyncTaskQueue } from '../block-sync-renderer'
const platform = (): string => {
switch (process.platform) {
@@ -83,35 +84,33 @@ export const startCkbNode = async () => {
logger.info('CKB:\tstarting node...')
const options = ['run', '-C', SettingsService.getInstance().ckbDataPath, '--indexer']
+ const stdio: (StdioNull | StdioPipe)[] = ['ignore', 'ignore', 'pipe']
if (app.isPackaged && process.env.CKB_NODE_ASSUME_VALID_TARGET) {
options.push('--assume-valid-target', process.env.CKB_NODE_ASSUME_VALID_TARGET)
+ stdio[1] = 'pipe'
}
- ckb = spawn(ckbBinary(), options, { stdio: ['ignore', 'pipe', 'pipe'] })
-
- ckb.stderr &&
- ckb.stderr.on('data', data => {
- const dataString: string = data.toString()
- logger.error('CKB:\trun fail:', dataString)
- if (dataString.includes('CKB wants to migrate the data into new format')) {
- MigrateSubject.next({ type: 'need-migrate' })
- }
- })
- if (app.isPackaged && process.env.CKB_NODE_ASSUME_VALID_TARGET) {
- ckb.stdout &&
- ckb.stdout.on('data', data => {
- const dataString: string = data.toString()
- if (
- dataString.includes(
- `can't find assume valid target temporarily, hash: Byte32(${process.env.CKB_NODE_ASSUME_VALID_TARGET})`
- )
- ) {
- isLookingValidTarget = true
- lastLogTime = Date.now()
- } else if (lastLogTime && Date.now() - lastLogTime > 10000) {
- isLookingValidTarget = false
- }
- })
- }
+ ckb = spawn(ckbBinary(), options, { stdio })
+
+ ckb.stderr?.on('data', data => {
+ const dataString: string = data.toString()
+ logger.error('CKB:\trun fail:', dataString)
+ if (dataString.includes('CKB wants to migrate the data into new format')) {
+ MigrateSubject.next({ type: 'need-migrate' })
+ }
+ })
+ ckb.stdout?.on('data', data => {
+ const dataString: string = data.toString()
+ if (
+ dataString.includes(
+ `can't find assume valid target temporarily, hash: Byte32(${process.env.CKB_NODE_ASSUME_VALID_TARGET})`
+ )
+ ) {
+ isLookingValidTarget = true
+ lastLogTime = Date.now()
+ } else if (lastLogTime && Date.now() - lastLogTime > 10000) {
+ isLookingValidTarget = false
+ }
+ })
ckb.on('error', error => {
logger.error('CKB:\trun fail:', error)
@@ -133,7 +132,7 @@ export const stopCkbNode = () => {
if (ckb) {
logger.info('CKB:\tkilling node')
ckb.once('close', () => resolve())
- ckb.kill('SIGKILL')
+ ckb.kill()
ckb = null
} else {
resolve()
@@ -148,6 +147,7 @@ export const clearCkbNodeCache = async () => {
await stopCkbNode()
fs.rmSync(SettingsService.getInstance().ckbDataPath, { recursive: true, force: true })
await startCkbNode()
+ resetSyncTaskQueue.asyncPush(true)
}
export function migrateCkbData() {
diff --git a/packages/neuron-wallet/src/services/indexer.ts b/packages/neuron-wallet/src/services/indexer.ts
index 77924c3599..f6e536bcc0 100644
--- a/packages/neuron-wallet/src/services/indexer.ts
+++ b/packages/neuron-wallet/src/services/indexer.ts
@@ -6,6 +6,7 @@ import { clean as cleanChain } from '../database/chain'
import SettingsService from './settings'
import startMonitor, { stopMonitor } from './monitor'
import NodeService from './node'
+import { resetSyncTaskQueue } from '../block-sync-renderer'
export default class IndexerService {
private constructor() {}
@@ -19,19 +20,15 @@ export default class IndexerService {
}
static clearCache = async (clearIndexerFolder = false) => {
- if (!NodeService.getInstance().isCkbNodeExternal) {
- await stopMonitor('ckb')
- }
await cleanChain()
- if (clearIndexerFolder) {
+ if (!NodeService.getInstance().isCkbNodeExternal && clearIndexerFolder) {
+ await stopMonitor('ckb')
IndexerService.getInstance().clearData()
await new SyncedBlockNumber().setNextBlock(BigInt(0))
- }
-
- if (!NodeService.getInstance().isCkbNodeExternal) {
await startMonitor('ckb', true)
}
+ resetSyncTaskQueue.asyncPush(true)
}
static cleanOldIndexerData() {
diff --git a/packages/neuron-wallet/src/services/light-runner.ts b/packages/neuron-wallet/src/services/light-runner.ts
index 9133da0917..e2f495adca 100644
--- a/packages/neuron-wallet/src/services/light-runner.ts
+++ b/packages/neuron-wallet/src/services/light-runner.ts
@@ -5,6 +5,7 @@ import env from '../env'
import logger from '../utils/logger'
import SettingsService from '../services/settings'
import { clean } from '../database/chain'
+import { resetSyncTaskQueue } from '../block-sync-renderer'
const { app } = env
@@ -53,7 +54,7 @@ abstract class NodeRunner {
if (this.runnerProcess) {
logger.info('Runner:\tkilling node')
this.runnerProcess.once('close', () => resolve())
- this.runnerProcess.kill('SIGKILL')
+ this.runnerProcess.kill()
this.runnerProcess = undefined
} else {
resolve()
@@ -167,5 +168,6 @@ export class CKBLightRunner extends NodeRunner {
fs.rmSync(SettingsService.getInstance().testnetLightDataPath, { recursive: true, force: true })
await clean()
await this.start()
+ resetSyncTaskQueue.asyncPush(true)
}
}
diff --git a/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts b/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts
index 71747eea1e..eba85c88e9 100644
--- a/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts
+++ b/packages/neuron-wallet/src/services/monitor/ckb-monitor.ts
@@ -13,8 +13,10 @@ export default class CkbMonitor extends BaseMonitor {
}
async stop(): Promise {
- await stopCkbNode()
- await CKBLightRunner.getInstance().stop()
+ await Promise.all([
+ stopCkbNode(),
+ CKBLightRunner.getInstance().stop()
+ ])
}
name: string = 'ckb'
diff --git a/packages/neuron-wallet/src/utils/ckb-rpc.ts b/packages/neuron-wallet/src/utils/ckb-rpc.ts
index 284cf88d2b..57481b27ce 100644
--- a/packages/neuron-wallet/src/utils/ckb-rpc.ts
+++ b/packages/neuron-wallet/src/utils/ckb-rpc.ts
@@ -90,8 +90,10 @@ const lightRPCProperties: Record[0]
method: 'fetch_transaction',
paramsFormatters: [paramsFormatter.toHash],
resultFormatters: (result: { status: 'fetched' | 'fetching' | 'added' | 'not_found', data?: RPC.TransactionWithStatus }) => {
- if (result.status === 'fetched' && result.data) {return resultFormatter.toTransactionWithStatus(result.data)}
- return result
+ return {
+ status: result.status,
+ txWithStatus: result.status === 'fetched' && result.data ? resultFormatter.toTransactionWithStatus(result.data) : undefined
+ }
}
}
}
@@ -106,6 +108,8 @@ export class FullCKBRPC extends CKBRPC {
}
}
+export type FetchTransactionReturnType = { status: 'fetched' | 'fetching' | 'added' | 'not_found', txWithStatus?: CKBComponents.TransactionWithStatus }
+
export class LightRPC extends Base {
setScripts: (params: LightScriptFilter[]) => Promise
getScripts: () => Promise
@@ -117,7 +121,7 @@ export class LightRPC extends Base {
) => Promise<{ lastCursor: HexString, txs: { txHash: HexString, txIndex: HexString, blockNumber: CKBComponents.BlockNumber }[]}>
getTransactionInLight: Base['getTransaction']
- fetchTransaction: (hash: string) => Promise
+ fetchTransaction: (hash: string) => Promise
getGenesisBlock: () => Promise
exceptionMethods = ['getCurrentEpoch', 'getEpochByNumber', 'getBlockHash', 'getLiveCell']
@@ -151,10 +155,10 @@ export class LightRPC extends Base {
if (!tx?.transaction) {
tx = await CommonUtils.retry(3, 100, async () => {
const tmp = await this.fetchTransaction(hash)
- if ('status' in tmp) {
+ if (!tmp.txWithStatus) {
throw new Error(`transaction ${hash} status: ${tmp.status}`)
}
- return tmp
+ return tmp.txWithStatus
})
if (!tx) {throw new Error(`Fetch transaction tx failed, please try it later: ${hash}`)}
}
diff --git a/packages/neuron-wallet/src/utils/common.ts b/packages/neuron-wallet/src/utils/common.ts
index c8edb8b122..e67be1b0a4 100644
--- a/packages/neuron-wallet/src/utils/common.ts
+++ b/packages/neuron-wallet/src/utils/common.ts
@@ -1,14 +1,5 @@
import logger from '../utils/logger'
-//TODO remove it after typescript upgrade to above 4.5
-type Awaited = T extends null | undefined
- ? T // special case for `null | undefined` when not in `--strictNullChecks` mode
- : T extends object & { then(onfulfilled: infer F, ...args: infer _): any } // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
- ? F extends (value: infer V, ...args: infer _) => any // if the argument to `then` is callable, extracts the first argument
- ? Awaited // recursively unwrap the value
- : never // the argument to `then` was not callable
- : T // non-object or non-thenable
-
export default class CommonUtils {
public static sleep = (ms: number): Promise => {
return new Promise(resolve => setTimeout(resolve, ms))
diff --git a/packages/neuron-wallet/tests/services/ckb-runner.test.ts b/packages/neuron-wallet/tests/services/ckb-runner.test.ts
index e4fe206a45..fd2a07f6f8 100644
--- a/packages/neuron-wallet/tests/services/ckb-runner.test.ts
+++ b/packages/neuron-wallet/tests/services/ckb-runner.test.ts
@@ -1,5 +1,6 @@
import { EventEmitter } from 'typeorm/platform/PlatformTools'
import path from 'path'
+import { scheduler } from 'timers/promises'
const stubbedChildProcess = jest.fn()
const stubbedSpawn = jest.fn()
@@ -8,6 +9,7 @@ const stubbedExistsSync = jest.fn()
const stubbedLoggerInfo = jest.fn()
const stubbedLoggerError = jest.fn()
const stubbedLoggerLog = jest.fn()
+const resetSyncTaskQueueAsyncPushMock = jest.fn()
const stubbedProcess: any = {}
@@ -18,6 +20,7 @@ const resetMocks = () => {
stubbedLoggerInfo.mockReset()
stubbedLoggerError.mockReset()
stubbedLoggerLog.mockReset()
+ resetSyncTaskQueueAsyncPushMock.mockReset()
}
jest.doMock('child_process', () => {
@@ -72,6 +75,11 @@ jest.mock('../../src/block-sync-renderer', () => ({
jest.mock('../../src/services/indexer', () => ({
cleanOldIndexerData: jest.fn(),
}))
+jest.doMock('../../src/block-sync-renderer', () => ({
+ resetSyncTaskQueue: {
+ asyncPush: resetSyncTaskQueueAsyncPushMock
+ }
+}))
const {
startCkbNode,
stopCkbNode,
@@ -88,6 +96,7 @@ describe('ckb runner', () => {
stubbedCkb.stderr = new EventEmitter()
stubbedCkb.stdout = new EventEmitter()
stubbedSpawn.mockReturnValue(stubbedCkb)
+ resetSyncTaskQueueAsyncPushMock.mockReturnValue('')
})
;[
{ platform: 'win32', platformPath: 'win' },
@@ -122,7 +131,7 @@ describe('ckb runner', () => {
expect(stubbedSpawn).toHaveBeenCalledWith(
expect.stringContaining(path.join(platformPath, 'ckb')),
['run', '-C', ckbDataPath, '--indexer'],
- { stdio: ['ignore', 'pipe', 'pipe'] }
+ { stdio: ['ignore', 'ignore', 'pipe'] }
)
})
})
@@ -156,7 +165,7 @@ describe('ckb runner', () => {
expect(stubbedSpawn).toHaveBeenCalledWith(
expect.stringContaining(path.join(platformPath, 'ckb')),
['run', '-C', ckbDataPath, '--indexer'],
- { stdio: ['ignore', 'pipe', 'pipe'] }
+ { stdio: ['ignore', 'ignore', 'pipe'] }
)
})
})
@@ -178,14 +187,25 @@ describe('ckb runner', () => {
describe('with assume valid target', () => {
beforeEach(async () => {
+ stubbedProcess.platform = platform
app.isPackaged = true
stubbedProcess.env = { CKB_NODE_ASSUME_VALID_TARGET: '0x' + '0'.repeat(64) }
stubbedExistsSync.mockReturnValue(true)
await startCkbNode()
})
- afterEach(() => {
+ afterEach(async () => {
app.isPackaged = false
stubbedProcess.env = {}
+ const promise = stopCkbNode()
+ stubbedCkb.emit('close')
+ await promise
+ })
+ it('runs ckb binary', () => {
+ expect(stubbedSpawn).toHaveBeenCalledWith(
+ expect.stringContaining(path.join('bin', 'ckb')),
+ ['run', '-C', ckbDataPath, '--indexer', "--assume-valid-target", '0x' + '0'.repeat(64)],
+ { stdio: ['ignore', 'pipe', 'pipe'] }
+ )
})
it('is Looking valid target', () => {
stubbedCkb.stdout.emit(
@@ -193,21 +213,15 @@ describe('ckb runner', () => {
`can't find assume valid target temporarily, hash: Byte32(0x${'0'.repeat(64)})`
)
expect(getLookingValidTargetStatus()).toBeTruthy()
- stubbedCkb.emit('close')
})
it('is Looking valid target', async () => {
stubbedCkb.stdout.emit(
'data',
`can't find assume valid target temporarily, hash: Byte32(0x${'0'.repeat(64)})`
)
- await new Promise(resolve =>
- setTimeout(() => {
- resolve(undefined)
- }, 11000)
- )
+ await scheduler.wait(11000)
stubbedCkb.stdout.emit('data', `had find valid target`)
expect(getLookingValidTargetStatus()).toBeFalsy()
- stubbedCkb.emit('close')
}, 15000)
it('ckb has closed', async () => {
stubbedCkb.stdout.emit(
diff --git a/packages/neuron-wallet/tests/services/indexer.test.ts b/packages/neuron-wallet/tests/services/indexer.test.ts
index 9c08d4fea4..0d8872c9fb 100644
--- a/packages/neuron-wallet/tests/services/indexer.test.ts
+++ b/packages/neuron-wallet/tests/services/indexer.test.ts
@@ -4,6 +4,7 @@ const existsSyncMock = jest.fn()
const rmSyncMock = jest.fn()
const isCkbNodeExternalMock = jest.fn()
const stopMonitorMock = jest.fn()
+const checkNodeMock = jest.fn()
jest.mock('fs', () => {
return {
@@ -26,6 +27,9 @@ jest.mock('../../src/services/settings', () => {
set indexerDataPath(value: string) {
setIndexerDataPathMock(value)
},
+ get ckbDataPath() {
+ return jest.fn().mockReturnValue('')()
+ }
}
}
}
@@ -35,7 +39,13 @@ jest.mock('../../src/utils/logger', () => ({
debug: () => jest.fn(),
}))
-jest.mock('../../src/models/synced-block-number', () => ({}))
+jest.mock('../../src/models/synced-block-number', () => {
+ return function() {
+ return {
+ setNextBlock: jest.fn()
+ }
+ }
+})
jest.mock('../../src/database/chain', () => ({
clean: () => jest.fn()
@@ -53,10 +63,18 @@ jest.mock('../../src/services/node', () => ({
get isCkbNodeExternal() {
return isCkbNodeExternalMock()
},
+ checkNode: checkNodeMock,
}
},
}))
+const resetSyncTaskQueueAsyncPushMock = jest.fn()
+jest.mock('../../src/block-sync-renderer', () => ({
+ resetSyncTaskQueue: {
+ asyncPush: () => resetSyncTaskQueueAsyncPushMock()
+ }
+}))
+
describe('test IndexerService', () => {
beforeEach(() => {
existsSyncMock.mockReset()
@@ -88,15 +106,32 @@ describe('test IndexerService', () => {
})
describe('test clear cache', () => {
- it('is external ckb node', () => {
+ beforeEach(() => {
+ resetSyncTaskQueueAsyncPushMock.mockReset()
+ })
+ it('is external ckb node', async () => {
isCkbNodeExternalMock.mockReturnValue(true)
- IndexerService.clearCache()
+ await IndexerService.clearCache()
+ expect(stopMonitorMock).toBeCalledTimes(0)
+ expect(resetSyncTaskQueueAsyncPushMock).toBeCalledTimes(1)
+ })
+ it('is internal ckb node', async () => {
+ isCkbNodeExternalMock.mockReturnValue(false)
+ await IndexerService.clearCache()
expect(stopMonitorMock).toBeCalledTimes(0)
+ expect(resetSyncTaskQueueAsyncPushMock).toBeCalledTimes(1)
})
- it('is internal ckb node', () => {
+ it('clear indexer data with internal ckb node', async () => {
isCkbNodeExternalMock.mockReturnValue(false)
- IndexerService.clearCache()
+ await IndexerService.clearCache(true)
expect(stopMonitorMock).toBeCalledTimes(1)
+ expect(resetSyncTaskQueueAsyncPushMock).toBeCalledTimes(1)
+ })
+ it('clear indexer data with external ckb node', async () => {
+ isCkbNodeExternalMock.mockReturnValue(true)
+ await IndexerService.clearCache(true)
+ expect(stopMonitorMock).toBeCalledTimes(0)
+ expect(resetSyncTaskQueueAsyncPushMock).toBeCalledTimes(1)
})
})
})
diff --git a/packages/neuron-wallet/tests/services/light-runner.test.ts b/packages/neuron-wallet/tests/services/light-runner.test.ts
index 11b3d85557..f2b1fdf74c 100644
--- a/packages/neuron-wallet/tests/services/light-runner.test.ts
+++ b/packages/neuron-wallet/tests/services/light-runner.test.ts
@@ -20,6 +20,7 @@ const loggerErrorMock = jest.fn()
const loggerInfoMock = jest.fn()
const transportsGetFileMock = jest.fn()
const cleanMock = jest.fn()
+const resetSyncTaskQueueAsyncPushMock = jest.fn()
function resetMock() {
mockFn.mockReset()
@@ -40,6 +41,7 @@ function resetMock() {
loggerInfoMock.mockReset()
transportsGetFileMock.mockReset()
cleanMock.mockReset()
+ resetSyncTaskQueueAsyncPushMock.mockReset()
}
jest.doMock('../../src/env', () => ({
@@ -73,6 +75,12 @@ jest.doMock('../../src/database/chain', () => ({
clean: cleanMock
}))
+jest.doMock('../../src/block-sync-renderer', () => ({
+ resetSyncTaskQueue: {
+ asyncPush: resetSyncTaskQueueAsyncPushMock
+ }
+}))
+
jest.doMock('process', () => ({
get platform() {
return platformMock()
@@ -282,7 +290,7 @@ describe('test light runner', () => {
CKBLightRunner.getInstance().runnerProcess = emitter
mockFn.mockImplementation(() => { emitter.emit('close') })
await CKBLightRunner.getInstance().stop()
- expect(mockFn).toBeCalledWith('SIGKILL')
+ expect(mockFn).toBeCalledWith()
expect(CKBLightRunner.getInstance().runnerProcess).toBeUndefined()
})
})
From 8b59e20a3f147f6c09c428812eb0f4d1e2a2b678 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com>
Date: Wed, 24 May 2023 09:06:29 +0800
Subject: [PATCH 15/17] fix: Fix dao progress bar and light client should not
verify version and indexer (#2679)
* fix: Fix dao progress bar and light client should not verify version and indexer.
* fix: use const if variable will not change.
* feat: update light client version.
* fix: More clear code.
---
.ckb-light-version | 2 +-
packages/neuron-ui/src/components/NervosDAO/hooks.ts | 12 ++++++------
packages/neuron-wallet/src/services/node.ts | 9 ++++++---
3 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/.ckb-light-version b/.ckb-light-version
index 576b77719a..f82e0685d9 100644
--- a/.ckb-light-version
+++ b/.ckb-light-version
@@ -1 +1 @@
-v0.2.3
+v0.2.4
diff --git a/packages/neuron-ui/src/components/NervosDAO/hooks.ts b/packages/neuron-ui/src/components/NervosDAO/hooks.ts
index 2b7bb35c4e..4db179fa68 100644
--- a/packages/neuron-ui/src/components/NervosDAO/hooks.ts
+++ b/packages/neuron-ui/src/components/NervosDAO/hooks.ts
@@ -563,18 +563,18 @@ export const useUpdateDepositEpochList = ({
if (connectionStatus === 'online') {
getBlockHashes(records.map(v => v.depositOutPoint?.txHash).filter(v => !!v) as string[]).then(
depositBlockHashes => {
- const recordKeyIdxMap = new Map()
+ const recordKeyIdx: string[] = []
const batchParams: ['getHeader', string][] = []
- records.forEach((record, idx) => {
+ records.forEach((record) => {
if (!record.depositOutPoint && record.blockHash) {
batchParams.push(['getHeader', record.blockHash])
- recordKeyIdxMap.set(record.outPoint.txHash, idx)
+ recordKeyIdx.push(record.outPoint.txHash)
}
})
- depositBlockHashes.forEach((v, idx) => {
+ depositBlockHashes.forEach((v) => {
if (v.blockHash) {
batchParams.push(['getHeader', v.blockHash])
- recordKeyIdxMap.set(v.txHash, idx)
+ recordKeyIdx.push(v.txHash)
}
})
ckbCore.rpc
@@ -584,7 +584,7 @@ export const useUpdateDepositEpochList = ({
const epochList = new Map()
records.forEach(record => {
const key = record.depositOutPoint ? record.depositOutPoint.txHash : record.outPoint.txHash
- epochList.set(key, recordKeyIdxMap.has(key) ? res[recordKeyIdxMap.get(key)!]?.epoch : null)
+ epochList.set(key, res[recordKeyIdx.indexOf(key)]?.epoch)
})
setDepositEpochList(epochList)
})
diff --git a/packages/neuron-wallet/src/services/node.ts b/packages/neuron-wallet/src/services/node.ts
index 6e0d3feb7d..b8e38df6d3 100644
--- a/packages/neuron-wallet/src/services/node.ts
+++ b/packages/neuron-wallet/src/services/node.ts
@@ -143,13 +143,16 @@ class NodeService {
} else {
logger.info('CKB:\texternal RPC on default uri detected, skip starting bundled CKB node.')
this._isCkbNodeExternal = true
- await this.verifyNodeVersion()
- await this.verifyStartWithIndexer()
+ const network = NetworksService.getInstance().getCurrent()
+ if (network.type !== NetworkType.Light) {
+ await this.verifyNodeVersion()
+ await this.verifyStartWithIndexer()
+ }
}
}
public async isDefaultCKBNeedRestart() {
- let network = NetworksService.getInstance().getCurrent()
+ const network = NetworksService.getInstance().getCurrent()
if (network.remote !== BUNDLED_CKB_URL && network.remote !== BUNDLED_LIGHT_CKB_URL) {
return false
}
From d84682a7b62580bd2b32f388c54adbc3b24a13d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com>
Date: Fri, 26 May 2023 10:51:20 +0800
Subject: [PATCH 16/17] fix: Fix multisig with light client (#2682)
* fix: Fix multisig with light client
1. Any multisig script should sync from 0.
2. When adding multisig address after deletion, it should start sync again.
3. Every 10s to refresh multisig sync height, not when multisig address changes.
---
.../src/components/MultisigAddress/hooks.ts | 16 ++++++---
.../sync/light-connector.ts | 34 ++++++++++++-------
.../src/services/sync-progress.ts | 19 +++++++++--
.../light-connector.test.ts | 11 ++++--
4 files changed, 57 insertions(+), 23 deletions(-)
diff --git a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts
index 6acbb6c2e6..b448c0ee00 100644
--- a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts
+++ b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts
@@ -320,17 +320,23 @@ export const useSubscription = ({
useEffect(() => {
const dataUpdateSubscription = MultisigOutputUpdate.subscribe(() => {
getAndSaveMultisigBalances()
- if (isLightClient) {
- getAndSaveMultisigSyncProgress()
- }
})
getAndSaveMultisigBalances()
+ return () => {
+ dataUpdateSubscription.unsubscribe()
+ }
+ }, [walletId, getAndSaveMultisigBalances])
+ useEffect(() => {
+ let interval: ReturnType | undefined
if (isLightClient) {
+ interval = setInterval(() => {
+ getAndSaveMultisigSyncProgress()
+ }, 10000)
getAndSaveMultisigSyncProgress()
}
return () => {
- dataUpdateSubscription.unsubscribe()
+ clearInterval(interval)
}
- }, [walletId, getAndSaveMultisigBalances])
+ }, [isLightClient, getAndSaveMultisigSyncProgress])
return { multisigBanlances, multisigSyncProgress }
}
diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
index a55ce16778..e50f5a99fb 100644
--- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
+++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-connector.ts
@@ -156,8 +156,8 @@ export default class LightConnector extends Connector {
})
}
- private async initSyncProgress(appendScripts?: AppendScript[]) {
- if (!this.addressMetas.length && !appendScripts?.length) {
+ private async initSyncProgress(appendScripts: AppendScript[] = []) {
+ if (!this.addressMetas.length && !appendScripts.length) {
return
}
const syncScripts = await this.lightRpc.getScripts()
@@ -181,27 +181,35 @@ export default class LightConnector extends Connector {
}))
})
.flat()
- .concat(appendScripts ?? [])
const walletMinBlockNumber = await SyncProgressService.getWalletMinBlockNumber()
const wallets = await WalletService.getInstance().getAll()
const walletStartBlockMap = wallets.reduce>(
(pre, cur) => ({ ...pre, [cur.id]: cur.startBlockNumberInLight }),
{}
)
- const setScriptsParams = allScripts.map(v => ({
- ...v,
- blockNumber:
- existSyncscripts[scriptToHash(v.script)]?.blockNumber ??
- walletStartBlockMap[v.walletId] ??
- `0x${(walletMinBlockNumber?.[v.walletId] ?? 0).toString(16)}`
- }))
+ const otherTypeSyncProgress = await SyncProgressService.getOtherTypeSyncProgress()
+ const setScriptsParams = [
+ ...allScripts.map(v => ({
+ ...v,
+ blockNumber:
+ existSyncscripts[scriptToHash(v.script)]?.blockNumber ??
+ walletStartBlockMap[v.walletId] ??
+ `0x${(walletMinBlockNumber?.[v.walletId] ?? 0).toString(16)}`
+ })),
+ ...appendScripts.map(v => ({
+ ...v,
+ blockNumber:
+ existSyncscripts[scriptToHash(v.script)]?.blockNumber ??
+ `0x${(otherTypeSyncProgress[scriptToHash(v.script)] ?? 0).toString(16)}`
+ }))
+ ]
await this.lightRpc.setScripts(setScriptsParams)
- await SyncProgressService.resetSyncProgress(allScripts)
const walletIds = [...new Set(this.addressMetas.map(v => v.walletId))]
- await SyncProgressService.removeWalletsByExists(walletIds)
+ await SyncProgressService.resetSyncProgress([allScripts, appendScripts].flat())
+ await SyncProgressService.updateSyncProgressFlag(walletIds)
await SyncProgressService.removeByHashesAndAddressType(
SyncAddressType.Multisig,
- appendScripts?.map(v => scriptToHash(v.script))
+ appendScripts.map(v => scriptToHash(v.script))
)
}
diff --git a/packages/neuron-wallet/src/services/sync-progress.ts b/packages/neuron-wallet/src/services/sync-progress.ts
index 8530e90516..7a1d9d5a9b 100644
--- a/packages/neuron-wallet/src/services/sync-progress.ts
+++ b/packages/neuron-wallet/src/services/sync-progress.ts
@@ -22,13 +22,19 @@ export default class SyncProgressService {
.execute()
}
- static async removeWalletsByExists(existWalletIds: string[]) {
+ static async updateSyncProgressFlag(existWalletIds: string[]) {
await getConnection()
.createQueryBuilder()
.update(SyncProgress)
.set({ delete: true })
.where({ walletId: Not(In(existWalletIds)) })
.execute()
+ await getConnection()
+ .createQueryBuilder()
+ .update(SyncProgress)
+ .set({ delete: false })
+ .where({ walletId: In(existWalletIds) })
+ .execute()
}
static async removeByHashesAndAddressType(addressType: SyncAddressType, existHashes?: string[]) {
@@ -84,7 +90,7 @@ export default class SyncProgressService {
const item = await getConnection()
.getRepository(SyncProgress)
.createQueryBuilder()
- .where({ delete: false, ...(currentWallet ? { walletId: currentWallet.id } : {}) })
+ .where({ delete: false, addressType: SyncAddressType.Default, ...(currentWallet ? { walletId: currentWallet.id } : {}) })
.orderBy('blockEndNumber', 'ASC')
.getOne()
return item?.blockEndNumber || 0
@@ -101,6 +107,15 @@ export default class SyncProgressService {
return items.reduce>((pre, cur) => ({ ...pre, [cur.walletId]: cur.blockStartNumber }), {})
}
+ static async getOtherTypeSyncProgress() {
+ const items = await getConnection()
+ .getRepository(SyncProgress)
+ .find({
+ addressType: SyncAddressType.Multisig,
+ })
+ return items.reduce>((pre, cur) => ({ ...pre, [cur.hash]: cur.blockStartNumber }), {})
+ }
+
static async getSyncProgressByHashes(hashes: string[]) {
return await getConnection()
.getRepository(SyncProgress)
diff --git a/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts
index 5ebffb5634..779a7f366e 100644
--- a/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts
+++ b/packages/neuron-wallet/tests/block-sync-renderer/light-connector.test.ts
@@ -9,9 +9,10 @@ const getCurrentWalletMinBlockNumberMock = jest.fn()
const getAllSyncStatusToMapMock = jest.fn()
const resetSyncProgressMock = jest.fn()
const updateSyncStatusMock = jest.fn()
-const removeWalletsByExistsMock = jest.fn()
+const updateSyncProgressFlagMock = jest.fn()
const getWalletMinBlockNumberMock = jest.fn()
const removeByHashesAndAddressType = jest.fn()
+const getOtherTypeSyncProgressMock = jest.fn()
const setScriptsMock = jest.fn()
const getScriptsMock = jest.fn()
@@ -31,6 +32,7 @@ function mockReset() {
resetSyncProgressMock.mockReset()
updateSyncStatusMock.mockReset()
getWalletMinBlockNumberMock.mockReset()
+ getOtherTypeSyncProgressMock.mockReset()
setScriptsMock.mockReset()
getScriptsMock.mockReset()
@@ -52,9 +54,10 @@ jest.mock('../../src/services/sync-progress', () => {
static getAllSyncStatusToMap: any = () => getAllSyncStatusToMapMock()
static resetSyncProgress: any = (arg: any) => resetSyncProgressMock(arg)
static updateSyncStatus: any = (hash: string, update: any) => updateSyncStatusMock(hash, update)
- static removeWalletsByExists: any = (walletIds: string[]) => removeWalletsByExistsMock(walletIds)
+ static updateSyncProgressFlag: any = (walletIds: string[]) => updateSyncProgressFlagMock(walletIds)
static getWalletMinBlockNumber: any = () => getWalletMinBlockNumberMock()
static removeByHashesAndAddressType: any = (type: number, scripts: CKBComponents.Script[]) => removeByHashesAndAddressType(type, scripts)
+ static getOtherTypeSyncProgress: any = () => getOtherTypeSyncProgressMock()
}
})
@@ -101,6 +104,8 @@ describe('test light connector', () => {
beforeEach(() => {
walletGetAllMock.mockReturnValue([])
createBatchRequestMock.mockResolvedValue([])
+ getMultisigConfigForLightMock.mockResolvedValue([])
+ getOtherTypeSyncProgressMock.mockResolvedValue({})
})
afterEach(() => {
mockReset()
@@ -287,7 +292,7 @@ describe('test light connector', () => {
{ script: addressMeta.generateACPLockScript().toSDK(), scriptType: 'lock', walletId: 'walletId' },
{ script: addressMeta.generateLegacyACPLockScript().toSDK(), scriptType: 'lock', walletId: 'walletId' },
])
- expect(removeWalletsByExistsMock).toBeCalledWith(['walletId'])
+ expect(updateSyncProgressFlagMock).toBeCalledWith(['walletId'])
})
it('set new script with the synced min block number', async () => {
getScriptsMock.mockResolvedValue([])
From 673cf85e415f1d08ed3f55648625ebf42cdd6e32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com>
Date: Fri, 26 May 2023 22:01:00 +0800
Subject: [PATCH 17/17] Fix clean cache and network display (#2685)
---
.../src/components/NetworkStatus/index.tsx | 2 +-
.../NetworkStatus/networkStatus.module.scss | 18 +++++++++++-------
.../neuron-wallet/src/database/chain/index.ts | 5 +++--
.../neuron-wallet/src/services/light-runner.ts | 2 +-
.../src/services/sync-progress.ts | 8 +++++++-
5 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/packages/neuron-ui/src/components/NetworkStatus/index.tsx b/packages/neuron-ui/src/components/NetworkStatus/index.tsx
index 5fde398cf4..2a3c9b575e 100644
--- a/packages/neuron-ui/src/components/NetworkStatus/index.tsx
+++ b/packages/neuron-ui/src/components/NetworkStatus/index.tsx
@@ -57,7 +57,7 @@ const NetworkStatus = ({
) : null}
{isMigrate && {t('network-status.migrating')} }
{network ? (
-
+
{network.name}
diff --git a/packages/neuron-ui/src/components/NetworkStatus/networkStatus.module.scss b/packages/neuron-ui/src/components/NetworkStatus/networkStatus.module.scss
index 562b4ec785..95fd700a2b 100644
--- a/packages/neuron-ui/src/components/NetworkStatus/networkStatus.module.scss
+++ b/packages/neuron-ui/src/components/NetworkStatus/networkStatus.module.scss
@@ -9,13 +9,17 @@ $hover-bg-color: #3cc68a4d;
font-size: 0.8rem;
font-weight: bold;
- .name {
- position: relative;
- display: flex;
- align-items: center;
- line-height: 1em;
- word-break: break-all;
- margin-top: 3px;
+ .networkDisplay {
+ max-width: 100%;
+
+ .name {
+ position: relative;
+ display: block;
+ line-height: 16px;
+ word-break: break-all;
+ overflow-wrap: break-word;
+ margin-top: 3px;
+ }
}
/* keep the tooltip popped to make it more obvious */
diff --git a/packages/neuron-wallet/src/database/chain/index.ts b/packages/neuron-wallet/src/database/chain/index.ts
index e01503f5ff..94a266ed6a 100644
--- a/packages/neuron-wallet/src/database/chain/index.ts
+++ b/packages/neuron-wallet/src/database/chain/index.ts
@@ -7,18 +7,19 @@ import TransactionEntity from './entities/transaction'
import SyncInfoEntity from './entities/sync-info'
import IndexerTxHashCache from './entities/indexer-tx-hash-cache'
import MultisigOutput from './entities/multisig-output'
+import SyncProgress from './entities/sync-progress'
/*
* Clean local sqlite storage
*/
-export const clean = async () => {
+export const clean = async (clearAllLightClientData?: boolean) => {
await Promise.all([
...[InputEntity, OutputEntity, TransactionEntity, IndexerTxHashCache, MultisigOutput].map(entity => {
return getConnection()
.getRepository(entity)
.clear()
}),
- SyncProgressService.clearCurrentWalletProgress()
+ clearAllLightClientData ? getConnection().getRepository(SyncProgress).clear() : SyncProgressService.clearCurrentWalletProgress()
])
MultisigOutputChangedSubject.getSubject().next('reset')
diff --git a/packages/neuron-wallet/src/services/light-runner.ts b/packages/neuron-wallet/src/services/light-runner.ts
index e2f495adca..2db887e093 100644
--- a/packages/neuron-wallet/src/services/light-runner.ts
+++ b/packages/neuron-wallet/src/services/light-runner.ts
@@ -166,7 +166,7 @@ export class CKBLightRunner extends NodeRunner {
async clearNodeCache(): Promise {
await this.stop()
fs.rmSync(SettingsService.getInstance().testnetLightDataPath, { recursive: true, force: true })
- await clean()
+ await clean(true)
await this.start()
resetSyncTaskQueue.asyncPush(true)
}
diff --git a/packages/neuron-wallet/src/services/sync-progress.ts b/packages/neuron-wallet/src/services/sync-progress.ts
index 7a1d9d5a9b..0a856973a4 100644
--- a/packages/neuron-wallet/src/services/sync-progress.ts
+++ b/packages/neuron-wallet/src/services/sync-progress.ts
@@ -1,4 +1,4 @@
-import { getConnection, In, Not } from 'typeorm'
+import { Equal, getConnection, In, Not } from 'typeorm'
import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils'
import { HexString } from '@ckb-lumos/base'
import SyncProgress, { SyncAddressType } from '../database/chain/entities/sync-progress'
@@ -129,5 +129,11 @@ export default class SyncProgressService {
await getConnection()
.getRepository(SyncProgress)
.delete({ walletId: currentWallet?.id })
+ await getConnection()
+ .createQueryBuilder()
+ .update(SyncProgress)
+ .set({ blockEndNumber: 0, cursor: undefined })
+ .where({ walletId: Not(Equal(currentWallet?.id)) })
+ .execute()
}
}
|