@@ -5,21 +5,15 @@ import {
5
5
IndexerCell ,
6
6
leToU128 ,
7
7
isScriptEqual ,
8
- buildPreLockArgs ,
9
8
buildRgbppLockArgs ,
10
9
genRgbppLockScript ,
11
- getRgbppLockScript ,
12
- genBtcTimeLockArgs ,
13
- getBtcTimeLockScript ,
14
10
btcTxIdFromBtcTimeLockArgs ,
15
- calculateCommitment ,
16
- BTCTimeLock ,
17
11
RGBPP_TX_ID_PLACEHOLDER ,
18
12
RGBPP_TX_INPUTS_MAX_LENGTH ,
19
13
} from '@rgbpp-sdk/ckb' ;
20
14
import { remove0x } from '@rgbpp-sdk/btc' ;
21
15
import { unpackRgbppLockArgs } from '@rgbpp-sdk/btc/lib/ckb/molecule' ;
22
- import { groupBy , cloneDeep , uniq } from 'lodash' ;
16
+ import { groupBy , uniq , findLastIndex } from 'lodash' ;
23
17
import { z } from 'zod' ;
24
18
import { Job } from 'bullmq' ;
25
19
import { BI , RPC , Script } from '@ckb-lumos/lumos' ;
@@ -30,8 +24,9 @@ import { Transaction, UTXO } from './bitcoin/schema';
30
24
import BaseQueueWorker from './base/queue-worker' ;
31
25
import DataCache from './base/data-cache' ;
32
26
import { Cradle } from '../container' ;
33
- import { TestnetTypeMap } from '../constants' ;
34
- import { tryGetCommitmentFromBtcTx } from '../utils/commitment' ;
27
+ import { isCommitmentMatchToCkbTx , tryGetCommitmentFromBtcTx } from '../utils/commitment' ;
28
+ import { getBtcTimeLock , isBtcTimeLock , isRgbppLock } from '../utils/lockscript' ;
29
+ import { IS_MAINNET , TESTNET_TYPE } from '../constants' ;
35
30
36
31
type GetCellsParams = Parameters < RPC [ 'getCells' ] > ;
37
32
export type SearchKey = GetCellsParams [ 0 ] ;
@@ -92,30 +87,6 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
92
87
this . limit = pLimit ( 100 ) ;
93
88
}
94
89
95
- private get isMainnet ( ) {
96
- return this . cradle . env . NETWORK === 'mainnet' ;
97
- }
98
-
99
- private get testnetType ( ) {
100
- return TestnetTypeMap [ this . cradle . env . NETWORK ] ;
101
- }
102
-
103
- private get rgbppLockScript ( ) {
104
- return getRgbppLockScript ( this . isMainnet , this . testnetType ) ;
105
- }
106
-
107
- private get btcTimeLockScript ( ) {
108
- return getBtcTimeLockScript ( this . isMainnet , this . testnetType ) ;
109
- }
110
-
111
- private isRgbppLock ( lock : CKBComponents . Script ) {
112
- return lock . codeHash === this . rgbppLockScript . codeHash && lock . hashType === this . rgbppLockScript . hashType ;
113
- }
114
-
115
- private isBtcTimeLock ( lock : CKBComponents . Script ) {
116
- return lock . codeHash === this . btcTimeLockScript . codeHash && lock . hashType === this . btcTimeLockScript . hashType ;
117
- }
118
-
119
90
/**
120
91
* Capture the exception to the sentry scope with the btc address and utxos
121
92
* @param job - the job that failed
@@ -189,7 +160,7 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
189
160
const { txid, vout } = utxo ;
190
161
const args = buildRgbppLockArgs ( vout , txid ) ;
191
162
const searchKey : SearchKey = {
192
- script : genRgbppLockScript ( args , this . isMainnet , this . testnetType ) ,
163
+ script : genRgbppLockScript ( args , IS_MAINNET , TESTNET_TYPE ) ,
193
164
scriptType : 'lock' ,
194
165
} ;
195
166
if ( typeScript ) {
@@ -277,7 +248,7 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
277
248
const batchRequest = this . cradle . ckb . rpc . createBatchRequest (
278
249
btcTx . vout . map ( ( _ , index ) => {
279
250
const args = buildRgbppLockArgs ( index , btcTx . txid ) ;
280
- const lock = genRgbppLockScript ( args , this . isMainnet , this . testnetType ) ;
251
+ const lock = genRgbppLockScript ( args , IS_MAINNET , TESTNET_TYPE ) ;
281
252
const searchKey : SearchKey = {
282
253
script : lock ,
283
254
scriptType : 'lock' ,
@@ -291,7 +262,6 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
291
262
for ( const indexerTx of tx . objects ) {
292
263
const ckbTx = await this . cradle . ckb . rpc . getTransaction ( indexerTx . txHash ) ;
293
264
const isIsomorphic = await this . isIsomorphicTx ( btcTx , ckbTx . transaction ) ;
294
- // console.log('isIsomorphic', btcTx.txid, ckbTx.transaction.hash, isIsomorphic);
295
265
if ( isIsomorphic ) {
296
266
return indexerTx ;
297
267
}
@@ -302,21 +272,27 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
302
272
303
273
public async queryBtcTimeLockTxByBtcTxId ( btcTxId : string ) {
304
274
// XXX: unstable, need to be improved: https://github.com/ckb-cell/btc-assets-api/issues/45
305
- const btcTimeLockTxs = await this . cradle . ckb . indexer . getTransactions ( {
306
- script : {
307
- ...this . btcTimeLockScript ,
308
- args : '0x' ,
275
+ const btcTimeLockTxs = await this . cradle . ckb . indexer . getTransactions (
276
+ {
277
+ script : {
278
+ ...getBtcTimeLock ( ) ,
279
+ args : '0x' ,
280
+ } ,
281
+ scriptType : 'lock' ,
282
+ groupByTransaction : true ,
309
283
} ,
310
- scriptType : 'lock' ,
311
- } ) ;
284
+ {
285
+ order : 'asc' ,
286
+ } ,
287
+ ) ;
312
288
313
289
const txHashes = uniq ( btcTimeLockTxs . objects . map ( ( { txHash } ) => txHash ) ) ;
314
290
const batchRequest = this . cradle . ckb . rpc . createBatchRequest ( txHashes . map ( ( txHash ) => [ 'getTransaction' , txHash ] ) ) ;
315
291
const transactions : TransactionWithStatus [ ] = await batchRequest . exec ( ) ;
316
292
if ( transactions . length > 0 ) {
317
293
for ( const tx of transactions ) {
318
294
const isBtcTimeLockTx = tx . transaction . outputs . some ( ( output ) => {
319
- if ( ! isScriptEqual ( output . lock , this . btcTimeLockScript ) ) {
295
+ if ( ! isScriptEqual ( output . lock , getBtcTimeLock ( ) ) ) {
320
296
return false ;
321
297
}
322
298
const outputBtcTxId = btcTxIdFromBtcTimeLockArgs ( output . lock . args ) ;
@@ -330,18 +306,11 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
330
306
return null ;
331
307
}
332
308
333
- async isIsomorphicTx ( btcTx : Transaction , ckbTx : CKBComponents . RawTransaction , validateCommitment ?: boolean ) {
334
- const replaceLockArgsWithPlaceholder = ( cell : CKBComponents . CellOutput , index : number ) => {
335
- if ( this . isRgbppLock ( cell . lock ) ) {
336
- cell . lock . args = buildPreLockArgs ( index + 1 ) ;
337
- }
338
- if ( this . isBtcTimeLock ( cell . lock ) ) {
339
- const lockArgs = BTCTimeLock . unpack ( cell . lock . args ) ;
340
- cell . lock . args = genBtcTimeLockArgs ( lockArgs . lockScript , RGBPP_TX_ID_PLACEHOLDER , lockArgs . after ) ;
341
- }
342
- return cell ;
343
- } ;
344
-
309
+ async isIsomorphicTx (
310
+ btcTx : Transaction ,
311
+ ckbTx : CKBComponents . RawTransaction ,
312
+ validateCommitment ?: boolean ,
313
+ ) : Promise < boolean > {
345
314
// Find the commitment from the btc_tx
346
315
const btcTxCommitment = tryGetCommitmentFromBtcTx ( btcTx ) ;
347
316
if ( ! btcTxCommitment ) {
@@ -352,86 +321,68 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
352
321
// 1. Find the last index of the type inputs
353
322
// 2. Check if all rgbpp_lock inputs can be found in the btc_tx.vin
354
323
// 3. Check if the inputs contain at least one rgbpp_lock cell (as L1-L1 and L1-L2 transactions should have)
355
- let lastTypeInputIndex = - 1 ;
356
- let foundRgbppLockInput = false ;
357
- const outPoints = ckbTx . inputs . map ( ( input ) => input . previousOutput ! ) ;
358
- const inputs = await this . cradle . ckb . getInputCellsByOutPoint ( outPoints ) ;
359
- for ( let i = 0 ; i < inputs . length ; i ++ ) {
360
- if ( inputs [ i ] . type ) {
361
- lastTypeInputIndex = i ;
362
- const isRgbppLock = this . isRgbppLock ( inputs [ i ] . lock ) ;
363
- if ( isRgbppLock ) {
364
- foundRgbppLockInput = true ;
365
- const btcInput = btcTx . vin [ i ] ;
366
- const rgbppLockArgs = unpackRgbppLockArgs ( inputs [ i ] . lock . args ) ;
367
- if (
368
- ! btcInput ||
369
- btcInput . txid !== remove0x ( rgbppLockArgs . btcTxid ) ||
370
- btcInput . vout !== rgbppLockArgs . outIndex
371
- ) {
372
- return false ;
373
- }
374
- }
375
- }
324
+ const inputs = await this . cradle . ckb . getInputCellsByOutPoint ( ckbTx . inputs . map ( ( input ) => input . previousOutput ! ) ) ;
325
+ const lastTypeInputIndex = findLastIndex ( inputs , ( input ) => ! ! input . cellOutput . type ) ;
326
+ const anyRgbppLockInput = inputs . some ( ( input ) => isRgbppLock ( input . cellOutput . lock ) ) ;
327
+ if ( ! anyRgbppLockInput ) {
328
+ return false ;
376
329
}
377
- // XXX: In some type of RGB++ transactions, the inputs may not contain any rgbpp_lock cells
378
- // We add this check to ensure this function only validates for L1-L1 and L1-L2 transactions
379
- if ( ! foundRgbppLockInput ) {
330
+ const allInputsValid = inputs . every ( ( input , index ) => {
331
+ if ( ! input . cellOutput . type ) {
332
+ return true ;
333
+ }
334
+ if ( ! isRgbppLock ( input . cellOutput . lock ) ) {
335
+ return true ;
336
+ }
337
+ const btcInput = btcTx . vin [ index ] ;
338
+ const rgbppLockArgs = unpackRgbppLockArgs ( input . cellOutput . lock . args ) ;
339
+ return btcInput && btcInput . txid === remove0x ( rgbppLockArgs . btcTxid ) && btcInput . vout === rgbppLockArgs . outIndex ;
340
+ } ) ;
341
+ if ( ! allInputsValid ) {
380
342
return false ;
381
343
}
382
344
383
345
// Check outputs:
384
- // 1. Find the last index of the type outputs
385
- // 2. Check if all type outputs are rgbpp_lock/btc_time_lock cells
386
- // 3. Check if each rgbpp_lock cell has an isomorphic UTXO in the btc_tx.vout
387
- // 4. Check if each btc_time_lock cell contains the corresponding btc_txid in the lock args
388
- // 5. Check if the outputs contain at least one rgbpp_lock/btc_time_lock cell
389
- let lastTypeOutputIndex = - 1 ;
390
- for ( let i = 0 ; i < ckbTx . outputs . length ; i ++ ) {
391
- const ckbOutput = ckbTx . outputs [ i ] ;
392
- const isRgbppLock = this . isRgbppLock ( ckbOutput . lock ) ;
393
- const isBtcTimeLock = this . isBtcTimeLock ( ckbOutput . lock ) ;
394
- if ( isRgbppLock ) {
395
- const rgbppLockArgs = unpackRgbppLockArgs ( ckbOutput . lock . args ) ;
346
+ // 1. Find the last index of the type outputs, and check if at least one type output exists
347
+ // 2. Check if all type outputs are rgbpp_lock or btc_time_lock cells
348
+ // 4. Check if each rgbpp_lock cell has an isomorphic UTXO in the btc_tx.vout
349
+ // 5. Check if each btc_time_lock cell contains the corresponding btc_txid in the lock args
350
+ const lastTypeOutputIndex = findLastIndex ( ckbTx . outputs , ( output ) => ! ! output . type ) ;
351
+ if ( lastTypeOutputIndex < 0 ) {
352
+ return false ;
353
+ }
354
+ const anyRelatedLockToTypeOutput = ckbTx . outputs . some (
355
+ ( output ) => output . type && ( isRgbppLock ( output . lock ) || isBtcTimeLock ( output . lock ) ) ,
356
+ ) ;
357
+ if ( ! anyRelatedLockToTypeOutput ) {
358
+ return false ;
359
+ }
360
+ const allOutputsValid = ckbTx . outputs . every ( ( output ) => {
361
+ if ( isRgbppLock ( output . lock ) ) {
362
+ const rgbppLockArgs = unpackRgbppLockArgs ( output . lock . args ) ;
396
363
const btcTxId = remove0x ( rgbppLockArgs . btcTxid ) ;
397
364
if ( btcTxId !== RGBPP_TX_ID_PLACEHOLDER && ( btcTxId !== btcTx . txid || ! btcTx . vout [ rgbppLockArgs . outIndex ] ) ) {
398
365
return false ;
399
366
}
400
367
}
401
- if ( isBtcTimeLock ) {
402
- const btcTxId = remove0x ( btcTxIdFromBtcTimeLockArgs ( ckbOutput . lock . args ) ) ;
368
+ if ( isBtcTimeLock ( output . lock ) ) {
369
+ const btcTxId = remove0x ( btcTxIdFromBtcTimeLockArgs ( output . lock . args ) ) ;
403
370
if ( btcTxId !== RGBPP_TX_ID_PLACEHOLDER && btcTx . txid !== btcTxId ) {
404
371
return false ;
405
372
}
406
373
}
407
- if ( ckbOutput . type ) {
408
- lastTypeOutputIndex = i ;
409
- }
410
- }
411
- if ( lastTypeOutputIndex < 0 ) {
374
+ return true ;
375
+ } ) ;
376
+ if ( ! allOutputsValid ) {
412
377
return false ;
413
378
}
414
379
415
- // Cut the ckb_tx to simulate how the ckb_virtual_tx looks like
416
- const ckbVirtualTx = cloneDeep ( ckbTx ) ;
417
- ckbVirtualTx . inputs = ckbVirtualTx . inputs . slice ( 0 , Math . max ( lastTypeInputIndex , 0 ) + 1 ) ;
418
- ckbVirtualTx . outputs = ckbVirtualTx . outputs . slice ( 0 , lastTypeOutputIndex + 1 ) . map ( replaceLockArgsWithPlaceholder ) ;
419
-
420
- // Copy ckb_tx and change output lock args to placeholder args
421
- const ckbPlaceholderTx = cloneDeep ( ckbTx ) ;
422
- ckbPlaceholderTx . outputs = ckbPlaceholderTx . outputs . map ( replaceLockArgsWithPlaceholder ) ;
380
+ // Compare commitment between btc_tx and ckb_tx
423
381
if ( ! validateCommitment ) {
424
382
return true ;
425
383
}
426
-
427
- // Generate commitment with the ckb_tx/ckb_virtual_tx, then compare it with the btc_tx commitment.
428
- // If both commitments don't match the btc_tx commitment:
429
- // 1. The ckb_tx is not the isomorphic transaction of the btc_tx (this is the usual case)
430
- // 2. The commitment calculation logic differs from the one used in the btc_tx/ckb_tx
431
- const ckbTxCommitment = calculateCommitment ( ckbPlaceholderTx ) ;
432
- const ckbVirtualTxCommitment = calculateCommitment ( ckbVirtualTx ) ;
433
384
const btcTxCommitmentHex = btcTxCommitment . toString ( 'hex' ) ;
434
- return btcTxCommitmentHex === ckbVirtualTxCommitment || btcTxCommitmentHex === ckbTxCommitment ;
385
+ return isCommitmentMatchToCkbTx ( btcTxCommitmentHex , ckbTx , lastTypeInputIndex , lastTypeOutputIndex ) ;
435
386
}
436
387
437
388
/**
0 commit comments