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..2245f1b951 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 @@ -171,6 +172,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 @@ -178,13 +191,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(), @@ -198,32 +211,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')