Skip to content

Commit

Permalink
Merge branch '90-integrate-bitcoin-box-selection' into 'dev'
Browse files Browse the repository at this point in the history
Resolve "integrate bitcoin-box-selection"

Closes #90

See merge request ergo/rosen-bridge/rosen-chains!98
  • Loading branch information
zargarzadehm committed Mar 3, 2024
2 parents 5a10c48 + 5637453 commit bef89b0
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .changeset/big-pumpkins-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
14 changes: 13 additions & 1 deletion package-lock.json

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

67 changes: 58 additions & 9 deletions packages/chains/bitcoin/lib/BitcoinChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { AbstractLogger } from '@rosen-bridge/abstract-logger';
import { Fee } from '@rosen-bridge/minimum-fee';
import {
AbstractUtxoChain,
AssetBalance,
BoxInfo,
ChainUtils,
CoveringBoxes,
EventTrigger,
FailedError,
GET_BOX_API_LIMIT,
NetworkError,
NotEnoughAssetsError,
NotEnoughValidBoxesError,
Expand All @@ -27,6 +30,7 @@ import JsonBigInt from '@rosen-bridge/json-bigint';
import { estimateTxFee, getPsbtTxInputBoxId } from './bitcoinUtils';
import { BITCOIN_CHAIN, SEGWIT_INPUT_WEIGHT_UNIT } from './constants';
import { blake2b } from 'blakejs';
import { selectBitcoinUtxos } from '@rosen-bridge/bitcoin-utxo-selection';

class BitcoinChain extends AbstractUtxoChain<BitcoinUtxo> {
declare network: AbstractBitcoinNetwork;
Expand Down Expand Up @@ -68,17 +72,18 @@ class BitcoinChain extends AbstractUtxoChain<BitcoinUtxo> {
txType: TransactionType,
order: PaymentOrder,
unsignedTransactions: PaymentTransaction[],
serializedSignedTransactions: string[],
...extra: Array<any>
serializedSignedTransactions: string[]
): Promise<BitcoinTransaction> => {
this.logger.debug(
`Generating Bitcoin transaction for Order: ${JsonBigInt.stringify(order)}`
);
const feeRatio = await this.network.getFeeRatio();

// calculate required assets
const requiredAssets = order
.map((order) => order.assets)
.reduce(ChainUtils.sumAssetBalance, {
nativeToken: await this.minimumMeaningfulSatoshi(),
nativeToken: this.minimumMeaningfulSatoshi(feeRatio),
tokens: [],
});
this.logger.debug(
Expand Down Expand Up @@ -107,8 +112,7 @@ class BitcoinChain extends AbstractUtxoChain<BitcoinUtxo> {
this.configs.addresses.lock
);

// TODO: improve box fetching (use bitcoin-box-selection package)
// local:ergo/rosen-bridge/rosen-chains#90
// fetch input boxes
const coveredBoxes = await this.getCoveringBoxes(
this.configs.addresses.lock,
requiredAssets,
Expand Down Expand Up @@ -165,7 +169,7 @@ class BitcoinChain extends AbstractUtxoChain<BitcoinUtxo> {
const estimatedFee = estimateTxFee(
psbt.txInputs.length,
psbt.txOutputs.length + 1,
await this.network.getFeeRatio()
feeRatio
);
this.logger.debug(`Estimated Fee: ${estimatedFee}`);
remainingBtc -= estimatedFee;
Expand Down Expand Up @@ -708,14 +712,59 @@ class BitcoinChain extends AbstractUtxoChain<BitcoinUtxo> {
* additional fee for adding it to a tx
* @returns the minimum amount
*/
minimumMeaningfulSatoshi = async (): Promise<bigint> => {
const currentFeeRatio = await this.network.getFeeRatio();
minimumMeaningfulSatoshi = (feeRatio: number): bigint => {
return BigInt(
Math.ceil(
(currentFeeRatio * SEGWIT_INPUT_WEIGHT_UNIT) / 4 // estimate fee per weight and convert to virtual size
(feeRatio * SEGWIT_INPUT_WEIGHT_UNIT) / 4 // estimate fee per weight and convert to virtual size
)
);
};

/**
* gets useful, allowable and last boxes for an address until required assets are satisfied
* @param address the address
* @param requiredAssets the required assets
* @param forbiddenBoxIds the id of forbidden boxes
* @param trackMap the mapping of a box id to it's next box
* @returns an object containing the selected boxes with a boolean showing if requirements covered or not
*/
getCoveringBoxes = async (
address: string,
requiredAssets: AssetBalance,
forbiddenBoxIds: Array<string>,
trackMap: Map<string, BitcoinUtxo | undefined>
): Promise<CoveringBoxes<BitcoinUtxo>> => {
const getAddressBoxes = this.network.getAddressBoxes;
async function* generator() {
let offset = 0;
const limit = GET_BOX_API_LIMIT;
while (true) {
const page = await getAddressBoxes(address, offset, limit);
if (page.length === 0) break;
yield* page;
offset += limit;
}
return undefined;
}
const utxoIterator = generator();

// estimate tx weight without considering inputs
// 0 inputs, 2 outputs, 1 for feeRatio to get weights only, multiply by 4 to convert vSize to weight unit
const estimatedTxWeight = Number(estimateTxFee(0, 2, 1)) * 4;

const feeRatio = await this.network.getFeeRatio();
return selectBitcoinUtxos(
requiredAssets.nativeToken,
forbiddenBoxIds,
trackMap,
utxoIterator,
this.minimumMeaningfulSatoshi(feeRatio),
SEGWIT_INPUT_WEIGHT_UNIT,
estimatedTxWeight,
feeRatio,
this.logger
);
};
}

export default BitcoinChain;
1 change: 1 addition & 0 deletions packages/chains/bitcoin/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export const BITCOIN_CHAIN = 'bitcoin';

export const SEGWIT_INPUT_WEIGHT_UNIT = 272;
export const SEGWIT_OUTPUT_WEIGHT_UNIT = 124;
export const CONFIRMATION_TARGET = 6;
22 changes: 21 additions & 1 deletion packages/chains/bitcoin/lib/network/AbstractBitcoinNetwork.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { AbstractUtxoChainNetwork } from '@rosen-chains/abstract-chain';
import {
AbstractUtxoChainNetwork,
TokenDetail,
} from '@rosen-chains/abstract-chain';
import { Psbt } from 'bitcoinjs-lib';
import { BitcoinTx, BitcoinUtxo } from '../types';
import { BitcoinRosenExtractor } from '@rosen-bridge/rosen-extractor';
Expand Down Expand Up @@ -33,6 +36,23 @@ abstract class AbstractBitcoinNetwork extends AbstractUtxoChainNetwork<
* @returns
*/
abstract getMempoolTxIds: () => Promise<Array<string>>;

/**
* gets all transactions in mempool (returns empty list if the chain has no mempool)
* Note: due to heavy size of transactions in mempool, we ignore getting mempool txs in Bitcoin
* @returns empty list
*/
getMempoolTransactions = async (): Promise<Array<BitcoinTx>> => {
return [];
};

/**
* gets token details (name, decimals)
* @param tokenId
*/
getTokenDetail = async (tokenId: string): Promise<TokenDetail> => {
throw Error(`Bitcoin does not support token`);
};
}

export default AbstractBitcoinNetwork;
3 changes: 2 additions & 1 deletion packages/chains/bitcoin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rosen-chains/bitcoin",
"version": "0.0.0",
"version": "0.1.0",
"description": "this project contains bitcoin chain for Rosen-bridge",
"repository": "https://github.com/rosen-bridge/rosen-chains",
"license": "GPL-3.0",
Expand Down Expand Up @@ -34,6 +34,7 @@
},
"dependencies": {
"@rosen-bridge/abstract-logger": "^1.0.0",
"@rosen-bridge/bitcoin-utxo-selection": "^0.2.0",
"@rosen-bridge/json-bigint": "^0.1.0",
"@rosen-bridge/minimum-fee": "^0.1.13",
"@rosen-bridge/rosen-extractor": "^3.4.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/chains/bitcoin/tests/BitcoinChain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ describe('BitcoinChain', () => {
'targetChainTokenId',
'toAddress',
'fromAddress',
])('should return false when event %p is wrong', async (key: string) => {
])('should return false when event %s is wrong', async (key: string) => {
// mock an event
const event = testData.validEvent;

Expand Down

0 comments on commit bef89b0

Please sign in to comment.