From beeb8fa4859768ab05081bcbe74ba2d29211e5fc Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Thu, 16 May 2024 22:27:16 +0800 Subject: [PATCH 01/57] feat: Add help link for sync failed. --- .../GeneralSetting/generalSetting.module.scss | 2 +- .../src/components/SyncStatus/index.tsx | 44 +++++++++++- .../SyncStatus/syncStatus.module.scss | 68 ++++++++++++++++++- packages/neuron-ui/src/locales/en.json | 7 +- packages/neuron-ui/src/locales/es.json | 7 +- packages/neuron-ui/src/locales/fr.json | 7 +- packages/neuron-ui/src/locales/zh-tw.json | 7 +- packages/neuron-ui/src/locales/zh.json | 7 +- .../neuron-ui/src/widgets/Icons/Question.svg | 12 ++++ packages/neuron-ui/src/widgets/Icons/icon.tsx | 2 + .../src/widgets/Tooltip/tooltip.module.scss | 12 ++-- 11 files changed, 154 insertions(+), 21 deletions(-) create mode 100644 packages/neuron-ui/src/widgets/Icons/Question.svg diff --git a/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss b/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss index 92e5f8d34f..39881944b7 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss +++ b/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss @@ -9,7 +9,7 @@ $action-button-width: 11.25rem; gap: 16px; .content { - width: 176px; + min-width: 176px; padding: 16px; background: var(--input-disabled-color); border-radius: 8px; diff --git a/packages/neuron-ui/src/components/SyncStatus/index.tsx b/packages/neuron-ui/src/components/SyncStatus/index.tsx index 5735881e07..d425869b0e 100644 --- a/packages/neuron-ui/src/components/SyncStatus/index.tsx +++ b/packages/neuron-ui/src/components/SyncStatus/index.tsx @@ -1,10 +1,11 @@ import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { SyncStatus as SyncStatusEnum, ConnectionStatus, clsx, localNumberFormatter, getSyncLeftTime } from 'utils' -import { Confirming, NewTab } from 'widgets/Icons/icon' +import { Confirming, NewTab, Question } from 'widgets/Icons/icon' import { ReactComponent as UnexpandStatus } from 'widgets/Icons/UnexpandStatus.svg' import { ReactComponent as StartBlock } from 'widgets/Icons/StartBlock.svg' import Tooltip from 'widgets/Tooltip' +import { openExternal } from 'services/remote' import styles from './syncStatus.module.scss' const SyncDetail = ({ @@ -53,7 +54,17 @@ const SyncDetail = ({ ) : null}
-
{t('network-status.left-time')}
+
+ {t('network-status.left-time')}: + {t('network-status.left-time-tip')}
} + className={styles.question} + tipClassName={styles.questionTip} + placement="right-bottom" + > + + +
{getSyncLeftTime(estimate)}
{isLightClient && ( @@ -114,7 +125,34 @@ const SyncStatus = ({ } if (ConnectionStatus.Offline === connectionStatus) { - return {t('sync.sync-failed')} + return ( + + {t('sync.sync-failed')} + + {t('sync.sync-failed-detail')} +    + + + } + trigger="click" + placement="left-bottom" + showTriangle + tipClassName={styles.helpTip} + > + + + + ) } if (SyncStatusEnum.SyncNotStart === syncStatus) { diff --git a/packages/neuron-ui/src/components/SyncStatus/syncStatus.module.scss b/packages/neuron-ui/src/components/SyncStatus/syncStatus.module.scss index 0392091b75..130bb5597b 100755 --- a/packages/neuron-ui/src/components/SyncStatus/syncStatus.module.scss +++ b/packages/neuron-ui/src/components/SyncStatus/syncStatus.module.scss @@ -99,6 +99,37 @@ .title { font-size: 12px; font-weight: 400; + display: flex; + align-items: center; + + .question { + height: 14px; + flex: 1 1 auto; + svg { + margin-left: 4px; + cursor: pointer; + path { + stroke: var(--secondary-text-color); + } + &:hover { + path { + stroke: var(--primary-color); + } + } + } + } + + .questionTip { + top: calc(100% + 28px); + right: -36px; + width: 200px; + left: inherit; + } + + .leftTimeTip { + white-space: normal; + width: 200px; + } } .number { color: var(--third-text-color); @@ -110,8 +141,20 @@ .redDot { position: relative; - color: '#FF1E1E'; + color: var(--error-color); padding-left: 14px; + display: flex; + align-items: center; + + & > div { + height: 14px; + } + + svg { + margin-left: 4px; + cursor: pointer; + } + &::before { content: ''; height: 6px; @@ -122,4 +165,27 @@ top: calc(50% - 3px); background: var(--error-color); } + + .helpTip { + margin-top: 20px; + right: -10px !important; + + & > div:nth-last-child(1) { + right: 12px; + } + } + + .failedDetail { + white-space: normal; + min-width: 250px; + } + .openHelper { + font-weight: 500; + background: inherit; + color: var(--primary-color); + border: none; + padding: 0; + margin: 0; + cursor: pointer; + } } diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 037af66417..9e4244118b 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -36,7 +36,8 @@ "set-start-block-number": "Set start block number" }, "migrating": "migrating...", - "left-time": "Left Time" + "left-time": "Time Remaining", + "left-time-tip": "The time remaining is an estimate and is related to the current network and other factors, and is for reference only." }, "import-hardware": { "title": { @@ -687,7 +688,9 @@ "block-number": "block number", "syncing-balance": "Balance is updating", "slow": "Sync is slow", - "sync-failed": "Sync failed, please check network", + "sync-failed": "Synchronization failed", + "sync-failed-detail": "Synchronization failed due to network node or other problems.", + "learn-more": "Learn More", "sync-not-start": "Sync not started yet, please try to restart Neuron Wallet", "connecting": "Connecting" }, diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 0a62c02f52..7c9046316e 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -35,7 +35,8 @@ "set-start-block-number": "Establecer el número de bloque inicial" }, "migrating": "migrando...", - "left-time": "Tiempo restante" + "left-time": "Tiempo restante", + "left-time-tip": "El tiempo restante es una estimación y está relacionado con la red actual y otros factores, y es solo para referencia." }, "import-hardware": { "title": { @@ -670,7 +671,9 @@ "block-number": "número de bloque", "syncing-balance": "El saldo se está actualizando", "slow": "La sincronización es lenta", - "sync-failed": "Error de sincronización, por favor compruebe la red", + "sync-failed": "sincronización fallida", + "sync-failed-detail": "Debido a nodos de red u otros problemas, la sincronización ha fallado.", + "learn-more": "Saber más", "sync-not-start": "La sincronización no se ha iniciado todavía, por favor intente reiniciar Neuron Wallet", "connecting": "Conectando" }, diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index c355aabd83..9265e92776 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -36,7 +36,8 @@ "set-start-block-number": "Définir le numéro de bloc de départ" }, "migrating": "migration en cours...", - "left-time": "Temps restant" + "left-time": "Temps restant", + "left-time-tip": "Le temps restant est une estimation et est lié au réseau actuel et à d'autres facteurs, et est fourni à titre indicatif seulement." }, "import-hardware": { "title": { @@ -677,7 +678,9 @@ "block-number": "numéro de bloc", "syncing-balance": "La balance est en cours de mise à jour", "slow": "La synchronisation est lente", - "sync-failed": "Échec de la synchronisation, veuillez vérifier le réseau", + "sync-failed": "échec de la synchronisation", + "sync-failed-detail": "En raison de nœuds réseau ou d'autres problèmes, la synchronisation a échoué.", + "learn-more": "En savoir plus", "sync-not-start": "La synchronisation n'a pas encore commencé, veuillez essayer de redémarrer le Wallet Neuron", "connecting": "Connexion en cours" }, diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index e1de094b06..47ff57a27c 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -36,7 +36,8 @@ "set-start-block-number": "設置同步起始區塊" }, "migrating": "正在數據遷移...", - "left-time": "剩余時間" + "left-time": "剩余時間", + "left-time-tip": "剩余時間僅為估計值,與當前網絡和其他因素有關,僅供參考。" }, "import-hardware": { "title": { @@ -681,7 +682,9 @@ "block-number": "區塊高度", "syncing-balance": "區塊同步中, 正在獲取最新余額", "slow": "同步緩慢,請檢查網絡", - "sync-failed": "連接失敗, 請檢查網絡", + "sync-failed": "同步失敗", + "sync-failed-detail": "由於網絡節點或其他問題,同步失敗。", + "learn-more": "了解更多", "sync-not-start": "同步尚未開始, 請嘗試重啟", "connecting": "正在連接節點" }, diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 3355e80542..7d0a45f220 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -36,7 +36,8 @@ "set-start-block-number": "设置同步起始区块" }, "migrating": "正在数据迁移...", - "left-time": "剩余时间" + "left-time": "剩余时间", + "left-time-tip": "剩余时间仅为估计值,与当前网络和其他因素有关,仅供参考。" }, "import-hardware": { "title": { @@ -680,7 +681,9 @@ "block-number": "区块高度", "syncing-balance": "区块同步中, 正在获取最新余额", "slow": "同步缓慢,请检查网络", - "sync-failed": "连接失败, 请检查网络", + "sync-failed": "同步失败", + "sync-failed-detail": "由于网络节点或其他问题,同步失败。", + "learn-more": "了解更多", "sync-not-start": "同步尚未开始, 请尝试重启", "connecting": "正在连接节点" }, diff --git a/packages/neuron-ui/src/widgets/Icons/Question.svg b/packages/neuron-ui/src/widgets/Icons/Question.svg new file mode 100644 index 0000000000..c2e6b78f5c --- /dev/null +++ b/packages/neuron-ui/src/widgets/Icons/Question.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/neuron-ui/src/widgets/Icons/icon.tsx b/packages/neuron-ui/src/widgets/Icons/icon.tsx index 806510ad82..c8a1f9bd2e 100644 --- a/packages/neuron-ui/src/widgets/Icons/icon.tsx +++ b/packages/neuron-ui/src/widgets/Icons/icon.tsx @@ -57,6 +57,7 @@ import { ReactComponent as DeleteSvg } from './Delete.svg' import { ReactComponent as ImportKeystoreSvg } from './SoftWalletImportKeystore.svg' import { ReactComponent as ImportHardwareSvg } from './HardWalletImport.svg' import { ReactComponent as DepositTimeSortSvg } from './DepositTimeSort.svg' +import { ReactComponent as QuestionSvg } from './Question.svg' import styles from './icon.module.scss' @@ -130,3 +131,4 @@ export const Delete = WrapSvg(DeleteSvg) export const ImportKeystore = WrapSvg(ImportKeystoreSvg) export const ImportHardware = WrapSvg(ImportHardwareSvg) export const DepositTimeSort = WrapSvg(DepositTimeSortSvg) +export const Question = WrapSvg(QuestionSvg) diff --git a/packages/neuron-ui/src/widgets/Tooltip/tooltip.module.scss b/packages/neuron-ui/src/widgets/Tooltip/tooltip.module.scss index 6c4d88ffd1..111bd6d1d3 100644 --- a/packages/neuron-ui/src/widgets/Tooltip/tooltip.module.scss +++ b/packages/neuron-ui/src/widgets/Tooltip/tooltip.module.scss @@ -251,25 +251,25 @@ $placements: left, right, top, bottom, left-top, right-top, left-bottom, right-b .tipWithNode { position: relative; - .tip { + > .tip { @include tip; } &[data-type='always-dark'] { - .tip { + > .tip { background: #000000; color: #ffffff; } } &[data-trigger='click'] { - &[data-tip-show='true'] .tip { + &[data-tip-show='true'] > .tip { display: block; } } @each $placement in $placements { - @include tip-placement($placement, ' .tip'); + @include tip-placement($placement, '> .tip'); } &[data-trigger='hover'] { @@ -286,14 +286,14 @@ $placements: left, right, top, bottom, left-top, right-top, left-bottom, right-b &[data-has-trigger='true'] { &[data-placement='bottom'] { &:not([data-trigger-next-to-child='true']) { - & .tip { + & > .tip { top: calc(100% + 20px); } } } &[data-placement='top'] { &[data-trigger-next-to-child='true'] { - & .tip { + & > .tip { margin-bottom: 2px; } } From 4a4aa6bbbf0371c137f7b8d9dd1cca517e191ae0 Mon Sep 17 00:00:00 2001 From: Tom Wang Date: Tue, 21 May 2024 16:34:09 +0800 Subject: [PATCH 02/57] ci: upgrade github actions version (#3161) Co-authored-by: Chen Yu --- .github/workflows/check-code-style.yml | 2 +- .github/workflows/check_storybook.yml | 2 +- .github/workflows/package.yml | 4 ++-- .github/workflows/package_for_test.yml | 8 ++++---- .github/workflows/unit_tests.yml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/check-code-style.yml b/.github/workflows/check-code-style.yml index 87de0e3831..3584d79ce5 100644 --- a/.github/workflows/check-code-style.yml +++ b/.github/workflows/check-code-style.yml @@ -18,7 +18,7 @@ jobs: cache: "yarn" - name: Restore - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules diff --git a/.github/workflows/check_storybook.yml b/.github/workflows/check_storybook.yml index 1f351e7fb1..82aded4a8d 100644 --- a/.github/workflows/check_storybook.yml +++ b/.github/workflows/check_storybook.yml @@ -35,7 +35,7 @@ jobs: cache: "yarn" - name: Restore - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index e4a0171d42..0b74b02f2f 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -42,7 +42,7 @@ jobs: cache: "yarn" - name: Restore - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -51,7 +51,7 @@ jobs: - name: Add msbuild to PATH if: matrix.os == 'windows-2019' - uses: microsoft/setup-msbuild@v1.3.1 + uses: microsoft/setup-msbuild@v2 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: "true" diff --git a/.github/workflows/package_for_test.yml b/.github/workflows/package_for_test.yml index 757811fc6d..a475701b0c 100644 --- a/.github/workflows/package_for_test.yml +++ b/.github/workflows/package_for_test.yml @@ -53,7 +53,7 @@ jobs: cache: "yarn" - name: Restore - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -62,7 +62,7 @@ jobs: - name: Add msbuild to PATH if: matrix.os == 'windows-2019' - uses: microsoft/setup-msbuild@v1.3.1 + uses: microsoft/setup-msbuild@v2 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: "true" @@ -225,7 +225,7 @@ jobs: - name: Comment by pull request comment event if: ${{ github.event_name == 'issue_comment' }} - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ github.event.comment.id }} body: | @@ -242,7 +242,7 @@ jobs: steps: - name: Comment by pull request comment event when package failed if: ${{ github.event_name == 'issue_comment' }} - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ github.event.comment.id }} body: Packageing failed in [${{ github.run_id }}](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}). @${{ github.event.comment.user.login }} diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index e77a974953..334bc4c2f9 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -35,7 +35,7 @@ jobs: cache: "yarn" - name: Restore - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules From 590dc0d8693122d534dcc00debccd893624260cc Mon Sep 17 00:00:00 2001 From: Tom Wang Date: Tue, 21 May 2024 19:17:42 +0800 Subject: [PATCH 03/57] refactor: refactor models/multisig with lumos (#3156) * refactor: refactor models/multisig with lumos Signed-off-by: Tom Wang * refactor: move BLAKE160_HEX_LENGTH to blake2b.ts Signed-off-by: Tom Wang * feat: Add help link for sync failed. Signed-off-by: Tom Wang * ci: upgrade github actions version (#3161) Co-authored-by: Chen Yu Signed-off-by: Tom Wang --------- Signed-off-by: Tom Wang Co-authored-by: yanguoyu <841185308@qq.com> Co-authored-by: Chen Yu --- packages/neuron-wallet/src/models/blake2b.ts | 2 + packages/neuron-wallet/src/models/multisig.ts | 49 ++++++------------- .../src/services/tx/transaction-generator.ts | 6 +-- .../tests/models/multi-sign.test.ts | 6 +-- .../tests/services/cells.test.ts | 4 +- .../services/tx/transaction-generator.test.ts | 27 +++++----- 6 files changed, 37 insertions(+), 57 deletions(-) diff --git a/packages/neuron-wallet/src/models/blake2b.ts b/packages/neuron-wallet/src/models/blake2b.ts index 7dc99b6c7e..33c01368df 100644 --- a/packages/neuron-wallet/src/models/blake2b.ts +++ b/packages/neuron-wallet/src/models/blake2b.ts @@ -1,6 +1,8 @@ import { CKBHasher } from '@ckb-lumos/base/lib/utils' import { bytes } from '@ckb-lumos/codec' +export const BLAKE160_HEX_LENGTH = 42 + export default class Blake2b { private blake2b: CKBHasher diff --git a/packages/neuron-wallet/src/models/multisig.ts b/packages/neuron-wallet/src/models/multisig.ts index 39f2153d41..6d62aaf6a6 100644 --- a/packages/neuron-wallet/src/models/multisig.ts +++ b/packages/neuron-wallet/src/models/multisig.ts @@ -1,6 +1,8 @@ import { MultisigPrefixError } from '../exceptions' -import Blake2b from './blake2b' import SystemScriptInfo from './system-script-info' +import { since } from '@ckb-lumos/base' +import { bytes, number } from '@ckb-lumos/codec' +import Blake2b, { BLAKE160_HEX_LENGTH } from './blake2b' export interface MultisigPrefix { S: string @@ -11,7 +13,7 @@ export interface MultisigPrefix { export default class Multisig { // 1 epoch = 4h = 240min - EPOCH_MINUTES = 240 + static EPOCH_MINUTES = 240 static defaultS: string = '00' @@ -24,32 +26,22 @@ export default class Multisig { static hash(blake160s: string[], r: number = 0, m: number = 1, n: number = 1): string { const serializeResult = Multisig.serialize(blake160s, r, m, n) - return Blake2b.digest(serializeResult).slice(0, 42) + return Blake2b.digest(serializeResult).slice(0, BLAKE160_HEX_LENGTH) } - since(minutes: number, headerEpoch: string): string { + static since(minutes: number, headerEpoch: string): string { if (minutes < 0) { throw new Error("minutes to calculate since can't be less than 0") } - const currentEpochInfo = this.parseEpoch(BigInt(headerEpoch)) - const totalMinutes = - minutes + - parseInt( - ( - (parseInt(currentEpochInfo.index.toString()) / parseInt(currentEpochInfo.length.toString())) * - this.EPOCH_MINUTES - ).toString() - ) + const currentEpoch = since.parseEpoch(headerEpoch) + const totalMinutes = minutes + Math.floor((currentEpoch.index / currentEpoch.length) * this.EPOCH_MINUTES) const leftMinutes = totalMinutes % this.EPOCH_MINUTES - const epochs: bigint = - BigInt(parseInt((totalMinutes / this.EPOCH_MINUTES).toString(), 10)) + currentEpochInfo.number - const result = this.epochSince(BigInt(this.EPOCH_MINUTES), BigInt(leftMinutes), epochs) - const buf = Buffer.alloc(8) - buf.writeBigUInt64LE(result) - return `0x${buf.toString('hex')}` + const epochs = Math.floor(totalMinutes / this.EPOCH_MINUTES) + currentEpoch.number + const result = this.epochSince(BigInt(this.EPOCH_MINUTES), BigInt(leftMinutes), BigInt(epochs)) + return bytes.hexify(number.Uint64LE.pack(result)) } - args(blake160: string, minutes: number, headerEpoch: string): string { + static args(blake160: string, minutes: number, headerEpoch: string): string { return Multisig.hash([blake160]) + this.since(minutes, headerEpoch).slice(2) } @@ -57,25 +49,14 @@ export default class Multisig { return SystemScriptInfo.generateMultiSignScript(Multisig.hash(blake160s, r, m, n)) } - parseSince(args: string): bigint { - const str = args.slice(42) - const buf = Buffer.from(str, 'hex') - const sin: bigint = buf.readBigUInt64LE() - return sin + static parseSince(args: string): bigint { + return number.Uint64LE.unpack(`0x${args.slice(BLAKE160_HEX_LENGTH)}`).toBigInt() } - private epochSince(length: bigint, index: bigint, number: bigint): bigint { + private static epochSince(length: bigint, index: bigint, number: bigint): bigint { return (BigInt(0x20) << BigInt(56)) + (length << BigInt(40)) + (index << BigInt(24)) + number } - private parseEpoch(epoch: bigint) { - return { - length: (epoch >> BigInt(40)) & BigInt(0xffff), - index: (epoch >> BigInt(24)) & BigInt(0xffff), - number: epoch & BigInt(0xffffff), - } - } - private static getMultisigParamsHex(v: number) { if (v < 0 || v > 255) { throw new MultisigPrefixError() diff --git a/packages/neuron-wallet/src/services/tx/transaction-generator.ts b/packages/neuron-wallet/src/services/tx/transaction-generator.ts index 3164d353cc..78746dc8b5 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-generator.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-generator.ts @@ -212,7 +212,7 @@ export class TransactionGenerator { if (date) { const blake160 = lockScript.args const minutes: number = +((BigInt(date) - BigInt(tipHeaderTimestamp)) / BigInt(1000 * 60)).toString() - const script = SystemScriptInfo.generateMultiSignScript(new Multisig().args(blake160, +minutes, tipHeaderEpoch)) + const script = SystemScriptInfo.generateMultiSignScript(Multisig.args(blake160, +minutes, tipHeaderEpoch)) output.setLock(script) output.setMultiSignBlake160(script.args.slice(0, 42)) } @@ -333,7 +333,7 @@ export class TransactionGenerator { const blake160 = lockScript.args const minutes: number = +((BigInt(date) - BigInt(tipHeaderTimestamp)) / BigInt(1000 * 60)).toString() const script: Script = SystemScriptInfo.generateMultiSignScript( - new Multisig().args(blake160, minutes, tipHeaderEpoch) + Multisig.args(blake160, minutes, tipHeaderEpoch) ) output.setLock(script) output.setMultiSignBlake160(script.args.slice(0, 42)) @@ -625,7 +625,7 @@ export class TransactionGenerator { lock: lockScript, }) - const since = new Multisig().parseSince(prevOutput.lock.args) + const since = Multisig.parseSince(prevOutput.lock.args) const input = new Input(outPoint, since.toString(), prevOutput.capacity, prevOutput.lock) const tx = Transaction.fromObject({ diff --git a/packages/neuron-wallet/tests/models/multi-sign.test.ts b/packages/neuron-wallet/tests/models/multi-sign.test.ts index bf9416a94b..97fe34b0e6 100644 --- a/packages/neuron-wallet/tests/models/multi-sign.test.ts +++ b/packages/neuron-wallet/tests/models/multi-sign.test.ts @@ -15,13 +15,13 @@ describe('MultiSign Test', () => { const serialized = '0x0000010136c329ed630d6ce750712a477543672adab57f4c' it('since', () => { - const since = new Multisig().since(minutes, headerEpoch) + const since = Multisig.since(minutes, headerEpoch) expect(since).toEqual(expectedSince) }) it('since, minutes < 0', () => { expect(() => { - new Multisig().since(-1, headerEpoch) + Multisig.since(-1, headerEpoch) }).toThrowError() }) @@ -47,7 +47,7 @@ describe('MultiSign Test', () => { }) it('args', () => { - const args = new Multisig().args(bob.blake160, minutes, headerEpoch) + const args = Multisig.args(bob.blake160, minutes, headerEpoch) expect(args).toEqual(expectedArgs) }) }) diff --git a/packages/neuron-wallet/tests/services/cells.test.ts b/packages/neuron-wallet/tests/services/cells.test.ts index 8d3dcd17d1..813a373f69 100644 --- a/packages/neuron-wallet/tests/services/cells.test.ts +++ b/packages/neuron-wallet/tests/services/cells.test.ts @@ -1676,7 +1676,7 @@ describe('CellsService', () => { '61', OutputStatus.Sent, false, - SystemScriptInfo.generateMultiSignScript(new Multisig().args(bob.blake160, +10, '0x7080291000049')) + SystemScriptInfo.generateMultiSignScript(Multisig.args(bob.blake160, +10, '0x7080291000049')) ) await expect(CellsService.getLiveOrSentCellByWalletId(bob.walletId)).resolves.toHaveLength(1) }) @@ -1718,7 +1718,7 @@ describe('CellsService', () => { it('MULTI_LOCK_TIME', () => { const output = Output.fromObject({ capacity: '1000', - lock: SystemScriptInfo.generateMultiSignScript(new Multisig().args(bob.blake160, +10, '0x7080291000049')), + lock: SystemScriptInfo.generateMultiSignScript(Multisig.args(bob.blake160, +10, '0x7080291000049')), }) expect(CellsService.getCellLockType(output)).toBe(LockScriptCategory.MULTI_LOCK_TIME) }) diff --git a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts index 3644000e9d..c5217e5f11 100644 --- a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts +++ b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts @@ -1,4 +1,5 @@ import { when } from 'jest-when' +import { since } from '@ckb-lumos/base' import { bytes } from '@ckb-lumos/codec' import OutputEntity from '../../../src/database/chain/entities/output' import InputEntity from '../../../src/database/chain/entities/input' @@ -470,13 +471,11 @@ describe('TransactionGenerator', () => { const multiSignOutput = tx.outputs.find(o => o.lock.codeHash === SystemScriptInfo.MULTI_SIGN_CODE_HASH) expect(multiSignOutput).toBeDefined() - const multiSign = new Multisig() - const epoch = multiSign.parseSince(multiSignOutput!.lock.args) - // @ts-ignore: Private method - const parsedEpoch = multiSign.parseEpoch(epoch) - expect(parsedEpoch.number).toEqual(BigInt(5)) - expect(parsedEpoch.length).toEqual(BigInt(240)) - expect(parsedEpoch.index).toEqual(BigInt(43)) + const epoch = Multisig.parseSince(multiSignOutput!.lock.args) + const parsedEpoch = since.parseEpoch(epoch) + expect(parsedEpoch.number).toEqual(5) + expect(parsedEpoch.length).toEqual(240) + expect(parsedEpoch.index).toEqual(43) }) }) }) @@ -733,13 +732,11 @@ describe('TransactionGenerator', () => { expect(tx.outputs[0].lock.codeHash).toEqual(SystemScriptInfo.MULTI_SIGN_CODE_HASH) - const multiSign = new Multisig() - const epoch = multiSign.parseSince(tx.outputs[0].lock.args) - // @ts-ignore: Private method - const parsedEpoch = multiSign.parseEpoch(epoch) - expect(parsedEpoch.number).toEqual(BigInt(5)) - expect(parsedEpoch.length).toEqual(BigInt(240)) - expect(parsedEpoch.index).toEqual(BigInt(43)) + const epoch = Multisig.parseSince(tx.outputs[0].lock.args) + const parsedEpoch = since.parseEpoch(epoch) + expect(parsedEpoch.number).toEqual(5) + expect(parsedEpoch.length).toEqual(240) + expect(parsedEpoch.index).toEqual(43) }) }) @@ -1045,7 +1042,7 @@ describe('TransactionGenerator', () => { describe('generateWithdrawMultiSignTx', () => { const prevOutput = Output.fromObject({ capacity: toShannon('1000'), - lock: SystemScriptInfo.generateMultiSignScript(new Multisig().args(bob.lockScript.args, 100, '0x7080018000001')), + lock: SystemScriptInfo.generateMultiSignScript(Multisig.args(bob.lockScript.args, 100, '0x7080018000001')), }) const outPoint = OutPoint.fromObject({ txHash: '0x' + '0'.repeat(64), From a54408074c61886e6f220b1d2474049f80ef9df4 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, 24 May 2024 15:10:47 +0800 Subject: [PATCH 04/57] Merge master into dev (#3168) * chore: Update ckb node assume valid target for rc/v0.116.0. * chore: update versions and changelogs * fix: Use hex to avoid unexpected string. (#3162) * fix: Use lumos to init Buffer (#3164) * Update Neuron compatibility table (#3158) feat: Update Neuron compatibility table Co-authored-by: Keith-CY * fix: amend transaction when consume cells (#3166) * docs: add video introduction to v0.116.0 in changelog --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Chen Yu Co-authored-by: Keith-CY Co-authored-by: devchenyan --- CHANGELOG.md | 36 +++++++++++++++++++ compatible.json | 16 +++++++++ lerna.json | 2 +- package.json | 2 +- packages/neuron-ui/package.json | 2 +- .../src/components/AmendSend/index.tsx | 31 +++++++++++----- packages/neuron-wallet/.env | 4 +-- packages/neuron-wallet/package.json | 4 +-- .../neuron-wallet/src/controllers/wallets.ts | 4 +-- .../neuron-wallet/src/services/settings.ts | 5 ++- .../src/services/sign-message.ts | 5 +-- 11 files changed, 90 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a35aea95a9..3c71658c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +# 0.116.0 (2024-05-24) + +### CKB Node & Light Client + +- [CKB@v0.116.1](https://github.com/nervosnetwork/ckb/releases/tag/v0.116.1) was released on May. 11st, 2024. This version of CKB node is now bundled and preconfigured in Neuron. +- [CKB Light Client@v0.3.7](https://github.com/nervosnetwork/ckb-light-client/releases/tag/v0.3.7) was released on Apr. 13th, 2024. This version of CKB Light Client is now bundled and preconfigured in Neuron + +### Assumed valid target + +Block before `0x6dd077b407d019a0bce0cbad8c34e69a524ae4b2599b9feda2c7491f3559d32c`(at height `13,007,704`) will be skipped in validation.(https://github.com/nervosnetwork/neuron/pull/3157) + +--- + +[![Neuron@v0.116.0](https://github.com/Magickbase/neuron-public-issues/assets/7271329/ec10aa01-47fe-47a3-9636-3d4e86fc6c9b)](https://youtu.be/QXv8by2C8zU) + +YouTube: https://youtu.be/QXv8by2C8zU + +--- + +## New features + +- 3134: Support 'replace-by-fee' nervos dao transactions and sudt transactions.(@devchenyan) +- 3144: Reduce size of light client log in debug information and reveal start-block-number in log.(@yanguoyu) +- 3064: Support locking window by pin code.(@yanguoyu) +- 3131: Add detailed result for nervos dao transaction.(@devchenyan) + +## Bug fixes + +- 3121: Locate the first transaction on Explorer directly when users want to set the start-block-number for light client.(@yanguoyu) +- 3101: Show migration instruction properly.(@devchenyan) +- 3062: Migrate legacy ACP to active ACP account(@yanguoyu) +- 3141: Fix some issues about light client synchronizaiton.(@yanguoyu) +- 3120: Remove all sync data when start-block-number is set less than before.(@yanguoyu) + +**Full Changelog**: https://github.com/nervosnetwork/neuron/compare/v0.114.3...v0.116.0 + # 0.114.3 (2024-04-16) ### CKB Node & Light Client diff --git a/compatible.json b/compatible.json index 028ff3cb04..b2865b7a18 100644 --- a/compatible.json +++ b/compatible.json @@ -99,6 +99,22 @@ "0.3", "0.2" ] + }, + "0.116": { + "full": [ + "0.116", + "0.115", + "0.114", + "0.113", + "0.112", + "0.111", + "0.110", + "0.109" + ], + "light": [ + "0.3", + "0.2" + ] } } } diff --git a/lerna.json b/lerna.json index 0e77734cb4..922158bfd4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.114.3", + "version": "0.116.0", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/package.json b/package.json index 62c7a5ad1b..e8c55808df 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "neuron", "productName": "Neuron", "description": "CKB Neuron Wallet", - "version": "0.114.3", + "version": "0.116.0", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index 5c6ce190e1..b98ed97822 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -1,6 +1,6 @@ { "name": "neuron-ui", - "version": "0.114.3", + "version": "0.116.0", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/src/components/AmendSend/index.tsx b/packages/neuron-ui/src/components/AmendSend/index.tsx index 92eb75675d..7ba3873d1e 100644 --- a/packages/neuron-ui/src/components/AmendSend/index.tsx +++ b/packages/neuron-ui/src/components/AmendSend/index.tsx @@ -96,6 +96,15 @@ const AmendSend = () => { return '' } + const inputsCapacity = useMemo(() => { + if (transaction) { + return transaction.inputs.reduce((total, cur) => { + return total + BigInt(cur.capacity || '0') + }, BigInt(0)) + } + return undefined + }, [transaction]) + const items: { address: string amount: string @@ -103,21 +112,25 @@ const AmendSend = () => { isLastOutput: boolean output: State.DetailedOutput }[] = useMemo(() => { - if (transaction && transaction.outputs.length) { + if (transaction && transaction.outputs.length && inputsCapacity) { const lastOutputAddress = getLastOutputAddress(transaction.outputs) return transaction.outputs.map(output => { const address = scriptToAddress(output.lock, { isMainnet }) + const capacity = + transaction.outputs.length === 1 && address === lastOutputAddress + ? (inputsCapacity - fee).toString() + : output.capacity return { - capacity: output.capacity, + capacity, address, output, - amount: shannonToCKBFormatter(output.capacity || '0'), + amount: shannonToCKBFormatter(capacity || '0'), isLastOutput: address === lastOutputAddress, } }) } return [] - }, [transaction?.outputs]) + }, [transaction?.outputs, inputsCapacity, fee]) const outputsCapacity = useMemo(() => { const outputList = items.length === 1 ? items : items.filter(item => !item.isLastOutput) @@ -129,15 +142,15 @@ const AmendSend = () => { const totalAmount = shannonToCKBFormatter(outputsCapacity.toString()) const lastOutputsCapacity = useMemo(() => { - if (transaction) { - const inputsCapacity = transaction.inputs.reduce((total, cur) => { - return total + BigInt(cur.capacity || '0') - }, BigInt(0)) + if (inputsCapacity) { + if (items.length === 1) { + return BigInt(items[0].capacity || '0') + } return inputsCapacity - outputsCapacity - fee } return undefined - }, [transaction, fee, outputsCapacity]) + }, [inputsCapacity, fee, outputsCapacity, items]) useEffect(() => { if (transaction && lastOutputsCapacity !== undefined) { diff --git a/packages/neuron-wallet/.env b/packages/neuron-wallet/.env index f32862920f..3c4d9296b8 100644 --- a/packages/neuron-wallet/.env +++ b/packages/neuron-wallet/.env @@ -117,5 +117,5 @@ DAO_CODE_HASH=0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e MULTISIG_CODE_HASH=0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8 # CKB NODE OPTIONS -CKB_NODE_ASSUME_VALID_TARGET='0x9443ad8da9172d484367bc5467988cba7a0c46028398309edfdda7d2d79be897' -CKB_NODE_DATA_SIZE=53 +CKB_NODE_ASSUME_VALID_TARGET='0x6dd077b407d019a0bce0cbad8c34e69a524ae4b2599b9feda2c7491f3559d32c' +CKB_NODE_DATA_SIZE=54 diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 77479adeae..cad2f98bff 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -3,7 +3,7 @@ "productName": "Neuron", "description": "CKB Neuron Wallet", "homepage": "https://www.nervos.org/", - "version": "0.114.3", + "version": "0.116.0", "private": true, "author": { "name": "Nervos Core Dev", @@ -97,7 +97,7 @@ "electron-builder": "24.9.1", "electron-devtools-installer": "3.2.0", "jest-when": "3.6.0", - "neuron-ui": "0.114.3", + "neuron-ui": "0.116.0", "typescript": "5.3.3" } } diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index 29bff5030e..db66030bfa 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -168,8 +168,8 @@ export default class WalletsController { const keystoreObject = Keystore.fromJson(keystore) const masterPrivateKey = keystoreObject.extendedPrivateKey(password) const masterKeychain = new Keychain( - Buffer.from(masterPrivateKey.privateKey, 'hex'), - Buffer.from(masterPrivateKey.chainCode, 'hex') + Buffer.from(bytes.bytify(masterPrivateKey.privateKey)), + Buffer.from(bytes.bytify(masterPrivateKey.chainCode)) ) const accountKeychain = masterKeychain.derivePath(AccountExtendedPublicKey.ckbAccountPath) const accountExtendedPublicKey = new AccountExtendedPublicKey( diff --git a/packages/neuron-wallet/src/services/settings.ts b/packages/neuron-wallet/src/services/settings.ts index d482390249..f836ba5e0e 100644 --- a/packages/neuron-wallet/src/services/settings.ts +++ b/packages/neuron-wallet/src/services/settings.ts @@ -88,7 +88,7 @@ export default class SettingsService extends Store { private generateEncryptString(str: string) { if (safeStorage.isEncryptionAvailable()) { - return safeStorage.encryptString(str).toString('utf-8') + return safeStorage.encryptString(str).toString('hex') } const hash = crypto.createHash('sha256') hash.update(str) @@ -108,6 +108,9 @@ export default class SettingsService extends Store { } verifyLockWindowPassword(password: string) { + if (safeStorage.isEncryptionAvailable()) { + return safeStorage.decryptString(Buffer.from(this.lockWindowInfo.encryptedPassword!, 'hex')) === password + } const encryptedPassword = this.generateEncryptString(password) return SettingsService.getInstance().lockWindowInfo.encryptedPassword === encryptedPassword } diff --git a/packages/neuron-wallet/src/services/sign-message.ts b/packages/neuron-wallet/src/services/sign-message.ts index 8d3bfd562d..aeaf32dca7 100644 --- a/packages/neuron-wallet/src/services/sign-message.ts +++ b/packages/neuron-wallet/src/services/sign-message.ts @@ -6,6 +6,7 @@ import { ec as EC } from 'elliptic' import { AddressNotFound } from '../exceptions' import HardwareWalletService from './hardware' import AddressParser from '../models/address-parser' +import { bytes } from '@ckb-lumos/codec' export default class SignMessage { static GENERATE_COUNT = 100 @@ -91,8 +92,8 @@ export default class SignMessage { private static getPrivateKey(wallet: Wallet, path: string, password: string): string { const masterPrivateKey = wallet.loadKeystore().extendedPrivateKey(password) const masterKeychain = new Keychain( - Buffer.from(masterPrivateKey.privateKey, 'hex'), - Buffer.from(masterPrivateKey.chainCode, 'hex') + Buffer.from(bytes.bytify(masterPrivateKey.privateKey)), + Buffer.from(bytes.bytify(masterPrivateKey.chainCode)) ) return `0x${masterKeychain.derivePath(path).privateKey.toString('hex')}` From bed901be78d39e3edcf15032c6bacc44c8d9023e 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, 24 May 2024 15:21:08 +0800 Subject: [PATCH 05/57] fix: Replace `Asset` to `Amount` and better 'Asset' data format (#3151) fix: Replace `Asset` to `Amount` --- .../components/FormattedTokenAmount/index.tsx | 24 +++++++----- .../src/components/History/index.tsx | 2 +- .../src/components/Overview/index.tsx | 2 +- packages/neuron-ui/src/locales/en.json | 4 +- packages/neuron-ui/src/locales/es.json | 4 +- packages/neuron-ui/src/locales/fr.json | 4 +- packages/neuron-ui/src/locales/zh-tw.json | 4 +- packages/neuron-ui/src/locales/zh.json | 4 +- packages/neuron-ui/src/types/App/index.d.ts | 1 + .../src/models/chain/transaction.ts | 11 +++++- .../src/services/tx/transaction-service.ts | 38 ++++++++++++++++--- 11 files changed, 69 insertions(+), 29 deletions(-) diff --git a/packages/neuron-ui/src/components/FormattedTokenAmount/index.tsx b/packages/neuron-ui/src/components/FormattedTokenAmount/index.tsx index 4a180ba15f..b31da32d3c 100644 --- a/packages/neuron-ui/src/components/FormattedTokenAmount/index.tsx +++ b/packages/neuron-ui/src/components/FormattedTokenAmount/index.tsx @@ -12,7 +12,7 @@ type FormattedTokenAmountProps = { item: State.Transaction; show: boolean; symbo type AmountProps = Omit & { sudtAmount?: string isReceive: boolean - amount: string + amount?: string symbolClassName?: string } @@ -30,18 +30,18 @@ const Amount = ({ sudtAmount, show, item, isReceive, amount, symbolClassName, sy ) : (
- - {amount} + + {amount ?? '--'} -  {symbol} +  {amount ? symbol : ''}
) } export const FormattedTokenAmount = ({ item, show, symbolClassName }: FormattedTokenAmountProps) => { - let amount = '--' + let amount: string | undefined let sudtAmount = '' - let copyText = amount + let copyText: string | undefined = amount let isReceive = false let symbol = '' @@ -61,16 +61,22 @@ export const FormattedTokenAmount = ({ item, show, symbolClassName }: FormattedT isReceive = !sudtAmount.includes('-') } } else { - amount = show ? `${shannonToCKBFormatter(item.value, true)}` : `${HIDE_BALANCE}` - isReceive = !amount.includes('-') + amount = show + ? `${shannonToCKBFormatter(item.nervosDao ? item.daoCapacity ?? '--' : item.value, true)}` + : `${HIDE_BALANCE}` + isReceive = !amount?.includes('-') copyText = `${amount} CKB` symbol = 'CKB' + if (item.nervosDao && item.daoCapacity === undefined) { + amount = undefined + copyText = undefined + } } } const props = { sudtAmount, show, item, isReceive, amount, symbolClassName, symbol } - return show ? ( + return show && copyText ? ( diff --git a/packages/neuron-ui/src/components/History/index.tsx b/packages/neuron-ui/src/components/History/index.tsx index 6e1f19c2d3..f012415c87 100644 --- a/packages/neuron-ui/src/components/History/index.tsx +++ b/packages/neuron-ui/src/components/History/index.tsx @@ -126,7 +126,7 @@ const History = () => { sortable: true, }, { - title: t('history.table.amount'), + title: t('history.table.asset'), dataIndex: 'amount', align: 'left', isBalance: true, diff --git a/packages/neuron-ui/src/components/Overview/index.tsx b/packages/neuron-ui/src/components/Overview/index.tsx index 9ce1a0a94b..2aaf350574 100644 --- a/packages/neuron-ui/src/components/Overview/index.tsx +++ b/packages/neuron-ui/src/components/Overview/index.tsx @@ -268,7 +268,7 @@ const Overview = () => { }, }, { - title: t('overview.amount'), + title: t('overview.asset'), dataIndex: 'amount', align: 'left', isBalance: true, diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 2a90be36e6..1842c982ef 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -142,7 +142,7 @@ "activity": "Activity", "datetime": "Date & Time", "status": "Status", - "amount": "Amount", + "asset": "Asset", "address": "Address", "sent": "Sent", "sending": "Sending", @@ -313,7 +313,7 @@ "name": "Wallet Name", "type": "Type", "balance": "Balance", - "amount": "Amount", + "asset": "Asset", "timestamp": "Time", "status": "Status", "operation": "Operation" diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 8c35cae3c0..3ac8e1f57f 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -135,7 +135,7 @@ "activity": "Actividad", "datetime": "Fecha y Hora", "status": "Estado", - "amount": "Cantidad", + "asset": "Activos", "address": "Dirección", "sent": "Enviado", "sending": "Enviando", @@ -305,7 +305,7 @@ "name": "Nombre de la Billetera", "type": "Tipo", "balance": "Saldo", - "amount": "Cantidad", + "asset": "activos", "timestamp": "Tiempo", "status": "Estado", "operation": "Operación" diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index ae9db42555..a609830ca1 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -142,7 +142,7 @@ "activity": "Activité", "datetime": "Date et heure", "status": "Statut", - "amount": "Montant", + "asset": "actifs", "address": "Adresse", "sent": "Envoyé", "sending": "Envoi", @@ -312,7 +312,7 @@ "name": "Nom du Wallet", "type": "Type", "balance": "Solde", - "amount": "Montant", + "asset": "actifs", "timestamp": "Heure", "status": "Statut", "operation": "Opération" diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 76946f85f3..8e61fb1c5d 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -136,7 +136,7 @@ "activity": "收支活動", "datetime": "時間", "status": "狀態", - "amount": "金額", + "asset": "資產", "address": "地址", "sent": "已發送", "sending": "正在發送", @@ -308,7 +308,7 @@ "name": "錢包名稱", "type": "類型", "balance": "余額", - "amount": "金額", + "asset": "資產", "timestamp": "時間", "status": "狀態", "operation": "操作" diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 55109b76e9..d0ad40e458 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -135,7 +135,7 @@ "activity": "收支活动", "datetime": "时间", "status": "状态", - "amount": "金额", + "asset": "资产", "address": "地址", "sent": "已发送", "sending": "正在发送", @@ -306,7 +306,7 @@ "name": "钱包名称", "type": "类型", "balance": "余额", - "amount": "金额", + "asset": "资产", "timestamp": "时间", "status": "状态", "operation": "操作" diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 76ce82827b..e9902f710c 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -19,6 +19,7 @@ declare namespace State { data: string } assetAccountType?: 'CKB' | 'sUDT' | string + daoCapacity?: string } interface DetailedInput { diff --git a/packages/neuron-wallet/src/models/chain/transaction.ts b/packages/neuron-wallet/src/models/chain/transaction.ts index 1f4a0d1223..a8942bb43c 100644 --- a/packages/neuron-wallet/src/models/chain/transaction.ts +++ b/packages/neuron-wallet/src/models/chain/transaction.ts @@ -82,6 +82,8 @@ export default class Transaction { public signatures: Signatures = {} public assetAccountType?: AssetAccountType + public daoCapacity?: string + constructor( version: string, cellDeps: CellDep[] = [], @@ -106,7 +108,8 @@ export default class Transaction { sudtInfo?: SudtInfo, nftType?: NFTInfo, signatures: Signatures = {}, - assetAccountType?: AssetAccountType + assetAccountType?: AssetAccountType, + daoCapacity?: string ) { this.cellDeps = cellDeps this.headerDeps = headerDeps @@ -133,6 +136,7 @@ export default class Transaction { this.sudtInfo = sudtInfo this.nftInfo = nftType this.signatures = signatures + this.daoCapacity = daoCapacity TypeCheckerUtils.hashChecker(...this.headerDeps, this.blockHash) TypeCheckerUtils.numberChecker( this.version, @@ -171,6 +175,7 @@ export default class Transaction { nftInfo, signatures = {}, assetAccountType, + daoCapacity, }: { version: string cellDeps?: CellDep[] @@ -196,6 +201,7 @@ export default class Transaction { nftInfo?: NFTInfo signatures?: Signatures assetAccountType?: AssetAccountType + daoCapacity?: string }): Transaction { return new Transaction( version, @@ -226,7 +232,8 @@ export default class Transaction { sudtInfo, nftInfo, signatures, - assetAccountType + assetAccountType, + daoCapacity ) } diff --git a/packages/neuron-wallet/src/services/tx/transaction-service.ts b/packages/neuron-wallet/src/services/tx/transaction-service.ts index eb5cf93dd3..8b43057ed9 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-service.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-service.ts @@ -256,6 +256,7 @@ export class TransactionsService { .addSelect('input.transactionHash', 'transactionHash') .addSelect('input.outPointTxHash', 'outPointTxHash') .addSelect('input.outPointIndex', 'outPointIndex') + .addSelect('input.data', 'data') .where( ` input.transactionHash IN (:...txHashes) AND @@ -266,7 +267,13 @@ export class TransactionsService { walletId: params.walletID, } ) - .getRawMany() + .getRawMany<{ + capacity: string + transactionHash: string + outPointTxHash: string + outPointIndex: string + data: string + }>() const outputs = await connection .getRepository(OutputEntity) @@ -284,7 +291,7 @@ export class TransactionsService { walletId: params.walletID, } ) - .getRawMany() + .getRawMany<{ capacity: string; transactionHash: string; daoData: string }>() const assetAccountInputs = await connection .getRepository(InputEntity) @@ -373,13 +380,17 @@ export class TransactionsService { ).filter(o => inputPreviousTxHashes.includes(o.txHash)) const sums = new Map() - const daoFlag = new Map() + const daoInfo = new Map() outputs.map(o => { const s = sums.get(o.transactionHash) || BigInt(0) sums.set(o.transactionHash, s + BigInt(o.capacity)) if (o.daoData) { - daoFlag.set(o.transactionHash, true) + if (daoInfo.has(o.transactionHash)) { + daoInfo.get(o.transactionHash)!.outputs.push(o) + } else { + daoInfo.set(o.transactionHash, { inputs: [], outputs: [o] }) + } } }) @@ -391,7 +402,11 @@ export class TransactionsService { return dc.txHash === i.outPointTxHash && dc.index === i.outPointIndex }) if (result) { - daoFlag.set(i.transactionHash, true) + if (daoInfo.has(i.transactionHash)) { + daoInfo.get(i.transactionHash)!.inputs.push(i) + } else { + daoInfo.set(i.transactionHash, { inputs: [i], outputs: [] }) + } } }) @@ -477,6 +492,16 @@ export class TransactionsService { nftInfo = { type: NFTType.Receive, data: receiveNFTCell.typeArgs! } } + const txDaoInfo = daoInfo.get(tx.hash) + let daoCapacity: string | undefined + if (txDaoInfo) { + if (txDaoInfo.inputs.length && !txDaoInfo.outputs.length) { + daoCapacity = txDaoInfo.inputs.reduce((pre, cur) => BigInt(cur.capacity) + pre, BigInt(value)).toString() + } else if (!txDaoInfo.inputs.length && txDaoInfo.outputs.length) { + daoCapacity = `-${txDaoInfo.outputs.reduce((pre, cur) => BigInt(cur.capacity) + pre, BigInt(0)).toString()}` + } + } + return Transaction.fromObject({ timestamp: tx.timestamp, value: value.toString(), @@ -484,7 +509,7 @@ export class TransactionsService { version: tx.version, type: txType, assetAccountType: assetAccountType, - nervosDao: daoFlag.get(tx.hash!), + nervosDao: !!txDaoInfo, status: tx.status, description: tx.description, createdAt: tx.createdAt, @@ -492,6 +517,7 @@ export class TransactionsService { blockNumber: tx.blockNumber, sudtInfo: sudtInfo, nftInfo: nftInfo, + daoCapacity, }) }) ) From b44892adf2784a7e58b99e83defac134c9862dd0 Mon Sep 17 00:00:00 2001 From: Tom Wang Date: Mon, 27 May 2024 10:51:11 +0800 Subject: [PATCH 06/57] refactor: replace LumosCell, LumosCellQuery with Cell, QueryOptions (#3163) * refactor: replace LumosCell, LumosCellQuery with Cell, QueryOptions * refactor: use type CellWithOutPoint for live cells data --- .../src/block-sync-renderer/index.ts | 6 ++-- .../block-sync-renderer/sync/synchronizer.ts | 36 +++---------------- .../src/block-sync-renderer/task.ts | 4 +-- .../src/models/chain/live-cell.ts | 6 ++-- .../src/services/live-cell-service.ts | 12 +++---- .../block-sync-renderer/synchronizer.test.ts | 34 +++++++++--------- .../tests/models/chain/live-cell.test.ts | 5 ++- .../services/tx/transaction-generator.test.ts | 14 ++++---- 8 files changed, 44 insertions(+), 73 deletions(-) diff --git a/packages/neuron-wallet/src/block-sync-renderer/index.ts b/packages/neuron-wallet/src/block-sync-renderer/index.ts index cf690f014b..19b0394649 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/index.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/index.ts @@ -9,7 +9,7 @@ import DataUpdateSubject from '../models/subjects/data-update' import AddressCreatedSubject from '../models/subjects/address-created-subject' import WalletDeletedSubject from '../models/subjects/wallet-deleted-subject' import TxDbChangedSubject from '../models/subjects/tx-db-changed-subject' -import { LumosCellQuery, LumosCell } from './sync/synchronizer' +import { type Cell, type QueryOptions } from '@ckb-lumos/base' import { WorkerMessage, StartParams, QueryIndexerParams } from './task' import logger from '../utils/logger' import CommonUtils from '../utils/common' @@ -88,7 +88,7 @@ export const switchToNetwork = async (newNetwork: Network, reconnected = false, await resetSyncTaskQueue.asyncPush(shouldSync) } -export const queryIndexer = async (query: LumosCellQuery): Promise => { +export const queryIndexer = async (query: QueryOptions): Promise => { const _child = child if (!_child) { return [] @@ -102,7 +102,7 @@ export const queryIndexer = async (query: LumosCellQuery): Promise return registerRequest(_child, msg).catch(err => { logger.error(`Sync:\tfailed to register query indexer task`, err) return [] - }) as Promise + }) as Promise } export const createBlockSyncTask = async () => { diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts index b8d1a789a2..01fcfa3a12 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts @@ -1,7 +1,7 @@ import { Subject } from 'rxjs' import { queue, QueueObject } from 'async' +import { type QueryOptions } from '@ckb-lumos/base' import { Indexer as CkbIndexer, CellCollector } from '@ckb-lumos/ckb-indexer' -import { QueryOptions } from '@ckb-lumos/base' import AddressMeta from '../../database/address/meta' import { Address } from '../../models/address' import { SyncAddressType } from '../../database/chain/entities/sync-progress' @@ -14,34 +14,6 @@ export interface BlockTips { indexerTipNumber: number | undefined } -export interface LumosCellQuery { - lock: CKBComponents.Script | null - type: CKBComponents.Script | null - data: string | null -} - -export interface LumosCell { - blockHash: string - outPoint: { - txHash: string - index: string - } - cellOutput: { - capacity: string - lock: { - codeHash: string - args: string - hashType: string - } - type?: { - codeHash: string - args: string - hashType: string - } - } - data?: string -} - export interface AppendScript { walletId: string script: CKBComponents.Script @@ -57,7 +29,7 @@ export abstract class Synchronizer { protected processingBlockNumber?: string protected addressesByWalletId: Map = new Map() protected pollingIndexer: boolean = false - private indexerQueryQueue: QueueObject | undefined + private indexerQueryQueue: QueueObject | undefined protected _needGenerateAddress: boolean = false abstract connect(): Promise @@ -155,7 +127,7 @@ export abstract class Synchronizer { return false } - public async getLiveCellsByScript(query: LumosCellQuery) { + public async getLiveCellsByScript(query: QueryOptions) { return new Promise((resolve, reject) => { this.indexerQueryQueue!.push(query, (err: any, result: unknown) => { if (err) { @@ -166,7 +138,7 @@ export abstract class Synchronizer { }) } - private async collectLiveCellsByScript(query: LumosCellQuery) { + private async collectLiveCellsByScript(query: QueryOptions) { const { lock, type, data } = query if (!lock && !type) { throw new Error('at least one parameter is required') diff --git a/packages/neuron-wallet/src/block-sync-renderer/task.ts b/packages/neuron-wallet/src/block-sync-renderer/task.ts index e10291360e..4d5c4b9d5a 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/task.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/task.ts @@ -1,4 +1,4 @@ -import type { LumosCellQuery } from './sync/synchronizer' +import { type QueryOptions } from '@ckb-lumos/base' import initConnection from '../database/chain/ormconfig' import { register as registerTxStatusListener } from './tx-status-listener' import SyncQueue from './sync/queue' @@ -35,7 +35,7 @@ export interface StartParams { nodeType: SyncQueueParams[3] } -export type QueryIndexerParams = LumosCellQuery +export type QueryIndexerParams = QueryOptions export const listener = async ({ type, id, channel, message }: WorkerMessage) => { if (type === 'kill') { diff --git a/packages/neuron-wallet/src/models/chain/live-cell.ts b/packages/neuron-wallet/src/models/chain/live-cell.ts index c57fb25fed..9a7cda74a2 100644 --- a/packages/neuron-wallet/src/models/chain/live-cell.ts +++ b/packages/neuron-wallet/src/models/chain/live-cell.ts @@ -1,6 +1,6 @@ import Script, { ScriptHashType } from './script' import OutPoint from './out-point' -import { LumosCell } from '../../block-sync-renderer/sync/synchronizer' +import { type Cell, type OutPoint as IOutPoint } from '@ckb-lumos/base' const LUMOS_HASH_TYPE_MAP: Record = { type: ScriptHashType.Type, @@ -8,6 +8,8 @@ const LUMOS_HASH_TYPE_MAP: Record = { data: ScriptHashType.Data, } +export type CellWithOutPoint = Cell & { outPoint: IOutPoint } + export default class LiveCell { public txHash: string public outputIndex: string @@ -54,7 +56,7 @@ export default class LiveCell { return undefined } - public static fromLumos(cell: LumosCell): LiveCell { + public static fromLumos(cell: CellWithOutPoint): LiveCell { const type = cell.cellOutput.type ? new Script( cell.cellOutput.type.codeHash, diff --git a/packages/neuron-wallet/src/services/live-cell-service.ts b/packages/neuron-wallet/src/services/live-cell-service.ts index 4b7766d3db..3fe38814a6 100644 --- a/packages/neuron-wallet/src/services/live-cell-service.ts +++ b/packages/neuron-wallet/src/services/live-cell-service.ts @@ -1,7 +1,7 @@ import Script from '../models/chain/script' -import LiveCell from '../models/chain/live-cell' +import LiveCell, { CellWithOutPoint } from '../models/chain/live-cell' import { queryIndexer } from '../block-sync-renderer/index' -import { LumosCell, LumosCellQuery } from '../block-sync-renderer/sync/synchronizer' +import { type QueryOptions } from '@ckb-lumos/base' export default class LiveCellService { private static instance: LiveCellService @@ -20,14 +20,14 @@ export default class LiveCellService { lock: Script | null, type: Script | null, data: string | null - ): Promise { + ): Promise { if (!lock && !type) { throw new Error('at least one parameter is required') } - const query: LumosCellQuery = { lock, type, data } - const liveCells: LumosCell[] = await queryIndexer(query) - return liveCells + const query = { lock, type, data } as QueryOptions + const liveCells = await queryIndexer(query) + return liveCells as CellWithOutPoint[] } public async getOneByLockScriptAndTypeScript(lock: Script | null, type: Script | null) { diff --git a/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts index c3c1e7e325..f237f7068d 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts @@ -1,8 +1,9 @@ import { scriptToAddress } from '../../src/utils/scriptAndAddress' import { AddressType } from '@ckb-lumos/hd' +import { QueryOptions, type Cell } from '@ckb-lumos/base' import { Address, AddressVersion } from '../../src/models/address' import SystemScriptInfo from '../../src/models/system-script-info' -import { Synchronizer, type LumosCell, type LumosCellQuery } from '../../src/block-sync-renderer/sync/synchronizer' +import { Synchronizer } from '../../src/block-sync-renderer/sync/synchronizer' import AddressMeta from '../../src/database/address/meta' import IndexerTxHashCache from '../../src/database/chain/entities/indexer-tx-hash-cache' import { ScriptHashType } from '../../src/models/chain/script' @@ -220,10 +221,11 @@ describe('unit tests for IndexerConnector', () => { }) describe('#getLiveCellsByScript', () => { - let fakeCell1: LumosCell, fakeCell2: LumosCell - let cells: LumosCell[] + let fakeCell1: Cell, fakeCell2: Cell + let cells: Cell[] fakeCell1 = { + data: '0x', blockHash: '0x', outPoint: { txHash: '0x', @@ -244,6 +246,7 @@ describe('unit tests for IndexerConnector', () => { }, } fakeCell2 = { + data: '0x', blockHash: '0x', outPoint: { txHash: '0x', @@ -257,7 +260,7 @@ describe('unit tests for IndexerConnector', () => { args: '0x2', }, type: { - hashType: 'lock', + hashType: 'data', codeHash: '0xcode', args: '0x2', }, @@ -272,7 +275,7 @@ describe('unit tests for IndexerConnector', () => { }) describe('when success', () => { - const query: LumosCellQuery = { + const query = { lock: { hashType: ScriptHashType.Data, codeHash: '0xcode', @@ -283,7 +286,6 @@ describe('unit tests for IndexerConnector', () => { codeHash: '0xcode', args: '0x', }, - data: null, } beforeEach(async () => { @@ -298,14 +300,14 @@ describe('unit tests for IndexerConnector', () => { it('transform the query parameter', () => { expect(stubbedCellCollectorConstructor.mock.calls[0][1]).toEqual({ lock: { - hashType: query.lock!.hashType, - codeHash: query.lock!.codeHash, - args: query.lock!.args, + hashType: query.lock.hashType, + codeHash: query.lock.codeHash, + args: query.lock.args, }, type: { - hashType: query.type!.hashType, - codeHash: query.type!.codeHash, - args: query.type!.args, + hashType: query.type.hashType, + codeHash: query.type.codeHash, + args: query.type.args, }, data: 'any', }) @@ -316,7 +318,7 @@ describe('unit tests for IndexerConnector', () => { }) }) describe('when handling concurrent requests', () => { - const query1: LumosCellQuery = { + const query1: QueryOptions = { lock: { hashType: ScriptHashType.Data, codeHash: '0xcode', @@ -327,9 +329,8 @@ describe('unit tests for IndexerConnector', () => { codeHash: '0xcode', args: '0x1', }, - data: null, } - const query2: LumosCellQuery = { + const query2: QueryOptions = { lock: { hashType: ScriptHashType.Type, codeHash: '0xcode', @@ -340,7 +341,6 @@ describe('unit tests for IndexerConnector', () => { codeHash: '0xcode', args: '0x2', }, - data: null, } const results: unknown[] = [] @@ -401,7 +401,7 @@ describe('unit tests for IndexerConnector', () => { it('throws error', async () => { let err try { - await synchronizer.getLiveCellsByScript({ lock: null, type: null, data: null }) + await synchronizer.getLiveCellsByScript({}) } catch (error) { err = error } diff --git a/packages/neuron-wallet/tests/models/chain/live-cell.test.ts b/packages/neuron-wallet/tests/models/chain/live-cell.test.ts index 84a9b97830..1bc5cba2fd 100644 --- a/packages/neuron-wallet/tests/models/chain/live-cell.test.ts +++ b/packages/neuron-wallet/tests/models/chain/live-cell.test.ts @@ -1,6 +1,5 @@ import Script, { ScriptHashType } from '../../../src/models/chain/script' -import { LumosCell } from '../../../src/block-sync-renderer/sync/synchronizer' -import LiveCell from '../../../src/models/chain/live-cell' +import LiveCell, { CellWithOutPoint } from '../../../src/models/chain/live-cell' describe('LiveCell Test', () => { const INITIAL_DATA = { @@ -88,7 +87,7 @@ describe('LiveCell Test', () => { }) describe('get cells from lumos cell', () => { - const LUMOS_CELL: LumosCell = { + const LUMOS_CELL: CellWithOutPoint = { blockHash: '0x0000000000000000000000000000000000000000000000000000000000000000', outPoint: { txHash: INITIAL_DATA.txHash, diff --git a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts index c5217e5f11..c8ba7d414a 100644 --- a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts +++ b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts @@ -1,6 +1,6 @@ import { when } from 'jest-when' -import { since } from '@ckb-lumos/base' import { bytes } from '@ckb-lumos/codec' +import { since } from '@ckb-lumos/base' import OutputEntity from '../../../src/database/chain/entities/output' import InputEntity from '../../../src/database/chain/entities/input' import TransactionEntity from '../../../src/database/chain/entities/transaction' @@ -28,7 +28,7 @@ import { SudtAcpHaveDataError, TargetOutputNotFoundError, } from '../../../src/exceptions' -import LiveCell from '../../../src/models/chain/live-cell' +import LiveCell, { CellWithOutPoint } from '../../../src/models/chain/live-cell' import { keyInfos } from '../../setupAndTeardown/public-key-info.fixture' const randomHex = (length: number = 64): string => { @@ -86,7 +86,6 @@ import HdPublicKeyInfo from '../../../src/database/chain/entities/hd-public-key- import AssetAccount from '../../../src/models/asset-account' import MultisigConfigModel from '../../../src/models/multisig-config' import MultisigOutput from '../../../src/database/chain/entities/multisig-output' -import { LumosCell } from '../../../src/block-sync-renderer/sync/synchronizer' import { closeConnection, getConnection, initConnection } from '../../setupAndTeardown' describe('TransactionGenerator', () => { @@ -1097,8 +1096,8 @@ describe('TransactionGenerator', () => { tokenID: string | undefined = undefined, lockScript: Script = bobAnyoneCanPayLockScript, customData: string = '0x' - ): LumosCell => { - const liveCell: LumosCell = { + ): CellWithOutPoint => { + const liveCell: CellWithOutPoint = { blockHash: randomHex(), outPoint: { txHash: randomHex(), @@ -1109,18 +1108,17 @@ describe('TransactionGenerator', () => { lock: { codeHash: lockScript.codeHash, args: lockScript.args, - hashType: lockScript.hashType.toString(), + hashType: lockScript.hashType, }, }, data: '0x', } if (tokenID) { const typeScript = assetAccountInfo.generateSudtScript(tokenID) - // @ts-ignore liveCell.cellOutput.type = { codeHash: typeScript.codeHash, args: typeScript.args, - hashType: typeScript.hashType.toString(), + hashType: typeScript.hashType, } } liveCell.data = amount ? BufferUtils.writeBigUInt128LE(BigInt(amount)) : '0x' From 810ff2f91ca6618844d2633c068d60e55b298ea4 Mon Sep 17 00:00:00 2001 From: Tom Wang Date: Tue, 28 May 2024 10:11:34 +0800 Subject: [PATCH 07/57] refactor: remove indexerUrl (#3170) --- packages/neuron-wallet/src/block-sync-renderer/index.ts | 1 - .../src/block-sync-renderer/sync/full-synchronizer.ts | 4 ++-- .../src/block-sync-renderer/sync/light-synchronizer.ts | 6 +----- .../neuron-wallet/src/block-sync-renderer/sync/queue.ts | 6 ++---- .../src/block-sync-renderer/sync/synchronizer.ts | 4 ++-- packages/neuron-wallet/src/block-sync-renderer/task.ts | 5 ++--- .../tests/block-sync-renderer/full-synchronizer.test.ts | 8 +++----- .../index/createBlockSyncTask.test.ts | 1 - .../tests/block-sync-renderer/synchronizer.test.ts | 9 +-------- .../neuron-wallet/tests/block-sync-renderer/task.test.ts | 2 -- 10 files changed, 13 insertions(+), 33 deletions(-) diff --git a/packages/neuron-wallet/src/block-sync-renderer/index.ts b/packages/neuron-wallet/src/block-sync-renderer/index.ts index 19b0394649..7bada7dac1 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/index.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/index.ts @@ -177,7 +177,6 @@ export const createBlockSyncTask = async () => { genesisHash: network.genesisHash, url: network.remote, addressMetas, - indexerUrl: network.remote, nodeType: network.type, } const msg: Required> = { type: 'call', channel: 'start', id: requestId++, message } diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/full-synchronizer.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/full-synchronizer.ts index 972d37e5f0..eecace1ccf 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/full-synchronizer.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/full-synchronizer.ts @@ -10,8 +10,8 @@ import IndexerCacheService from './indexer-cache-service' export default class FullSynchronizer extends Synchronizer { private rpcService: RpcService - constructor(addresses: Address[], nodeUrl: string, indexerUrl: string, nodeType: NetworkType) { - super({ addresses, nodeUrl, indexerUrl }) + constructor(addresses: Address[], nodeUrl: string, nodeType: NetworkType) { + super({ addresses, nodeUrl }) this.rpcService = new RpcService(nodeUrl, nodeType) } diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts index f87dddf99b..d7b1fe7db6 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts @@ -29,11 +29,7 @@ export default class LightSynchronizer extends Synchronizer { private addressMetas: AddressMeta[] constructor(addresses: Address[], nodeUrl: string) { - super({ - addresses, - nodeUrl, - indexerUrl: nodeUrl, - }) + super({ addresses, nodeUrl }) this.lightRpc = new LightRPC(nodeUrl) this.addressMetas = addresses.map(address => AddressMeta.fromObject(address)) // fetch some dep cell 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 027bc14967..2c5d5008a5 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts @@ -28,7 +28,6 @@ export default class Queue { #lockHashes: string[] #url: string // ckb node #nodeType: NetworkType - #indexerUrl: string #addresses: AddressInterface[] #rpcService: RpcService #indexerConnector: Synchronizer | undefined @@ -39,9 +38,8 @@ export default class Queue { #anyoneCanPayLockHashes: string[] #assetAccountInfo: AssetAccountInfo - constructor(url: string, addresses: AddressInterface[], indexerUrl: string, nodeType: NetworkType) { + constructor(url: string, addresses: AddressInterface[], nodeType: NetworkType) { this.#url = url - this.#indexerUrl = indexerUrl this.#addresses = addresses this.#rpcService = new RpcService(url, nodeType) this.#nodeType = nodeType @@ -70,7 +68,7 @@ export default class Queue { if (this.#url === BUNDLED_LIGHT_CKB_URL) { this.#indexerConnector = new LightSynchronizer(this.#addresses, this.#url) } else { - this.#indexerConnector = new FullSynchronizer(this.#addresses, this.#url, this.#indexerUrl, this.#nodeType) + this.#indexerConnector = new FullSynchronizer(this.#addresses, this.#url, this.#nodeType) } await this.#indexerConnector!.connect() } catch (error) { diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts index 01fcfa3a12..cd6b840581 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/synchronizer.ts @@ -40,8 +40,8 @@ export abstract class Synchronizer { // do nothing } - constructor({ addresses, nodeUrl, indexerUrl }: { addresses: Address[]; nodeUrl: string; indexerUrl: string }) { - this.indexer = new CkbIndexer(nodeUrl, indexerUrl) + constructor({ addresses, nodeUrl }: { addresses: Address[]; nodeUrl: string }) { + this.indexer = new CkbIndexer(nodeUrl) this.addressesByWalletId = addresses .map(address => AddressMeta.fromObject(address)) .reduce((addressesByWalletId, addressMeta) => { diff --git a/packages/neuron-wallet/src/block-sync-renderer/task.ts b/packages/neuron-wallet/src/block-sync-renderer/task.ts index 4d5c4b9d5a..dc937616dd 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/task.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/task.ts @@ -31,8 +31,7 @@ export interface StartParams { genesisHash: string url: SyncQueueParams[0] addressMetas: SyncQueueParams[1] - indexerUrl: SyncQueueParams[2] - nodeType: SyncQueueParams[3] + nodeType: SyncQueueParams[2] } export type QueryIndexerParams = QueryOptions @@ -63,7 +62,7 @@ export const listener = async ({ type, id, channel, message }: WorkerMessage) => try { await initConnection(message.genesisHash) - syncQueue = new SyncQueue(message.url, message.addressMetas, message.indexerUrl, message.nodeType) + syncQueue = new SyncQueue(message.url, message.addressMetas, message.nodeType) syncQueue.start() } catch (err) { logger.error(`Block Sync Task:\t`, err) diff --git a/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts index b4f2cd19fd..b4af6a23a4 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/full-synchronizer.test.ts @@ -87,14 +87,12 @@ describe('unit tests for IndexerConnector', () => { }) describe('#constructor', () => { - const STUB_URI = 'stub_uri' - describe('when init with indexer folder path', () => { beforeEach(() => { - new stubbedFullSynchronizer([], nodeUrl, STUB_URI) + new stubbedFullSynchronizer([], nodeUrl) }) it('inits lumos indexer with a node url and indexer folder path', () => { - expect(stubbedIndexerConstructor).toHaveBeenCalledWith(nodeUrl, STUB_URI) + expect(stubbedIndexerConstructor).toHaveBeenCalledWith(nodeUrl) }) }) describe('when init without indexer folder path', () => { @@ -102,7 +100,7 @@ describe('unit tests for IndexerConnector', () => { new stubbedFullSynchronizer([], nodeUrl) }) it('inits mercury indexer with a node url and a default port', () => { - expect(stubbedIndexerConstructor).toHaveBeenCalledWith(nodeUrl, STUB_URI) + expect(stubbedIndexerConstructor).toHaveBeenCalledWith(nodeUrl) }) }) }) diff --git a/packages/neuron-wallet/tests/block-sync-renderer/index/createBlockSyncTask.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/index/createBlockSyncTask.test.ts index 61da50c476..d53022eb38 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/index/createBlockSyncTask.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/index/createBlockSyncTask.test.ts @@ -75,7 +75,6 @@ describe(`Create block sync task`, () => { message: { addressMetas: STUB_ADDRESS_METAS, genesisHash: STUB_NETWORK.genesisHash, - indexerUrl: STUB_NETWORK.remote, nodeType: NetworkType.Normal, url: STUB_NETWORK.remote, }, diff --git a/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts index f237f7068d..9836446cf2 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/synchronizer.test.ts @@ -111,22 +111,18 @@ describe('unit tests for IndexerConnector', () => { }) describe('#constructor', () => { - const STUB_URI = 'stub_uri' - it('inits lumos indexer with a node url and indexer folder path', () => { new TestSynchronizer({ addresses: [], nodeUrl, - indexerUrl: STUB_URI, }) - expect(stubbedIndexerConstructor).toHaveBeenCalledWith(nodeUrl, STUB_URI) + expect(stubbedIndexerConstructor).toHaveBeenCalledWith(nodeUrl) }) it('init with addresses', () => { const synchronizer = new TestSynchronizer({ addresses: [addressObj1, addressObj2], nodeUrl, - indexerUrl: STUB_URI, }) expect(synchronizer.getAddressesByWalletId().get(walletId1)?.[0]).toStrictEqual( AddressMeta.fromObject(addressObj1) @@ -141,7 +137,6 @@ describe('unit tests for IndexerConnector', () => { const synchronizer = new TestSynchronizer({ addresses: [addressObj1, addressObj2], nodeUrl, - indexerUrl: '', }) it('no cached tx', async () => { stubbedNextUnprocessedTxsGroupedByBlockNumberFn.mockResolvedValue([]) @@ -179,7 +174,6 @@ describe('unit tests for IndexerConnector', () => { const synchronizer = new TestSynchronizer({ addresses: [addressObj1, addressObj2], nodeUrl, - indexerUrl: '', }) synchronizer.blockTipsSubject.subscribe(stubbedBlockTipsSubscribe) @@ -271,7 +265,6 @@ describe('unit tests for IndexerConnector', () => { const synchronizer = new TestSynchronizer({ addresses: [addressObj1, addressObj2], nodeUrl, - indexerUrl: '', }) describe('when success', () => { diff --git a/packages/neuron-wallet/tests/block-sync-renderer/task.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/task.test.ts index 413e782d13..9e0369e00a 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/task.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/task.test.ts @@ -6,7 +6,6 @@ const STUB_START_MESSAGE = { genesisHash: 'stub_genesis_hash', url: 'stub_url', addressMetas: 'stub_address_metas', - indexerUrl: 'stub_indexer_url', nodeType: 2, }, } @@ -71,7 +70,6 @@ describe(`Block Sync Task`, () => { expect(stubbedSyncQueue).toHaveBeenCalledWith( STUB_START_MESSAGE.message.url, STUB_START_MESSAGE.message.addressMetas, - STUB_START_MESSAGE.message.indexerUrl, STUB_START_MESSAGE.message.nodeType ) expect(stubbedSyncQueueStart).toHaveBeenCalled() From 493d07bed56276b30e18eb2ea6c2dd798c843a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Tue, 28 May 2024 12:24:47 +0800 Subject: [PATCH 08/57] perf: Optimize synchronization logic for multiple wallets in the light client. (#3160) perf: Optimize synchronization logic for multiple wallets in a light client node Co-authored-by: Chen Yu --- .../sync/light-synchronizer.ts | 53 +++--- .../light-synchronizer.test.ts | 165 +++++++++++++++++- 2 files changed, 190 insertions(+), 28 deletions(-) diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts index d7b1fe7db6..eb1f52bcf0 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts @@ -23,6 +23,7 @@ import NetworksService from '../../services/networks' import { getConnection } from '../../database/chain/connection' const unpackGroup = molecule.vector(blockchain.OutPoint) +const THRESHOLD_BLOCK_NUMBER_IN_DIFF_WALLET = 100_000 export default class LightSynchronizer extends Synchronizer { private lightRpc: LightRPC @@ -167,6 +168,18 @@ export default class LightSynchronizer extends Synchronizer { await getConnection('light').manager.save(updatedSyncProgress, { chunk: 100 }) } + private static async getWalletsSyncedMinBlockNumber() { + const walletMinBlockNumber = await SyncProgressService.getWalletMinLocalSavedBlockNumber() + const wallets = await WalletService.getInstance().getAll() + return wallets.reduce>( + (pre, cur) => ({ + ...pre, + [cur.id]: Math.max(parseInt(cur.startBlockNumber ?? '0x0'), walletMinBlockNumber?.[cur.id] ?? 0), + }), + {} + ) + } + private async initSyncProgress(appendScripts: AppendScript[] = []) { if (!this.addressMetas.length && !appendScripts.length) { return @@ -174,13 +187,13 @@ export default class LightSynchronizer extends Synchronizer { const existSyncArgses = await SyncProgressService.getExistingSyncArgses() const syncScripts = await this.lightRpc.getScripts() const retainedSyncScripts = syncScripts.filter(v => existSyncArgses.has(v.script.args)) - const existSyncscripts: Record = {} + const existSyncScripts: Record = {} retainedSyncScripts.forEach(v => { - existSyncscripts[scriptToHash(v.script)] = v + existSyncScripts[scriptToHash(v.script)] = v }) + const walletMinBlockNumber = await LightSynchronizer.getWalletsSyncedMinBlockNumber() const currentWalletId = WalletService.getInstance().getCurrent()?.id const allScripts = this.addressMetas - .filter(v => (currentWalletId ? v.walletId === currentWalletId : true)) .map(addressMeta => { const lockScripts = [ addressMeta.generateDefaultLockScript(), @@ -194,32 +207,32 @@ export default class LightSynchronizer extends Synchronizer { })) }) .flat() - const walletMinBlockNumber = await SyncProgressService.getWalletMinLocalSavedBlockNumber() - const wallets = await WalletService.getInstance().getAll() - const walletStartBlockMap = wallets.reduce>( - (pre, cur) => ({ ...pre, [cur.id]: cur.startBlockNumber }), - {} - ) + .filter(v => { + return ( + !currentWalletId || + v.walletId === currentWalletId || + walletMinBlockNumber[v.walletId] > + walletMinBlockNumber[currentWalletId] - THRESHOLD_BLOCK_NUMBER_IN_DIFF_WALLET + ) + }) const otherTypeSyncBlockNumber = await SyncProgressService.getOtherTypeSyncBlockNumber() const addScripts = [ ...allScripts - .filter( - v => - !existSyncscripts[scriptToHash(v.script)] || - +existSyncscripts[scriptToHash(v.script)].blockNumber < +(walletStartBlockMap[v.walletId] ?? 0) - ) - .map(v => { - const blockNumber = Math.max( - parseInt(walletStartBlockMap[v.walletId] ?? '0x0'), - walletMinBlockNumber?.[v.walletId] ?? 0 + .filter(v => { + const scriptHash = scriptToHash(v.script) + return ( + !existSyncScripts[scriptHash] || + +existSyncScripts[scriptHash].blockNumber < walletMinBlockNumber[v.walletId] ) + }) + .map(v => { return { ...v, - blockNumber: `0x${blockNumber.toString(16)}`, + blockNumber: `0x${walletMinBlockNumber[v.walletId].toString(16)}`, } }), ...appendScripts - .filter(v => !existSyncscripts[scriptToHash(v.script)]) + .filter(v => !existSyncScripts[scriptToHash(v.script)]) .map(v => ({ ...v, blockNumber: `0x${(otherTypeSyncBlockNumber[scriptToHash(v.script)] ?? 0).toString(16)}`, diff --git a/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts b/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts index ccd7ee549e..8279828f14 100644 --- a/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts +++ b/packages/neuron-wallet/tests/block-sync-renderer/light-synchronizer.test.ts @@ -24,6 +24,7 @@ const schedulerWaitMock = jest.fn() const getMultisigConfigForLightMock = jest.fn() const walletGetCurrentMock = jest.fn() const walletGetAllMock = jest.fn() +const updateBlockStartNumberMock = jest.fn() function mockReset() { getSyncStatusMock.mockReset() @@ -46,6 +47,8 @@ function mockReset() { removeByHashesAndAddressType.mockReset() walletGetCurrentMock.mockReset() walletGetAllMock.mockReset() + + updateBlockStartNumberMock.mockReset() } jest.mock('../../src/services/sync-progress', () => { @@ -138,6 +141,7 @@ describe('test light synchronizer', () => { }) it('when syncing script in the local DB', async () => { getScriptsMock.mockResolvedValue([{ script, blockNumber: '0xaa' }]) + walletGetAllMock.mockReturnValue([{ id: 'walletId' }]) getExistingSyncArgsesMock.mockResolvedValue(new Set([script.args])) const addressMeta = AddressMeta.fromObject({ walletId: 'walletId', @@ -187,6 +191,7 @@ describe('test light synchronizer', () => { }) it('when syncing script not in the local DB', async () => { getScriptsMock.mockResolvedValue([{ script, blockNumber: '0xaa' }]) + walletGetAllMock.mockReturnValue([{ id: 'walletId' }]) getExistingSyncArgsesMock.mockResolvedValue(new Set()) const addressMeta = AddressMeta.fromObject({ walletId: 'walletId', @@ -256,6 +261,7 @@ describe('test light synchronizer', () => { addressType: 0, blake160: script.args, }) + walletGetAllMock.mockReturnValue([{ id: 'walletId' }]) getWalletMinLocalSavedBlockNumberMock.mockResolvedValue({ walletId: 170 }) const connect = new LightSynchronizer([addressMeta], '') //@ts-ignore @@ -367,6 +373,152 @@ describe('test light synchronizer', () => { ) expect(setScriptsMock).toHaveBeenLastCalledWith([], 'delete') }) + it('when other wallet min synced block number near the current wallet', async () => { + walletGetCurrentMock.mockReturnValue({ id: 'walletId1' }) + walletGetAllMock.mockReturnValue([ + { id: 'walletId1', startBlockNumber: '0xaa' }, + { id: 'walletId2', startBlockNumber: '0xab' }, + ]) + const addressMeta1 = AddressMeta.fromObject({ + walletId: 'walletId1', + address, + path: '', + addressIndex: 10, + addressType: 0, + blake160: script.args, + }) + const script2: Script = { + args: '0x403f0d4e833b2a8d372772a63facaa310dfeef93', + codeHash: '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', + hashType: 'type', + } + const getScriptsResult = [{ script: script2, blockNumber: '0xaa', scriptType: 'lock' }] + getScriptsMock.mockResolvedValue(getScriptsResult) + getExistingSyncArgsesMock.mockResolvedValue(new Set([script2.args])) + const addressMeta2 = AddressMeta.fromObject({ + walletId: 'walletId2', + address, + path: '', + addressIndex: 10, + addressType: 0, + blake160: script2.args, + }) + const connect = new LightSynchronizer([addressMeta1, addressMeta2], '') + //@ts-ignore + await connect.initSyncProgress() + expect(setScriptsMock).toHaveBeenNthCalledWith( + 1, + [ + ...[ + addressMeta1.generateDefaultLockScript().toSDK(), + addressMeta1.generateACPLockScript().toSDK(), + addressMeta1.generateLegacyACPLockScript().toSDK(), + ].map(script => ({ + script, + scriptType: 'lock', + walletId: addressMeta1.walletId, + blockNumber: '0xaa', + })), + ...[ + addressMeta2.generateDefaultLockScript().toSDK(), + addressMeta2.generateACPLockScript().toSDK(), + addressMeta2.generateLegacyACPLockScript().toSDK(), + ].map(script => ({ + script, + scriptType: 'lock', + walletId: addressMeta2.walletId, + blockNumber: '0xab', + })), + ], + 'partial' + ) + expect(setScriptsMock).toHaveBeenLastCalledWith([], 'delete') + expect(initSyncProgressMock).toBeCalledWith([ + ...[ + addressMeta1.generateDefaultLockScript().toSDK(), + addressMeta1.generateACPLockScript().toSDK(), + addressMeta1.generateLegacyACPLockScript().toSDK(), + ].map(script => ({ + script, + scriptType: 'lock', + walletId: addressMeta1.walletId, + blockNumber: '0xaa', + })), + ...[ + addressMeta2.generateDefaultLockScript().toSDK(), + addressMeta2.generateACPLockScript().toSDK(), + addressMeta2.generateLegacyACPLockScript().toSDK(), + ].map(script => ({ + script, + scriptType: 'lock', + walletId: addressMeta2.walletId, + blockNumber: '0xab', + })), + ]) + expect(updateSyncProgressFlagMock).toBeCalledWith(['walletId1', 'walletId2']) + }) + it('when other wallet min synced block number bigger than the current wallet', async () => { + walletGetCurrentMock.mockReturnValue({ id: 'walletId1' }) + walletGetAllMock.mockReturnValue([ + { id: 'walletId1', startBlockNumber: '0xaaaa0' }, + { id: 'walletId2', startBlockNumber: '0xab' }, + ]) + const addressMeta1 = AddressMeta.fromObject({ + walletId: 'walletId1', + address, + path: '', + addressIndex: 10, + addressType: 0, + blake160: script.args, + }) + const script2: Script = { + args: '0x403f0d4e833b2a8d372772a63facaa310dfeef93', + codeHash: '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', + hashType: 'type', + } + const getScriptsResult = [{ script: script2, blockNumber: '0xab', scriptType: 'lock' }] + getScriptsMock.mockResolvedValue(getScriptsResult) + getExistingSyncArgsesMock.mockResolvedValue(new Set([script2.args])) + const addressMeta2 = AddressMeta.fromObject({ + walletId: 'walletId2', + address, + path: '', + addressIndex: 10, + addressType: 0, + blake160: script2.args, + }) + const connect = new LightSynchronizer([addressMeta1, addressMeta2], '') + //@ts-ignore + await connect.initSyncProgress() + expect(setScriptsMock).toHaveBeenNthCalledWith( + 1, + [ + addressMeta1.generateDefaultLockScript().toSDK(), + addressMeta1.generateACPLockScript().toSDK(), + addressMeta1.generateLegacyACPLockScript().toSDK(), + ].map(script => ({ + script, + scriptType: 'lock', + walletId: addressMeta1.walletId, + blockNumber: '0xaaaa0', + })), + 'partial' + ) + expect(setScriptsMock).toHaveBeenLastCalledWith(getScriptsResult, 'delete') + expect(initSyncProgressMock).toBeCalledWith( + [ + addressMeta1.generateDefaultLockScript().toSDK(), + addressMeta1.generateACPLockScript().toSDK(), + addressMeta1.generateLegacyACPLockScript().toSDK(), + ].map(script => ({ + script, + scriptType: 'lock', + walletId: addressMeta1.walletId, + blockNumber: '0xaaaa0', + })) + ) + expect(updateSyncProgressFlagMock).toBeCalledWith(['walletId1', 'walletId2']) + }) }) describe('test initSync', () => { @@ -428,16 +580,10 @@ describe('test light synchronizer', () => { }) describe('#notifyCurrentBlockNumberProcessed', () => { - const synchronizer = new LightSynchronizer([], '') - const updateBlockStartNumberMock = jest.fn() - beforeAll(() => { + it('last process block number finish', async () => { + const synchronizer = new LightSynchronizer([], '') // @ts-ignore private property synchronizer.updateBlockStartNumber = updateBlockStartNumberMock - }) - beforeEach(() => { - updateBlockStartNumberMock.mockReset() - }) - it('last process block number finish', async () => { // @ts-ignore private property synchronizer.processingBlockNumber = '0xaa' getCurrentWalletMinSyncedBlockNumberMock.mockResolvedValueOnce(100) @@ -447,6 +593,9 @@ describe('test light synchronizer', () => { expect(updateBlockStartNumberMock).toBeCalledWith(100) }) it('not last process block number finish', async () => { + const synchronizer = new LightSynchronizer([], '') + // @ts-ignore private property + synchronizer.updateBlockStartNumber = updateBlockStartNumberMock // @ts-ignore private property synchronizer.processingBlockNumber = undefined await synchronizer.notifyCurrentBlockNumberProcessed('0xaa') From c3a2888d1e26b73b9b3f6758fb749a67a8317082 Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Wed, 29 May 2024 16:15:17 +0800 Subject: [PATCH 09/57] fix: Fix action branch sha --- .github/workflows/update_wallet_env.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update_wallet_env.yml b/.github/workflows/update_wallet_env.yml index f02a69b70b..5f3d7f090e 100644 --- a/.github/workflows/update_wallet_env.yml +++ b/.github/workflows/update_wallet_env.yml @@ -19,7 +19,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: branch: 'chore-update-wallet-env/${{github.ref_name}}' - sha: '${{ github.event.create.head.sha }}' + sha: '${{ github.sha }}' - name: Checkout uses: actions/checkout@v4 From 5d4afb1494d127be297f21ee642378154a47e33c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Thu, 30 May 2024 17:43:12 +0800 Subject: [PATCH 10/57] feat: Optimize light client external node detection (#3165) --- .../neuron-ui/src/containers/Main/index.tsx | 76 ++++++++++++------- packages/neuron-ui/src/locales/en.json | 3 +- packages/neuron-ui/src/locales/es.json | 3 +- packages/neuron-ui/src/locales/fr.json | 3 +- packages/neuron-ui/src/locales/zh-tw.json | 3 +- packages/neuron-ui/src/locales/zh.json | 3 +- .../src/block-sync-renderer/sync/queue.ts | 3 +- .../neuron-wallet/src/services/ckb-runner.ts | 8 +- .../src/services/light-runner.ts | 16 +++- packages/neuron-wallet/src/utils/toml.ts | 18 ++++- .../tests/services/ckb-runner.test.ts | 16 +++- .../tests/services/light-runner.test.ts | 30 ++++++-- .../neuron-wallet/tests/utils/toml/test.toml | 1 + .../tests/utils/toml/toml.test.ts | 33 +++++--- 14 files changed, 147 insertions(+), 69 deletions(-) diff --git a/packages/neuron-ui/src/containers/Main/index.tsx b/packages/neuron-ui/src/containers/Main/index.tsx index 038ca40a31..92b27d5cb3 100644 --- a/packages/neuron-ui/src/containers/Main/index.tsx +++ b/packages/neuron-ui/src/containers/Main/index.tsx @@ -37,6 +37,8 @@ const MainContent = () => { [network, networks] ) + const isLightClientNetwork = network?.type === 2 + useSyncChainData({ chainURL: network?.remote ?? '', dispatch, @@ -98,6 +100,48 @@ const MainContent = () => { } }, []) + const dialogProps = (function getDialogProps() { + if (isLightClientNetwork) { + return { + onConfirm: onCloseSwitchNetwork, + children: t('main.external-node-detected-dialog.external-node-is-light'), + } + } + if (sameUrlNetworks.length) { + return { + onConfirm: onSwitchNetwork, + children: ( + <> + + {t('main.external-node-detected-dialog.body-tips-with-network')} + +
+ ({ + value: v.id, + label: `${v.name} (${v.remote})`, + }))} + inputIdPrefix="main-switch" + /> +
+
+ +
+ + ), + } + } + return { + onConfirm: onOpenEditorDialog, + confirmText: t('main.external-node-detected-dialog.add-network'), + children: t('main.external-node-detected-dialog.body-tips-without-network'), + } + })() + return (
@@ -124,39 +168,13 @@ const MainContent = () => { - {sameUrlNetworks.length ? ( - - {t('main.external-node-detected-dialog.body-tips-with-network')} - - ) : ( - t('main.external-node-detected-dialog.body-tips-without-network') - )} - {sameUrlNetworks.length ? ( - <> -
- ({ - value: v.id, - label: `${v.name} (${v.remote})`, - }))} - inputIdPrefix="main-switch" - /> -
-
- -
- - ) : null} + {dialogProps.children}
{showEditorDialog ? ( Please allocate more disk space or migrate the data to another disk.", diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 3ac8e1f57f..325880ce4e 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -1192,7 +1192,8 @@ "body-tips-without-network": "\"Internal Node\" está reservado para el nodo CKB incorporado, agregue una nueva opción de red para el nodo externo.", "body-tips-with-network": "\"Internal Node\" está reservado para el nodo CKB incorporado, seleccione una de las siguientes opciones de red o agregue una nueva para el nodo externo.", "add-network": "Agregar red", - "ignore-external-node": "Ignorar nodo externo" + "ignore-external-node": "Ignorar nodo externo", + "external-node-is-light": "Debido a las diferentes suposiciones de seguridad del cliente ligero, Neuron no admite nodos de cliente ligero externos. ¿Desea conectarse a un \"Light Client\" ?" }, "no-disk-space-dialog": { "tip": "La sincronización se ha detenido debido a falta de espacio en disco.
Asigne más espacio en disco o migre los datos a otro disco.", diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index a609830ca1..710b521d1c 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -1202,7 +1202,8 @@ "body-tips-without-network": "\"Noeud interne\" est réservé au noeud CKB intégré, veuillez ajouter une nouvelle option réseau pour le noeud externe.", "body-tips-with-network": "\"Noeud interne\" est réservé au noeud CKB intégré, veuillez sélectionner parmi les options réseau suivantes ou en ajouter une nouvelle pour le noeud externe.", "add-network": "Ajouter un réseau", - "ignore-external-node": "Ignorer le noeud externe" + "ignore-external-node": "Ignorer le noeud externe", + "external-node-is-light": "En raison de différentes hypothèses de sécurité du client léger, Neuron ne prend pas en charge les nœuds clients légers externes. Voulez-vous vous connecter à un \"Light Client\" ?" }, "no-disk-space-dialog": { "tip": "En raison d'un espace disque insuffisant, la synchronisation a été interrompue.
Veuillez allouer plus d'espace disque ou migrer les données vers un autre disque.", diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 8e61fb1c5d..3d82319d84 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -1205,7 +1205,8 @@ "body-tips-without-network": "\"Internal Node\" 僅用於連接內置節點,請添加新的網絡選項以連接外部節點。", "body-tips-with-network": "\"Internal Node\" 僅用於連接內置節點,請選擇以下網絡選項或添加新的網絡選項以連接外部節點。", "add-network": "添加網絡", - "ignore-external-node": "忽略外部節點" + "ignore-external-node": "忽略外部節點", + "external-node-is-light": "由於輕客戶端的安全假設不同,Neuron 不支持外部輕客戶端節點。您想連接到 \"Light Client\" 嗎?" }, "no-disk-space-dialog": { "tip": "由於磁盤空間不足,同步已停止。
請分配更多磁盤空間或將數據遷移到其他磁盤。", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index d0ad40e458..0854352928 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -1204,7 +1204,8 @@ "body-tips-without-network": "\"Internal Node\" 仅用于连接内置节点,请添加新的网络选项以连接外部节点。", "body-tips-with-network": "\"Internal Node\" 仅用于连接内置节点,请选择以下网络选项或添加新的网络选项以连接外部节点。", "add-network": "添加网络", - "ignore-external-node": "忽略外部节点" + "ignore-external-node": "忽略外部节点", + "external-node-is-light": "由于轻客户端的安全假设不同,Neuron 不支持外部轻客户端节点。您想连接到 \"Light Client\" 吗?" }, "no-disk-space-dialog": { "tip": "由于磁盘空间不足,同步已停止。
请分配更多磁盘空间或将数据迁移到其他磁盘。", 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 2c5d5008a5..6ff88e8e90 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts @@ -20,7 +20,6 @@ import { ShouldInChildProcess } from '../../exceptions' import { AppendScript, BlockTips, Synchronizer } from './synchronizer' import LightSynchronizer from './light-synchronizer' import { generateRPC } from '../../utils/ckb-rpc' -import { BUNDLED_LIGHT_CKB_URL } from '../../utils/const' import { NetworkType } from '../../models/network' import WalletService from '../../services/wallets' @@ -65,7 +64,7 @@ export default class Queue { start = async () => { logger.info('Queue:\tstart') try { - if (this.#url === BUNDLED_LIGHT_CKB_URL) { + if (this.#nodeType === NetworkType.Light) { this.#indexerConnector = new LightSynchronizer(this.#addresses, this.#url) } else { this.#indexerConnector = new FullSynchronizer(this.#addresses, this.#url, this.#nodeType) diff --git a/packages/neuron-wallet/src/services/ckb-runner.ts b/packages/neuron-wallet/src/services/ckb-runner.ts index 003800312d..5eae7a91ef 100644 --- a/packages/neuron-wallet/src/services/ckb-runner.ts +++ b/packages/neuron-wallet/src/services/ckb-runner.ts @@ -115,8 +115,12 @@ export const startCkbNode = async () => { listenPort = await getUsablePort(rpcPort >= listenPort ? rpcPort + 1 : listenPort) updateToml(path.join(SettingsService.getInstance().getNodeDataPath(), 'ckb.toml'), { - rpc: `listen_address = "127.0.0.1:${rpcPort}"`, - network: `listen_addresses = ["/ip4/0.0.0.0/tcp/${listenPort}"]`, + rpc: { + listen_address: `"127.0.0.1:${rpcPort}"`, + }, + network: { + listen_addresses: `["/ip4/0.0.0.0/tcp/${listenPort}"]`, + }, }) const options = ['run', '-C', SettingsService.getInstance().getNodeDataPath(), '--indexer'] const stdio: (StdioNull | StdioPipe)[] = ['ignore', 'pipe', 'pipe'] diff --git a/packages/neuron-wallet/src/services/light-runner.ts b/packages/neuron-wallet/src/services/light-runner.ts index 1c05fbf38e..da93dd39d2 100644 --- a/packages/neuron-wallet/src/services/light-runner.ts +++ b/packages/neuron-wallet/src/services/light-runner.ts @@ -81,6 +81,7 @@ export class CKBLightRunner extends NodeRunner { protected binaryName: string = 'ckb-light-client' protected logStream: Map = new Map() protected _port: number = 9000 + protected listenPort: number = 8118 static getInstance(): CKBLightRunner { if (!CKBLightRunner.instance) { @@ -117,13 +118,22 @@ export class CKBLightRunner extends NodeRunner { async updateConfig() { const usablePort = await getUsablePort(this._port) + const listenPort = await getUsablePort(usablePort >= this._port ? this.listenPort + 1 : this.listenPort) this._port = usablePort + this.listenPort = listenPort const storePath = path.join(SettingsService.getInstance().getNodeDataPath(), './store') const networkPath = path.join(SettingsService.getInstance().getNodeDataPath(), './network') updateToml(this.configFile, { - store: `path = "${this.platform() === 'win' ? storePath.replace(/\\/g, '\\\\') : storePath}"`, - network: `path = "${this.platform() === 'win' ? networkPath.replace(/\\/g, '\\\\') : networkPath}"`, - rpc: `listen_address = "127.0.0.1:${usablePort}"`, + store: { + path: `"${this.platform() === 'win' ? storePath.replace(/\\/g, '\\\\') : storePath}"`, + }, + network: { + path: `"${this.platform() === 'win' ? networkPath.replace(/\\/g, '\\\\') : networkPath}"`, + listen_addresses: `["/ip4/0.0.0.0/tcp/${listenPort}"]`, + }, + rpc: { + listen_address: `"127.0.0.1:${usablePort}"`, + }, }) } diff --git a/packages/neuron-wallet/src/utils/toml.ts b/packages/neuron-wallet/src/utils/toml.ts index b0e724513f..97a57eef78 100644 --- a/packages/neuron-wallet/src/utils/toml.ts +++ b/packages/neuron-wallet/src/utils/toml.ts @@ -1,7 +1,11 @@ import fs from 'fs' import path from 'path' -export function updateToml(filePath: string, updateValue: Record, newFilePath?: string) { +export function updateToml( + filePath: string, + updateValue: Record>, + newFilePath?: string +) { const values = fs.readFileSync(filePath).toString().split('\n') let field: string | undefined = undefined const newValues = values.map(v => { @@ -14,9 +18,15 @@ export function updateToml(filePath: string, updateValue: Record return v } if (field && updateValue[field]) { - const newLine = updateValue[field] - field = undefined - return newLine + const equalIndex = v.indexOf('=') + if (equalIndex !== -1) { + const stringBeforeEqual = v.slice(0, equalIndex) + const key = stringBeforeEqual.trim() + if (updateValue[field][key]) { + const newLine = `${stringBeforeEqual}= ${updateValue[field][key]}` + return newLine + } + } } return v }) diff --git a/packages/neuron-wallet/tests/services/ckb-runner.test.ts b/packages/neuron-wallet/tests/services/ckb-runner.test.ts index 451d9b2462..067137a946 100644 --- a/packages/neuron-wallet/tests/services/ckb-runner.test.ts +++ b/packages/neuron-wallet/tests/services/ckb-runner.test.ts @@ -255,8 +255,12 @@ describe('ckb runner', () => { expect(getNodeUrl()).toBe('http://127.0.0.1:8114') expect(getUsablePortMock).toHaveBeenLastCalledWith(8115) expect(updateTomlMock).toBeCalledWith(path.join('/chains/mainnet', 'ckb.toml'), { - rpc: `listen_address = "127.0.0.1:8114"`, - network: `listen_addresses = ["/ip4/0.0.0.0/tcp/8115"]`, + rpc: { + listen_address: `"127.0.0.1:8114"`, + }, + network: { + listen_addresses: `["/ip4/0.0.0.0/tcp/8115"]`, + }, }) const promise = stopCkbNode() stubbedCkb.emit('close') @@ -270,8 +274,12 @@ describe('ckb runner', () => { expect(getNodeUrl()).toBe('http://127.0.0.1:8115') expect(getUsablePortMock).toHaveBeenLastCalledWith(8116) expect(updateTomlMock).toBeCalledWith(path.join('/chains/mainnet', 'ckb.toml'), { - rpc: `listen_address = "127.0.0.1:8115"`, - network: `listen_addresses = ["/ip4/0.0.0.0/tcp/8116"]`, + rpc: { + listen_address: `"127.0.0.1:8115"`, + }, + network: { + listen_addresses: `["/ip4/0.0.0.0/tcp/8116"]`, + }, }) let promise = stopCkbNode() stubbedCkb.emit('close') diff --git a/packages/neuron-wallet/tests/services/light-runner.test.ts b/packages/neuron-wallet/tests/services/light-runner.test.ts index a88ed7a1ec..1a2a2fe7ed 100644 --- a/packages/neuron-wallet/tests/services/light-runner.test.ts +++ b/packages/neuron-wallet/tests/services/light-runner.test.ts @@ -337,32 +337,46 @@ describe('test light runner', () => { describe('test update config', () => { it('port is used', async () => { - getUsablePortMock.mockResolvedValueOnce(9001) + getUsablePortMock.mockResolvedValueOnce(9001).mockResolvedValueOnce(8119) lightDataPathMock.mockReturnValue('lightDataPath') resolveMock.mockImplementation((...v: string[]) => v.join('')) joinMock.mockImplementation((...v: string[]) => v.join('')) await CKBLightRunner.getInstance().updateConfig() expect(CKBLightRunner.getInstance().port).toEqual(9001) expect(updateTomlMock).toHaveBeenCalledWith('lightDataPath./ckb_light.toml', { - store: `path = "lightDataPath./store"`, - network: `path = "lightDataPath./network"`, - rpc: `listen_address = "127.0.0.1:9001"`, + store: { + path: `"lightDataPath./store"`, + }, + network: { + listen_addresses: '["/ip4/0.0.0.0/tcp/8119"]', + path: `"lightDataPath./network"`, + }, + rpc: { + listen_address: `"127.0.0.1:9001"`, + }, }) //reset port getUsablePortMock.mockResolvedValueOnce(9000) await CKBLightRunner.getInstance().updateConfig() }) it('port is not used', async () => { - getUsablePortMock.mockResolvedValueOnce(9000) + getUsablePortMock.mockResolvedValueOnce(9000).mockResolvedValueOnce(8118) lightDataPathMock.mockReturnValue('lightDataPath') resolveMock.mockImplementation((...v: string[]) => v.join('')) joinMock.mockImplementation((...v: string[]) => v.join('')) await CKBLightRunner.getInstance().updateConfig() expect(CKBLightRunner.getInstance().port).toEqual(9000) expect(updateTomlMock).toHaveBeenCalledWith('lightDataPath./ckb_light.toml', { - store: `path = "lightDataPath./store"`, - network: `path = "lightDataPath./network"`, - rpc: `listen_address = "127.0.0.1:9000"`, + store: { + path: `"lightDataPath./store"`, + }, + network: { + listen_addresses: '["/ip4/0.0.0.0/tcp/8118"]', + path: `"lightDataPath./network"`, + }, + rpc: { + listen_address: `"127.0.0.1:9000"`, + }, }) }) }) diff --git a/packages/neuron-wallet/tests/utils/toml/test.toml b/packages/neuron-wallet/tests/utils/toml/test.toml index 7a693b7094..e5c6426928 100644 --- a/packages/neuron-wallet/tests/utils/toml/test.toml +++ b/packages/neuron-wallet/tests/utils/toml/test.toml @@ -17,6 +17,7 @@ log_to_stdout = true [network] network = 127 +listen_addresses = ["/ip4/0.0.0.0/tcp/8115"] ### Specify the public and routable network addresses # public_addresses = [] diff --git a/packages/neuron-wallet/tests/utils/toml/toml.test.ts b/packages/neuron-wallet/tests/utils/toml/toml.test.ts index 37ea295d0c..1184bcb430 100644 --- a/packages/neuron-wallet/tests/utils/toml/toml.test.ts +++ b/packages/neuron-wallet/tests/utils/toml/toml.test.ts @@ -27,41 +27,50 @@ describe('test toml', () => { resetMock() }) describe('test update toml', () => { - it('update skip comment', () => { + it('update skip non exist key', () => { const tomlPath = path.resolve(__dirname, './test.toml') updateToml(tomlPath, { - network: 'network = 127', + network: { + network1: '127', + }, }) - expect(writeFileSyncMock).toBeCalledWith( - tomlPath, - fs.readFileSync(tomlPath).toString().replace('listen_addresses = ["/ip4/0.0.0.0/tcp/8115"]', 'network = 127') - ) + expect(writeFileSyncMock).toBeCalledWith(tomlPath, fs.readFileSync(tomlPath).toString()) }) it('update multi values with new file and dir exist', () => { const tomlPath = path.resolve(__dirname, './test.toml') existsSyncMock.mockReturnValue(true) - updateToml(tomlPath, { network: 'network = 127', rpc: 'rpc = 8116' }, path.resolve(__dirname, './new.toml')) + updateToml( + tomlPath, + { network: { network: '126', listen_addresses: 'test' }, rpc: { listen_address: 'listen_address' } }, + path.resolve(__dirname, './new.toml') + ) expect(writeFileSyncMock).toBeCalledWith( path.resolve(__dirname, './new.toml'), fs .readFileSync(tomlPath) .toString() - .replace('listen_addresses = ["/ip4/0.0.0.0/tcp/8115"]', 'network = 127') - .replace('listen_address = "127.0.0.1:8114"', 'rpc = 8116') + .replace('network = 127', 'network = 126') + .replace('listen_addresses = ["/ip4/0.0.0.0/tcp/8115"]', 'listen_addresses = test') + .replace('listen_address = "127.0.0.1:8114"', 'listen_address = listen_address') ) }) it('update multi values with new file and dir not exist', () => { const tomlPath = path.resolve(__dirname, './test.toml') existsSyncMock.mockReturnValue(false) - updateToml(tomlPath, { network: 'network = 127', rpc: 'rpc = 8116' }, path.resolve(__dirname, './new.toml')) + updateToml( + tomlPath, + { network: { network: '126', listen_addresses: 'test' }, rpc: { listen_address: 'listen_address' } }, + path.resolve(__dirname, './new.toml') + ) expect(mkdirSyncMock).toBeCalledWith(__dirname, { recursive: true }) expect(writeFileSyncMock).toBeCalledWith( path.resolve(__dirname, './new.toml'), fs .readFileSync(tomlPath) .toString() - .replace('listen_addresses = ["/ip4/0.0.0.0/tcp/8115"]', 'network = 127') - .replace('listen_address = "127.0.0.1:8114"', 'rpc = 8116') + .replace('network = 127', 'network = 126') + .replace('listen_addresses = ["/ip4/0.0.0.0/tcp/8115"]', 'listen_addresses = test') + .replace('listen_address = "127.0.0.1:8114"', 'listen_address = listen_address') ) }) }) From 29ba4442fb538bf891c64bc41cad9aefd79d7181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Tue, 4 Jun 2024 20:14:13 +0800 Subject: [PATCH 11/57] feat: Support set start block number for multisig address (#3176) * feat: Support set start block number for multisig address * fix: Add pause status for wallet sync --- .../src/components/AmendSUDTSend/index.tsx | 2 +- .../src/components/AmendSend/index.tsx | 2 +- .../src/components/MultisigAddress/hooks.ts | 80 +++++++++- .../src/components/MultisigAddress/index.tsx | 140 ++++++++++++++--- .../multisigAddress.module.scss | 53 +++++-- .../src/components/NervosDAORecord/index.tsx | 2 +- .../src/components/PageContainer/hooks.ts | 102 ++---------- .../src/components/PageContainer/index.tsx | 74 ++------- .../PageContainer/pageContainer.module.scss | 42 ----- .../SetStartBlockNumberDialog/index.tsx | 148 ++++++++++++++++++ .../setStartBlockNumberDialog.module.scss | 41 +++++ .../src/components/SpecialAssetList/index.tsx | 4 +- .../src/components/SyncStatus/index.tsx | 4 + .../neuron-ui/src/containers/Main/index.tsx | 2 +- packages/neuron-ui/src/locales/en.json | 1 + packages/neuron-ui/src/locales/es.json | 1 + packages/neuron-ui/src/locales/fr.json | 1 + packages/neuron-ui/src/locales/zh-tw.json | 1 + packages/neuron-ui/src/locales/zh.json | 1 + .../neuron-ui/src/services/remote/multisig.ts | 2 + .../src/services/remote/remoteApiWrapper.ts | 1 + packages/neuron-ui/src/types/App/index.d.ts | 2 +- packages/neuron-ui/src/utils/enums.ts | 2 + .../neuron-ui/src/widgets/Button/index.tsx | 8 +- .../{NoDiskSpaceWarn.png => Attention.png} | Bin packages/neuron-ui/src/widgets/Icons/icon.tsx | 6 + .../widgets/MigrateCkbDataDialog/index.tsx | 2 +- .../src/block-sync-renderer/index.ts | 26 ++- .../sync/light-synchronizer.ts | 113 ++++++++----- .../src/block-sync-renderer/sync/queue.ts | 13 +- .../block-sync-renderer/sync/synchronizer.ts | 13 +- .../src/block-sync-renderer/task.ts | 10 +- packages/neuron-wallet/src/controllers/api.ts | 9 +- .../neuron-wallet/src/controllers/multisig.ts | 32 ++++ .../chain/entities/multisig-config.ts | 3 + ...820157100-RemoveAddressesMultisigConfig.ts | 4 +- .../1681360188494-AddTypeSyncProgress.ts | 7 +- .../1702781527414-RenameSyncProgress.ts | 19 +-- .../1716539079505-AddStartBlockNumber.ts | 14 ++ .../src/database/chain/ormconfig.ts | 2 + .../multisig-config-db-changed-subject.ts | 2 +- .../neuron-wallet/src/services/multisig.ts | 34 ++-- .../src/services/sync-progress.ts | 19 ++- .../index/createBlockSyncTask.test.ts | 1 + .../light-synchronizer.test.ts | 7 +- .../tests/block-sync-renderer/task.test.ts | 3 +- .../tests/services/multisig.test.ts | 24 +++ 47 files changed, 714 insertions(+), 365 deletions(-) create mode 100644 packages/neuron-ui/src/components/SetStartBlockNumberDialog/index.tsx create mode 100644 packages/neuron-ui/src/components/SetStartBlockNumberDialog/setStartBlockNumberDialog.module.scss rename packages/neuron-ui/src/widgets/Icons/{NoDiskSpaceWarn.png => Attention.png} (100%) create mode 100644 packages/neuron-wallet/src/database/chain/migrations/1716539079505-AddStartBlockNumber.ts diff --git a/packages/neuron-ui/src/components/AmendSUDTSend/index.tsx b/packages/neuron-ui/src/components/AmendSUDTSend/index.tsx index 5cdbc79812..66250d03b6 100644 --- a/packages/neuron-ui/src/components/AmendSUDTSend/index.tsx +++ b/packages/neuron-ui/src/components/AmendSUDTSend/index.tsx @@ -198,7 +198,7 @@ const AmendSUDTSend = () => {
-
diff --git a/packages/neuron-ui/src/components/AmendSend/index.tsx b/packages/neuron-ui/src/components/AmendSend/index.tsx index 7ba3873d1e..453eef097b 100644 --- a/packages/neuron-ui/src/components/AmendSend/index.tsx +++ b/packages/neuron-ui/src/components/AmendSend/index.tsx @@ -250,7 +250,7 @@ const AmendSend = () => { {t('send.allow-use-sent-cell')}
-
diff --git a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts index 20853bdbca..d5a431f22c 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts +++ b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts @@ -79,16 +79,21 @@ export const useConfigManage = ({ walletId, isMainnet }: { walletId: string; isM } }) }, [setEntities]) - const updateConfig = useCallback( + const onUpdateConfig = useCallback((values: Partial & { id: number }) => { + return updateMultisigConfig(values).then(res => { + if (isSuccessResponse(res)) { + setEntities(v => v.map(config => (res.result && config.id === res.result?.id ? res.result : config))) + } else { + throw new Error(typeof res.message === 'string' ? res.message : res.message.content!) + } + }) + }, []) + const onUpdateConfigAlias = useCallback( (id: number) => (e: React.SyntheticEvent) => { const { value } = e.target as HTMLInputElement - updateMultisigConfig({ id, alias: value || '' }).then(res => { - if (isSuccessResponse(res)) { - setEntities(v => v.map(config => (res.result && config.id === res.result?.id ? res.result : config))) - } - }) + onUpdateConfig({ id, alias: value || '' }) }, - [setEntities] + [onUpdateConfig] ) const deleteConfigById = useCallback( (id: number) => { @@ -141,7 +146,8 @@ export const useConfigManage = ({ walletId, isMainnet }: { walletId: string; isM return { saveConfig, allConfigs, - updateConfig, + onUpdateConfigAlias, + onUpdateConfig, deleteConfigById, onImportConfig, configs, @@ -365,3 +371,61 @@ export const useSubscription = ({ }, [isLightClient, getAndSaveMultisigSyncProgress]) return { multisigBanlances, multisigSyncProgress } } + +export const useCancelWithLightClient = () => { + const [isCloseWarningDialogShow, setIsCloseWarningDialogShow] = useState(false) + const onCancel = useCallback(() => { + setIsCloseWarningDialogShow(true) + }, [setIsCloseWarningDialogShow]) + const onCancelCloseMultisigDialog = useCallback(() => { + setIsCloseWarningDialogShow(false) + }, [setIsCloseWarningDialogShow]) + return { + isCloseWarningDialogShow, + onCancel, + onCancelCloseMultisigDialog, + } +} + +export const useSetStartBlockNumber = ({ + onUpdateConfig, +}: { + onUpdateConfig: (v: Partial & { id: number }) => Promise +}) => { + const [isSetStartBlockShown, setIsSetStartBlockShown] = useState(false) + const [editId, setEditId] = useState() + const [address, setAddress] = useState() + const [lastStartBlockNumber, setLastStartBlockNumber] = useState() + const onConfirm = useCallback( + (startBlockNumber: number) => { + if (editId) { + return onUpdateConfig({ + id: editId, + startBlockNumber, + }).then(() => { + setIsSetStartBlockShown(false) + }) + } + return Promise.reject(new Error('The Edit multisig config is empty')) + }, + [editId] + ) + const openDialog = useCallback>(e => { + const { id, address: editAddress, startBlockNumber } = e.currentTarget.dataset + if (id) { + setEditId(+id) + } + setAddress(editAddress) + setLastStartBlockNumber(startBlockNumber ? +startBlockNumber : undefined) + setIsSetStartBlockShown(true) + }, []) + return { + openDialog, + closeDialog: useCallback(() => setIsSetStartBlockShown(false), []), + isSetStartBlockShown, + onConfirm, + address, + lastStartBlockNumber, + onCancel: useCallback(() => setIsSetStartBlockShown(false), []), + } +} diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx index 262fbe8596..f0f3f4bbf0 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useOnLocaleChange, @@ -11,24 +11,39 @@ import { useState as useGlobalState } from 'states' import MultisigAddressCreateDialog from 'components/MultisigAddressCreateDialog' import MultisigAddressInfo from 'components/MultisigAddressInfo' import SendFromMultisigDialog from 'components/SendFromMultisigDialog' -import { MultisigConfig } from 'services/remote' +import { MultisigConfig, changeMultisigSyncStatus } from 'services/remote' import ApproveMultisigTxDialog from 'components/ApproveMultisigTxDialog' import Dialog from 'widgets/Dialog' import Table from 'widgets/Table' import Tooltip from 'widgets/Tooltip' import AlertDialog from 'widgets/AlertDialog' -import { ReactComponent as AddSimple } from 'widgets/Icons/AddSimple.svg' -import { ReactComponent as Details } from 'widgets/Icons/Details.svg' -import { ReactComponent as Delete } from 'widgets/Icons/Delete.svg' -import { ReactComponent as Confirm } from 'widgets/Icons/Confirm.svg' -import { ReactComponent as Transfer } from 'widgets/Icons/Transfer.svg' -import { ReactComponent as Upload } from 'widgets/Icons/Upload.svg' -import { ReactComponent as Edit } from 'widgets/Icons/Edit.svg' -import { Download, Search } from 'widgets/Icons/icon' +import { + Download, + Search, + AddSimple, + Details, + Delete, + Confirm, + Transfer, + Upload, + Edit, + Confirming, +} from 'widgets/Icons/icon' +import AttentionCloseDialog from 'widgets/Icons/Attention.png' import { HIDE_BALANCE, NetworkType } from 'utils/const' import { onEnter } from 'utils/inputDevice' import getMultisigSignStatus from 'utils/getMultisigSignStatus' -import { useSearch, useConfigManage, useExportConfig, useActions, useSubscription } from './hooks' +import Button from 'widgets/Button' +import SetStartBlockNumberDialog from 'components/SetStartBlockNumberDialog' +import { + useSearch, + useConfigManage, + useExportConfig, + useActions, + useSubscription, + useCancelWithLightClient, + useSetStartBlockNumber, +} from './hooks' import styles from './multisigAddress.module.scss' @@ -58,7 +73,11 @@ const MultisigAddress = () => { useExitOnWalletChange() const { wallet: { id: walletId, addresses }, - chain: { networkID }, + chain: { + syncState: { bestKnownBlockNumber }, + networkID, + connectionStatus, + }, settings: { networks = [] }, } = useGlobalState() const isMainnet = isMainnetUtil(networks, networkID) @@ -67,11 +86,19 @@ const MultisigAddress = () => { [networks, networkID] ) const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) - const { allConfigs, saveConfig, updateConfig, deleteConfigById, onImportConfig, configs, onFilterConfig } = - useConfigManage({ - walletId, - isMainnet, - }) + const { + allConfigs, + saveConfig, + onUpdateConfig, + onUpdateConfigAlias, + deleteConfigById, + onImportConfig, + configs, + onFilterConfig, + } = useConfigManage({ + walletId, + isMainnet, + }) const { multisigBanlances, multisigSyncProgress } = useSubscription({ walletId, isMainnet, @@ -141,10 +168,39 @@ const MultisigAddress = () => { }, [multisigBanlances, sendAction.sendFromMultisig]) const onBack = useGoBack() + const { + onCancel: onCancelWithLight, + isCloseWarningDialogShow, + onCancelCloseMultisigDialog, + } = useCancelWithLightClient() + const { + isSetStartBlockShown, + openDialog: openSetStartBlockNumber, + lastStartBlockNumber, + address, + onConfirm, + onCancel, + } = useSetStartBlockNumber({ onUpdateConfig }) + + useEffect(() => { + if (isLightClient) { + changeMultisigSyncStatus(true) + } + return () => { + if (isLightClient) { + changeMultisigSyncStatus(false) + } + } + }, [isLightClient]) return (
- +
@@ -221,8 +277,8 @@ const MultisigAddress = () => {