Skip to content

Commit

Permalink
Merge pull request #28 from rsksmart/bitcoin-utils
Browse files Browse the repository at this point in the history
Move bitcoin util functions to their own file
  • Loading branch information
jeremy-then authored Dec 21, 2023
2 parents 7d1f0f8 + daa9da0 commit ebb0be0
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 225 deletions.
12 changes: 10 additions & 2 deletions lib/2wp-utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
const expect = require('chai').expect;
const { sendFromCow, mineAndSync, sendTxWithCheck, getUnlockedAddress, waitForRskMempoolToGetNewTxs, waitAndUpdateBridge } = require('./rsk-utils');
const { retryWithCheck, waitForBitcoinTxToBeInMempool, waitForBitcoinMempoolToGetTxs } = require('./utils');
const {
sendFromCow,
mineAndSync,
sendTxWithCheck,
getUnlockedAddress,
waitForRskMempoolToGetNewTxs,
waitAndUpdateBridge
} = require('./rsk-utils');
const { retryWithCheck } = require('./utils');
const { waitForBitcoinTxToBeInMempool, waitForBitcoinMempoolToGetTxs } = require('./btc-utils');
const { getBridge, getLatestActiveForkName } = require('./precompiled-abi-forks-util');
const { getBridgeState } = require('@rsksmart/bridge-state-data-parser');
const btcEthUnitConverter = require('@rsksmart/btc-eth-unit-converter');
Expand Down
187 changes: 187 additions & 0 deletions lib/btc-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
const bitcoinJs = require('bitcoinjs-lib');
const merkleLib = require('merkle-lib');
const pmtBuilder = require('@rsksmart/pmt-builder');
const { retryWithCheck } = require('./utils');

const publicKeyToCompressed = (publicKey) => {
return bitcoinJs.ECPair.fromPublicKey(Buffer.from(publicKey, 'hex'), { compressed: true })
.publicKey
.toString('hex');
}

const fundAddressAndGetData = async (btcTxHelper, addressToFund, amountToFundInBtc, amountForFunderInBtc, btcTypeOfAddress = 'legacy') => {
const btcSenderAddressInformation = await btcTxHelper.generateBtcAddress(btcTypeOfAddress);
await btcTxHelper.fundAddress(btcSenderAddressInformation.address, amountForFunderInBtc);

const recipientsTransactionInformation = [{
recipientAddress: addressToFund,
amountInBtc: amountToFundInBtc
}];
const txId = await btcTxHelper.transferBtc(btcSenderAddressInformation, recipientsTransactionInformation);

// Wait for the pegin to be in the bitcoin mempool before mining
await waitForBitcoinTxToBeInMempool(btcTxHelper, txId);

const rawTx = await btcTxHelper.nodeClient.getRawTransaction(txId);

await btcTxHelper.importAddress(addressToFund);

const blockHash = await btcTxHelper.mine();
const blockWithFundingTx = await btcTxHelper.nodeClient.getBlock(blockHash[0], true);
const blockHeight = blockWithFundingTx.height;
const fundingBtcTx = await btcTxHelper.getTransaction(txId);

// if it's a segwit transaction this will add into the return data the info needed to register the coinbase
if (btcTypeOfAddress === 'p2sh-segwit'){
const txs = [];
for (const tx of blockWithFundingTx.tx) {
txs.push(await btcTxHelper.getTransaction(tx));
}

const coinbaseTx = txs[0];
const blockHash = blockWithFundingTx.hash;
const witnessReservedValue = coinbaseTx.ins[0].witness[0].toString('hex');
const coinbaseTxWithoutWitness = bitcoinJs.Transaction.fromBuffer(coinbaseTx.__toBuffer(undefined, undefined, false));
const coinbaseTxHashWithoutWitness = coinbaseTxWithoutWitness.getId();

// Create PMT for coinbase
const coinbasePmt = pmtBuilder.buildPMT(blockWithFundingTx.tx, coinbaseTxHashWithoutWitness);

// Calculate witnessRoot
const hashesWithWitness = txs.map(x => Buffer.from(x.getHash(true)));
const witnessMerkleTree = merkleLib(hashesWithWitness, bitcoinJs.crypto.hash256);

// Get witness merkleRoot from witnessMerkleTree. This is equals to the last element in witnessMerkleTree array
const witnessMerkleRoot = witnessMerkleTree[witnessMerkleTree.length-1].reverse();
const reversedHashesWithWitness = txs.map(x => Buffer.from(x.getHash(true)).reverse().toString('hex'));
const btcTxPmt = pmtBuilder.buildPMT(reversedHashesWithWitness, fundingBtcTx.getHash(true).reverse().toString('hex'));

return {
rawTx: rawTx,
pmt: btcTxPmt.hex,
height: blockHeight,
coinbaseParams: {
coinbaseTxWithoutWitness: coinbaseTxWithoutWitness,
blockHash: blockHash,
pmt: coinbasePmt,
witnessMerkleRoot: witnessMerkleRoot,
witnessReservedValue: witnessReservedValue
}
};
} else {
const pmt = pmtBuilder.buildPMT(blockWithFundingTx.tx, txId);
return {
rawTx: rawTx,
pmt: pmt.hex,
height: blockHeight
};
}
};

