Skip to content

Commit

Permalink
Merge pull request #200 from ckb-cell/feat/batch-queries
Browse files Browse the repository at this point in the history
feat(btc): batch queries to improve transaction build time
  • Loading branch information
Flouse authored May 29, 2024
2 parents 5785f5a + 3d41751 commit f0b99c7
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/smart-pigs-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rgbpp-sdk/btc": minor
---

Add p-limit and batch queries in the sendRgbppUtxos() and TxBuilder.validateInputs() to improve construction time
3 changes: 2 additions & 1 deletion packages/btc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"bip32": "^4.0.0",
"bitcoinjs-lib": "^6.1.5",
"ecpair": "^2.1.0",
"lodash": "^4.17.21"
"lodash": "^4.17.21",
"p-limit": "^3.1.0"
},
"devDependencies": {
"@types/lodash": "^4.17.0",
Expand Down
37 changes: 31 additions & 6 deletions packages/btc/src/api/sendRgbppUtxos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { InitOutput, TxAddressOutput, TxBuilder } from '../transaction/build';
import { networkTypeToConfig } from '../preset/config';
import { unpackRgbppLockArgs } from '../ckb/molecule';
import { createSendUtxosBuilder } from './sendUtxos';
import { limitPromiseBatchSize } from '../utils';

export interface SendRgbppUtxosProps {
ckbVirtualTx: CKBComponents.RawTransaction;
Expand Down Expand Up @@ -47,21 +48,45 @@ export async function createSendRgbppUtxosBuilder(props: SendRgbppUtxosProps): P
const config = networkTypeToConfig(props.source.networkType);
const isCkbMainnet = props.source.networkType === NetworkType.MAINNET;

// Batch querying live cells from CkbCollector
const ckbLiveCells = await Promise.all(
ckbVirtualTx.inputs.map((ckbInput) => {
return limitPromiseBatchSize(async () => {
const ckbLiveCell = await props.ckbCollector.getLiveCell(ckbInput.previousOutput!);
const isRgbppLock = isRgbppLockCell(ckbLiveCell.output, isCkbMainnet);
const lockArgs = isRgbppLock ? unpackRgbppLockArgs(ckbLiveCell.output.lock.args) : undefined;
return {
ckbLiveCell,
isRgbppLock,
lockArgs,
};
});
}),
);

// Batch querying UTXO from BtcAssetsApi
const btcUtxos = await Promise.all(
ckbLiveCells.map(({ ckbLiveCell, isRgbppLock }) => {
if (isRgbppLock) {
const args = unpackRgbppLockArgs(ckbLiveCell.output.lock.args);
return limitPromiseBatchSize(() => props.source.getUtxo(args.btcTxid, args.outIndex, props.onlyConfirmedUtxos));
}
return undefined;
}),
);

// Handle and check inputs
for (let i = 0; i < ckbVirtualTx.inputs.length; i++) {
const ckbInput = ckbVirtualTx.inputs[i];

const ckbLiveCell = await props.ckbCollector.getLiveCell(ckbInput.previousOutput!);
const isRgbppLock = isRgbppLockCell(ckbLiveCell.output, isCkbMainnet);
const { lockArgs, isRgbppLock } = ckbLiveCells[i];

// If input.lock == RgbppLock, add to inputs if:
// 1. input.lock.args can be unpacked to RgbppLockArgs
// 2. utxo can be found via the DataSource.getUtxo() API
// 3. utxo.scriptPk == addressToScriptPk(props.from)
// 4. utxo is not duplicated in the inputs
if (isRgbppLock) {
const args = unpackRgbppLockArgs(ckbLiveCell.output.lock.args);
const utxo = await props.source.getUtxo(args.btcTxid, args.outIndex, props.onlyConfirmedUtxos);
const args = lockArgs!;
const utxo = btcUtxos[i];
if (!utxo) {
throw TxBuildError.withComment(ErrorCodes.CANNOT_FIND_UTXO, `hash: ${args.btcTxid}, index: ${args.outIndex}`);
}
Expand Down
23 changes: 14 additions & 9 deletions packages/btc/src/transaction/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { NetworkType, RgbppBtcConfig } from '../preset/types';
import { AddressType, addressToScriptPublicKeyHex, getAddressType, isSupportedFromAddress } from '../address';
import { dataToOpReturnScriptPubkey, isOpReturnScriptPubkey } from './embed';
import { networkTypeToConfig } from '../preset/config';
import { limitPromiseBatchSize } from '../utils';
import { Utxo, utxoToInput } from './utxo';
import { FeeEstimator } from './fee';

Expand Down Expand Up @@ -86,15 +87,19 @@ export class TxBuilder {
}

async validateInputs() {
for (const input of this.inputs) {
const transactionConfirmed = await this.source.isTransactionConfirmed(input.data.hash);
if (!transactionConfirmed) {
throw TxBuildError.withComment(
ErrorCodes.UNCONFIRMED_UTXO,
`hash: ${input.data.hash}, index: ${input.data.index}`,
);
}
}
await Promise.all(
this.inputs.map(async (input) => {
return limitPromiseBatchSize(async () => {
const transactionConfirmed = await this.source.isTransactionConfirmed(input.data.hash);
if (!transactionConfirmed) {
throw TxBuildError.withComment(
ErrorCodes.UNCONFIRMED_UTXO,
`hash: ${input.data.hash}, index: ${input.data.index}`,
);
}
});
}),
);
}

addOutput(output: InitOutput) {
Expand Down
13 changes: 13 additions & 0 deletions packages/btc/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import limitPromiseConcurrency from 'p-limit';
import { bitcoin, ecc, ECPair } from './bitcoin';
import { bytes } from '@ckb-lumos/codec';

Expand Down Expand Up @@ -75,3 +76,15 @@ export function transactionToHex(tx: bitcoin.Transaction, withWitness?: boolean)
const buffer: Buffer = tx['__toBuffer'](undefined, undefined, withWitness ?? false);
return buffer.toString('hex');
}

/**
* Limits the batch size of promises when querying with Promise.all().
* @example
* await Promise.all([
* limitPromiseBatchSize(() => asyncDoSomething()),
* limitPromiseBatchSize(() => asyncDoSomething()),
* limitPromiseBatchSize(() => asyncDoSomething()),
* ...
* ]);
*/
export const limitPromiseBatchSize = limitPromiseConcurrency(10);
5 changes: 3 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f0b99c7

Please sign in to comment.