@@ -5,21 +5,15 @@ import {
55 IndexerCell ,
66 leToU128 ,
77 isScriptEqual ,
8- buildPreLockArgs ,
98 buildRgbppLockArgs ,
109 genRgbppLockScript ,
11- getRgbppLockScript ,
12- genBtcTimeLockArgs ,
13- getBtcTimeLockScript ,
1410 btcTxIdFromBtcTimeLockArgs ,
15- calculateCommitment ,
16- BTCTimeLock ,
1711 RGBPP_TX_ID_PLACEHOLDER ,
1812 RGBPP_TX_INPUTS_MAX_LENGTH ,
1913} from '@rgbpp-sdk/ckb' ;
2014import { remove0x } from '@rgbpp-sdk/btc' ;
2115import { unpackRgbppLockArgs } from '@rgbpp-sdk/btc/lib/ckb/molecule' ;
22- import { groupBy , cloneDeep , uniq } from 'lodash' ;
16+ import { groupBy , uniq , findLastIndex } from 'lodash' ;
2317import { z } from 'zod' ;
2418import { Job } from 'bullmq' ;
2519import { BI , RPC , Script } from '@ckb-lumos/lumos' ;
@@ -30,8 +24,9 @@ import { Transaction, UTXO } from './bitcoin/schema';
3024import BaseQueueWorker from './base/queue-worker' ;
3125import DataCache from './base/data-cache' ;
3226import { 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' ;
3530
3631type GetCellsParams = Parameters < RPC [ 'getCells' ] > ;
3732export type SearchKey = GetCellsParams [ 0 ] ;
@@ -92,30 +87,6 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
9287 this . limit = pLimit ( 100 ) ;
9388 }
9489
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-
11990 /**
12091 * Capture the exception to the sentry scope with the btc address and utxos
12192 * @param job - the job that failed
@@ -189,7 +160,7 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
189160 const { txid, vout } = utxo ;
190161 const args = buildRgbppLockArgs ( vout , txid ) ;
191162 const searchKey : SearchKey = {
192- script : genRgbppLockScript ( args , this . isMainnet , this . testnetType ) ,
163+ script : genRgbppLockScript ( args , IS_MAINNET , TESTNET_TYPE ) ,
193164 scriptType : 'lock' ,
194165 } ;
195166 if ( typeScript ) {
@@ -277,7 +248,7 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
277248 const batchRequest = this . cradle . ckb . rpc . createBatchRequest (
278249 btcTx . vout . map ( ( _ , index ) => {
279250 const args = buildRgbppLockArgs ( index , btcTx . txid ) ;
280- const lock = genRgbppLockScript ( args , this . isMainnet , this . testnetType ) ;
251+ const lock = genRgbppLockScript ( args , IS_MAINNET , TESTNET_TYPE ) ;
281252 const searchKey : SearchKey = {
282253 script : lock ,
283254 scriptType : 'lock' ,
@@ -291,7 +262,6 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
291262 for ( const indexerTx of tx . objects ) {
292263 const ckbTx = await this . cradle . ckb . rpc . getTransaction ( indexerTx . txHash ) ;
293264 const isIsomorphic = await this . isIsomorphicTx ( btcTx , ckbTx . transaction ) ;
294- // console.log('isIsomorphic', btcTx.txid, ckbTx.transaction.hash, isIsomorphic);
295265 if ( isIsomorphic ) {
296266 return indexerTx ;
297267 }
@@ -302,21 +272,27 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
302272
303273 public async queryBtcTimeLockTxByBtcTxId ( btcTxId : string ) {
304274 // 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 ,
309283 } ,
310- scriptType : 'lock' ,
311- } ) ;
284+ {
285+ order : 'asc' ,
286+ } ,
287+ ) ;
312288
313289 const txHashes = uniq ( btcTimeLockTxs . objects . map ( ( { txHash } ) => txHash ) ) ;
314290 const batchRequest = this . cradle . ckb . rpc . createBatchRequest ( txHashes . map ( ( txHash ) => [ 'getTransaction' , txHash ] ) ) ;
315291 const transactions : TransactionWithStatus [ ] = await batchRequest . exec ( ) ;
316292 if ( transactions . length > 0 ) {
317293 for ( const tx of transactions ) {
318294 const isBtcTimeLockTx = tx . transaction . outputs . some ( ( output ) => {
319- if ( ! isScriptEqual ( output . lock , this . btcTimeLockScript ) ) {
295+ if ( ! isScriptEqual ( output . lock , getBtcTimeLock ( ) ) ) {
320296 return false ;
321297 }
322298 const outputBtcTxId = btcTxIdFromBtcTimeLockArgs ( output . lock . args ) ;
@@ -330,18 +306,11 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
330306 return null ;
331307 }
332308
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 > {
345314 // Find the commitment from the btc_tx
346315 const btcTxCommitment = tryGetCommitmentFromBtcTx ( btcTx ) ;
347316 if ( ! btcTxCommitment ) {
@@ -352,86 +321,68 @@ export default class RgbppCollector extends BaseQueueWorker<IRgbppCollectRequest
352321 // 1. Find the last index of the type inputs
353322 // 2. Check if all rgbpp_lock inputs can be found in the btc_tx.vin
354323 // 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 ;
376329 }
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 ) {
380342 return false ;
381343 }
382344
383345 // 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 ) ;
396363 const btcTxId = remove0x ( rgbppLockArgs . btcTxid ) ;
397364 if ( btcTxId !== RGBPP_TX_ID_PLACEHOLDER && ( btcTxId !== btcTx . txid || ! btcTx . vout [ rgbppLockArgs . outIndex ] ) ) {
398365 return false ;
399366 }
400367 }
401- if ( isBtcTimeLock ) {
402- const btcTxId = remove0x ( btcTxIdFromBtcTimeLockArgs ( ckbOutput . lock . args ) ) ;
368+ if ( isBtcTimeLock ( output . lock ) ) {
369+ const btcTxId = remove0x ( btcTxIdFromBtcTimeLockArgs ( output . lock . args ) ) ;
403370 if ( btcTxId !== RGBPP_TX_ID_PLACEHOLDER && btcTx . txid !== btcTxId ) {
404371 return false ;
405372 }
406373 }
407- if ( ckbOutput . type ) {
408- lastTypeOutputIndex = i ;
409- }
410- }
411- if ( lastTypeOutputIndex < 0 ) {
374+ return true ;
375+ } ) ;
376+ if ( ! allOutputsValid ) {
412377 return false ;
413378 }
414379
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
423381 if ( ! validateCommitment ) {
424382 return true ;
425383 }
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 ) ;
433384 const btcTxCommitmentHex = btcTxCommitment . toString ( 'hex' ) ;
434- return btcTxCommitmentHex === ckbVirtualTxCommitment || btcTxCommitmentHex === ckbTxCommitment ;
385+ return isCommitmentMatchToCkbTx ( btcTxCommitmentHex , ckbTx , lastTypeInputIndex , lastTypeOutputIndex ) ;
435386 }
436387
437388 /**
0 commit comments