/**
*
* @param {BtcTransactionHelper} btcTxHelper
* @returns {Promise<Array<string>>} the mempool tx ids
*/
const getBitcoinTransactionsInMempool = async (btcTxHelper) => {
return await btcTxHelper.nodeClient.execute('getrawmempool', []);
};

/**
*
* @param {BtcTransactionHelper} btcTxHelper
* @param {string} btcTxHash
* @param {number} maxAttempts defaults to 3
* @param {number} checkEveryMilliseconds defaults to 500 milliseconds
* @returns {Promise<boolean>} whether the tx got to the mempool or not after the attempts
*/
const waitForBitcoinTxToBeInMempool = async (btcTxHelper, btcTxHash, maxAttempts = 3, checkEveryMilliseconds = 500) => {

const bitcoinMempoolHasTx = async () => {
const bitcoinMempool = await getBitcoinTransactionsInMempool(btcTxHelper);
const isTxInMempool = bitcoinMempool.includes(btcTxHash);
if(!isTxInMempool) {
console.debug(`Attempting to check if the btc tx (${btcTxHash}) was already mined since it's not in the mempool yet.`);
const tx = await btcTransactionHelper.getTransaction(btcTxHash);
if(tx) {
console.debug(`The btc tx (${btcTxHash}) was already mined.`);
return true;
}
return false;
}
return true;
};

const checkBitcoinMempoolHasTx = async (btcTxAlreadyFound, currentAttempts) => {
if(btcTxAlreadyFound) {
console.debug(`The btc tx ${btcTxHash} was found in the mempool at attempt ${currentAttempts}.`);
} else {
console.log(`Attempting to get the btc tx ${btcTxHash} in the mempool. Attempt: ${currentAttempts}.`);
}
return btcTxAlreadyFound;
};

const onError = async (e) => {
if(e.message.includes('No such mempool or blockchain transaction')) {
console.debug(`The btc tx ${btcTxHash} is not in the mempool nor mined yet. Let's allow some more time before retrying to get it.`);
return true;
}
console.error(`Un expected error while trying to get the btc tx ${btcTxHash} in the mempool.`, e);
throw e;
};

const { result: btcTxAlreadyFoundInMempool } = retryWithCheck(
bitcoinMempoolHasTx,
checkBitcoinMempoolHasTx,
maxAttempts,
checkEveryMilliseconds,
onError
);

return btcTxAlreadyFoundInMempool;
};

/**
* Waits until the bitcoin mempool has at least one tx.
* @param {BtcTransactionHelper} btcTxHelper
* @param {number} maxAttempts defaults to 3
* @param {number} checkEveryMilliseconds defaults to 500 milliseconds
* @returns {Promise<boolean>}
*/
const waitForBitcoinMempoolToGetTxs = async (btcTxHelper, maxAttempts = 3, checkEveryMilliseconds = 500) => {
const initialBitcoinMempoolSize = (await getBitcoinTransactionsInMempool(btcTxHelper)).length;
console.debug(`[waitForBitcoinMempoolToGetTxs] The initial bitcoin mempool size is ${initialBitcoinMempoolSize}.`);
console.debug(`Will wait and attempt to check if the bitcoin mempool has received any new transactions ${maxAttempts} times.`);

const getCountOfTransactionsInMempool = async () => {
const bitcoinMempool = await getBitcoinTransactionsInMempool(btcTxHelper);
const bitcoinMempoolSize = bitcoinMempool.length;
return bitcoinMempoolSize;
};

const checkBtcMempoolIsNotEmpty = async (bitcoinMempoolSize) => {
return bitcoinMempoolSize > 0;
};

const { result: bitcoinMempoolHasTx, attempts } = await retryWithCheck(
getCountOfTransactionsInMempool,
checkBtcMempoolIsNotEmpty,
maxAttempts,
checkEveryMilliseconds
);

const txsInMempool = await getBitcoinTransactionsInMempool(btcTxHelper);
const finalBitcoinMempoolSize = txsInMempool.length;

console.debug(`[waitForBitcoinMempoolToGetTxs] The final bitcoin mempool size is ${finalBitcoinMempoolSize}, after ${attempts} attempts. Difference with initial mempool size: ${finalBitcoinMempoolSize - initialBitcoinMempoolSize}.`);

return bitcoinMempoolHasTx;
}

