diff --git a/.gitignore b/.gitignore index 10886dc0..c5077bd0 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ coverage data redis.conf dump.rdb +.envrc diff --git a/package.json b/package.json index d2f3e897..5aac9560 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@fastify/sensible": "^5.5.0", "@fastify/swagger": "^8.14.0", "@fastify/swagger-ui": "^3.0.0", + "@nervosnetwork/ckb-sdk-utils": "^0.109.1", "@rgbpp-sdk/btc": "0.0.0-snap-20240321073744", "@rgbpp-sdk/ckb": "0.0.0-snap-20240321073744", "@sentry/node": "^7.102.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44d0911a..fe7089b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ dependencies: '@fastify/swagger-ui': specifier: ^3.0.0 version: 3.0.0 + '@nervosnetwork/ckb-sdk-utils': + specifier: ^0.109.1 + version: 0.109.1 '@rgbpp-sdk/btc': specifier: 0.0.0-snap-20240321073744 version: 0.0.0-snap-20240321073744 diff --git a/src/services/unlocker.ts b/src/services/unlocker.ts index 5c00b5db..41f877dd 100644 --- a/src/services/unlocker.ts +++ b/src/services/unlocker.ts @@ -41,7 +41,7 @@ export default class Unlocker implements IUnlocker { /** * Get next batch of BTC time lock cells */ - private async getNextBatchLockCell() { + public async getNextBatchLockCell() { const collect = this.collector.collect(); const cells: IndexerCell[] = []; diff --git a/test/services/unlocker.test.ts b/test/services/unlocker.test.ts new file mode 100644 index 00000000..bef15347 --- /dev/null +++ b/test/services/unlocker.test.ts @@ -0,0 +1,94 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import container from '../../src/container'; +import { describe, test, beforeEach, vi, expect, afterEach } from 'vitest'; +import Unlocker from '../../src/services/unlocker'; +import { Cell } from '@ckb-lumos/lumos'; +import { BTCTimeLock, genBtcTimeLockScript } from '@rgbpp-sdk/ckb'; + +describe('Unlocker', () => { + let unlocker: Unlocker; + + beforeEach(async () => { + const cradle = container.cradle; + // TODO: mock env.TRANSACTION_SPV_SERVICE_URL + unlocker = new Unlocker(cradle); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + }); + + function mockBtcTimeLockCell() { + vi.spyOn(BTCTimeLock, 'unpack').mockReturnValue({ + after: 6, + btcTxid: '0x12345', + lockScript: {} as unknown as CKBComponents.Script, + }); + vi.spyOn(unlocker['collector'], 'collect').mockImplementation(async function* () { + const toLock: CKBComponents.Script = { + args: '0xc0a45d9d7c024adcc8076c18b3f07c08de7c42120cdb7e6cbc05a28266b15b5f', + codeHash: '0x28e83a1277d48add8e72fadaa9248559e1b632bab2bd60b27955ebc4c03800a5', + hashType: 'data', + }; + yield { + blockNumber: '0x123', + outPoint: { + txHash: '0x', + index: '0x0', + }, + cellOutput: { + lock: genBtcTimeLockScript(toLock, false), + capacity: '0x123', + }, + data: '0x', + } as Cell; + yield { + blockNumber: '0x456', + outPoint: { + txHash: '0x', + index: '0x0', + }, + cellOutput: { + lock: genBtcTimeLockScript(toLock, false), + capacity: '0x456', + }, + data: '0x', + } as Cell; + }); + } + + test('getNextBatchLockCell: should skip unconfirmed btc tx', async () => { + // @ts-expect-error + vi.spyOn(unlocker['cradle'].bitcoind, 'getBlockchainInfo').mockResolvedValue({ blocks: 100 }); + // @ts-expect-error + vi.spyOn(unlocker['cradle'].bitcoind, 'getTransaction').mockResolvedValue({ blockheight: 95 }); + mockBtcTimeLockCell(); + + const cells = await unlocker.getNextBatchLockCell(); + expect(cells).toHaveLength(0); + }); + + test('getNextBatchLockCell: should return cells when btc tx is confirmed', async () => { + // @ts-expect-error + vi.spyOn(unlocker['cradle'].bitcoind, 'getBlockchainInfo').mockResolvedValue({ blocks: 101 }); + // @ts-expect-error + vi.spyOn(unlocker['cradle'].bitcoind, 'getTransaction').mockResolvedValue({ blockheight: 95 }); + mockBtcTimeLockCell(); + + const cells = await unlocker.getNextBatchLockCell(); + expect(cells).toHaveLength(2); + }); + + test('getNextBatchLockCell: should break when cells reach batch size', async () => { + unlocker['cradle'].env.UNLOCKER_CELL_BATCH_SIZE = 1; + + // @ts-expect-error + vi.spyOn(unlocker['cradle'].bitcoind, 'getBlockchainInfo').mockResolvedValue({ blocks: 101 }); + // @ts-expect-error + vi.spyOn(unlocker['cradle'].bitcoind, 'getTransaction').mockResolvedValue({ blockheight: 95 }); + mockBtcTimeLockCell(); + + const cells = await unlocker.getNextBatchLockCell(); + expect(cells).toHaveLength(1); + }); +});