module.exports = {
publicKeyToCompressed,
fundAddressAndGetData,
getBitcoinTransactionsInMempool,
waitForBitcoinTxToBeInMempool,
waitForBitcoinMempoolToGetTxs
}
10 changes: 5 additions & 5 deletions lib/liquidity-bridge-contract.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const fs = require('fs');
const { getRskTransactionHelper } = require('../lib/rsk-tx-helper-provider');

const solUtils = require('../lib/sol-utils');
const rskUtils = require('../lib/rsk-utils');
const btcEthUnitConverter = require('@rsksmart/btc-eth-unit-converter');
const { compileAndDeploy } = require('./sol-utils');
const { sendFromCow } = require('./rsk-utils');
const { btcToWeis } = require('@rsksmart/btc-eth-unit-converter');

const INITIAL_RSK_BALANCE_IN_BTC = 1;
const BRIDGE_ADDRESS = '0x0000000000000000000000000000000001000006';
Expand All @@ -16,12 +16,12 @@ let contractInstance;
const deployLiquidityBridgeContract = async (host = null) => {
const rskTransactionHelper = getRskTransactionHelper(host);
const fromAddress = await rskTransactionHelper.newAccountWithSeed('');
await rskUtils.sendFromCow(rskTransactionHelper, fromAddress, Number(btcEthUnitConverter.btcToWeis(INITIAL_RSK_BALANCE_IN_BTC)));
await sendFromCow(rskTransactionHelper, fromAddress, Number(btcToWeis(INITIAL_RSK_BALANCE_IN_BTC)));
await rskTransactionHelper.unlockAccount(fromAddress, '');

try {
const source = fs.readFileSync(LIQUIDITY_BRIDGE_CONTRACT_FILE).toString();
const liquidityBridgeContract = await solUtils.compileAndDeploy(
const liquidityBridgeContract = await compileAndDeploy(
SOLIDITY_COMPILER_VERSION,
source,
LIQUIDITY_BRIDGE_CONTRACT_NAME,
Expand Down
4 changes: 2 additions & 2 deletions lib/rsk-utils.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const { wait, retryWithCheck } = require('./utils');
const expect = require('chai').expect;
const { getBridgeState } = require('@rsksmart/bridge-state-data-parser');
const { getBridge, getLatestActiveForkName } = require('./precompiled-abi-forks-util');
const hopBridgeTxParser = require('bridge-transaction-parser-hop400');
const fingerrootBridgeTxParser = require('bridge-transaction-parser-fingerroot500');
const { getRskTransactionHelpers } = require('../lib/rsk-tx-helper-provider');
const { removePrefix0x, waitForBitcoinMempoolToGetTxs } = require('./utils');
const { wait, retryWithCheck, removePrefix0x } = require('./utils');
const { waitForBitcoinMempoolToGetTxs } = require('./btc-utils');

const BTC_TO_RSK_MINIMUM_ACCEPTABLE_CONFIRMATIONS = 3;
const RSK_TO_BTC_MINIMUM_ACCEPTABLE_CONFIRMATIONS = 3;
Expand Down
4 changes: 2 additions & 2 deletions lib/sol-utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
let solc = require('solc');
const { wait } = require('./utils');
const rskUtils = require('../lib/rsk-utils');
const { mineAndSync } = require('./rsk-utils');
const { getRskTransactionHelpers } = require('../lib/rsk-tx-helper-provider');

const promisefy = function(f, args) {
Expand All @@ -24,7 +24,7 @@ const compileAndDeploy = async(compilerVersion, source, name, constructorArgumen
// Default options
options = Object.assign({
mine: async () => {
return await rskUtils.mineAndSync(getRskTransactionHelpers());
return await mineAndSync(getRskTransactionHelpers());
},
gas: 'estimate',
}, options);
Expand Down
Loading

0 comments on commit ebb0be0

Please sign in to comment.