Skip to content

Commit

Permalink
Adds fix to wait for txs to get to the mempool before mining
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy-then committed Dec 11, 2023
1 parent 243495b commit e286330
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 34 deletions.
30 changes: 14 additions & 16 deletions lib/2wp-utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const expect = require('chai').expect;
const { sendFromCow, mineAndSync, sendTxWithCheck, getUnlockedAddress } = require('./rsk-utils');
const { wait, retryWithCheck } = require('./utils');
const { sendFromCow, mineAndSync, sendTxWithCheck, getUnlockedAddress, waitForRskMempoolToGetTxs, waitAndUpdateBridge } = require('./rsk-utils');
const { retryWithCheck, waitForBtcTxToBeInMempool, waitForBtcMempoolToGetTxs } = require('./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 Expand Up @@ -62,7 +62,10 @@ const sendTxToBridge = async (rskTxHelper, amountInRbtc, rskFromAddress, mine =
if(!mine) {
return;
}
await wait(1000);

// Wait for the rsk tx to be in the rsk mempool before mining
await waitForRskMempoolToGetTxs(rskTxHelper);

await mineAndSync(getRskTransactionHelpers());
const result = await txPromise;
return result;
Expand Down Expand Up @@ -103,9 +106,13 @@ const isUtxoRegisteredInBridge = async (rskTxHelper, peginTxHash, expectedUxtosC

const mineForPeginRegistration = async (rskTxHelper, btcTxHelper) => {
// Enough confirmations to register the coinbase but not the pegin.
// Wait for the pegin to be in the bitcoin mempool before mining
await waitForBtcMempoolToGetTxs(btcTxHelper);
await btcTxHelper.mine(BTC_TO_RSK_MINIMUM_CONFIRMATIONS - 1);
await waitAndUpdateBridge(rskTxHelper, 500);
// One more confirmation to register the pegin.
// Wait for the pegin to be in the bitcoin mempool before mining
await waitForBtcMempoolToGetTxs(btcTxHelper);
await btcTxHelper.mine(1);
await waitAndUpdateBridge(rskTxHelper, 500);
};
Expand All @@ -132,6 +139,9 @@ const sendPegin = async (rskTxHelper, btcTxHelper, btcSenderAddressInformation,

const peginBtcTxHash = await btcTxHelper.transferBtc(btcSenderAddressInformation, recipientsTransferInformation, data);

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

await mineForPeginRegistration(rskTxHelper, btcTxHelper);

return peginBtcTxHash;
Expand Down Expand Up @@ -177,18 +187,6 @@ const ensurePeginIsRegistered = async (rskTxHelper, peginBtcTxHash, expectedUtxo

};

/**
* Waits for the specified time, updates the bridge and mines 1 rsk block
* @param {RskTransactionHelper} rskTxHelper
* @param {number} timeInMilliseconds defaults to 1000
* @returns {Promise<void>}
*/
const waitAndUpdateBridge = async (rskTxHelper, timeInMilliseconds = 1000) => {
await wait(timeInMilliseconds);
await rskTxHelper.updateBridge();
await mineAndSync(getRskTransactionHelpers());
};

/**
* Creates a pegin v1 data for a user to indicate to which rsk address to receive their pegin funds.
* @param {string} rskDestinationAddress
Expand Down Expand Up @@ -273,5 +271,5 @@ module.exports = {
createPeginV1TxData,
mineForPeginRegistration,
MIN_PEGOUT_VALUE_IN_RBTC,
disableWhitelisting
disableWhitelisting,
};
120 changes: 108 additions & 12 deletions lib/rsk-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { getBridge, getLatestActiveForkName } = require('./precompiled-abi-forks-
const hopBridgeTxParser = require('bridge-transaction-parser-hop400');
const fingerrootBridgeTxParser = require('bridge-transaction-parser-fingerroot500');
const { getRskTransactionHelpers } = require('../lib/rsk-tx-helper-provider');
const { removePrefix0x } = require('./utils');
const { removePrefix0x, waitForBtcMempoolToGetTxs } = require('./utils');

const BTC_TO_RSK_MINIMUM_ACCEPTABLE_CONFIRMATIONS = 3;
const RSK_TO_BTC_MINIMUM_ACCEPTABLE_CONFIRMATIONS = 3;
Expand Down Expand Up @@ -127,6 +127,98 @@ const increaseBlockToNextPegoutHeight = async (rskTransactionHelpers) => {
}
};

/**
* Waits for the specified time, updates the bridge and mines 1 rsk block
* @param {RskTransactionHelper} rskTxHelper
* @param {number} timeInMilliseconds defaults to 1000
* @returns {Promise<void>}
*/
const waitAndUpdateBridge = async (rskTxHelper, timeInMilliseconds = 1000) => {
await wait(timeInMilliseconds);
await rskTxHelper.updateBridge();

// Wait for the rsk `updateBridge` tx to be in the rsk mempool before mining
await waitForRskMempoolToGetTxs(rskTxHelper);

await mineAndSync(getRskTransactionHelpers());
};

/**
*
* @param {RskTransactionHelper} rskTxHelper
* @returns {Promise<string[]>} array of tx hashes in the mempool
*/
const getRskMempoolTransactionHashes = async (rskTxHelper) => {
const mempoolBlock = await rskTxHelper.getClient().eth.getBlock('pending');
return mempoolBlock.transactions;
};

/**
*
* @param {RskTransactionHelper} rskTxHelper
* @param {string} txHash
* @param {number} maxAttempts Defaults to 20
* @param {number} checkEveryMilliseconds Defaults to 1000 milliseconds
* @returns {boolean} whether the tx is in the mempool or not
*/
const waitForRskTxToBeInTheMempool = async (rskTxHelper, txHash, maxAttempts = 5, checkEveryMilliseconds = 1000) => {

const method = async () => {
const tx = await rskTxHelper.getClient().eth.getTransaction(txHash);
if(tx && !tx.blockNumber) {
console.debug(`The tx (${txHash}) is in the mempool`);
return true;
} else if(tx && tx.blockNumber) {
console.debug(`The tx (${txHash}) is already mined in a block`);
return true;
} else {
console.debug(`The tx (${txHash}) is not in the mempool nor in a block yet. Will keep retrying until it is in the mempool, block, or it reaches the max attempts to find it`);
return false;
}
};

const check = async (txIsInTheMempool, currentAttempts) => {
console.debug(`Attempting to find the tx ${txHash} in the mempool. Attempt ${currentAttempts} out of ${maxAttempts}`);
return txIsInTheMempool;
};

return await retryWithCheck(method, check, maxAttempts, checkEveryMilliseconds);

};

/**
*
* @param {RskTransactionHelper} rskTxHelper
* @param {number} maxAttempts Defaults to 20
* @param {number} checkEveryMilliseconds Defaults to 1000 milliseconds
* @returns
*/
const waitForRskMempoolToGetTxs = async (rskTxHelper, maxAttempts = 5, checkEveryMilliseconds = 1000) => {

const initialRskMempoolTxHashes = await getRskMempoolTransactionHashes(rskTxHelper);

console.debug(`[waitForRskMempoolToGetTxs] initial mempool size: ${initialRskMempoolTxHashes.length}`);

const method = async () => {
const mempoolTxHashes = await getRskMempoolTransactionHashes(rskTxHelper);
if(mempoolTxHashes.length > initialRskMempoolTxHashes.length) {
console.debug(`The mempool got new ${mempoolTxHashes.length - initialRskMempoolTxHashes.length} transactions`);
return true;
} else {
console.debug(`The mempool did not get new transactions yet. Will keep retrying until it gets new transactions or it reaches the max attempts`);
return false;
}
};

const check = async (mempoolHasTxs, currentAttempts) => {
console.debug(`Attempting to find new txs in the mempool. Attempt ${currentAttempts} out of ${maxAttempts}`);
return mempoolHasTxs;
};

return await retryWithCheck(method, check, maxAttempts, checkEveryMilliseconds);

};

/**
*
* @param {Array<RskTransactionHelper>} rskTransactionHelpers RskTransactionHelper instances each belonging to one federator node to make calls to the rsk network
Expand All @@ -141,12 +233,9 @@ const triggerRelease = async (rskTransactionHelpers, btcClient, callbacks = {})
await increaseBlockToNextPegoutHeight(rskTransactionHelpers);
}

// Sync all nodes
await waitForSync(rskTransactionHelpers);

// Adds the pegout to the pegoutsWaitingForConfirmations structure with this 1 confirmation
await rskTxHelper.updateBridge();
await mineAndSync(rskTransactionHelpers); // release_request_received and batch_pegout_created triggered here (if appropriate fork, RSKIP185 and RSKIP271, is/are active)
// release_request_received and batch_pegout_created triggered here (if appropriate fork, RSKIP185 and RSKIP271, is/are active)
await waitAndUpdateBridge(rskTxHelper);

if(callbacks.pegoutCreatedCallback) {
await callbacks.pegoutCreatedCallback(rskTxHelper);
Expand All @@ -156,8 +245,8 @@ const triggerRelease = async (rskTransactionHelpers, btcClient, callbacks = {})
await mineAndSync(rskTransactionHelpers, BTC_TO_RSK_MINIMUM_ACCEPTABLE_CONFIRMATIONS - 1);

// Moves the pegout from pegoutsWaitingForConfirmations to pegoutsWaitingForSignatures
await rskTxHelper.updateBridge(); // Makes an updateCollections call which is the tx that will move the pegout to pegoutsWaitingForSignatures
await mineAndSync(rskTransactionHelpers); // pegout_confirmed event triggered here (if appropriate fork, RSKIP326, is active)
// pegout_confirmed event triggered here (if appropriate fork, RSKIP326, is active)
await waitAndUpdateBridge(rskTxHelper);

if(callbacks.pegoutConfirmedCallback) {
await callbacks.pegoutConfirmedCallback(rskTxHelper);
Expand Down Expand Up @@ -192,13 +281,16 @@ const triggerRelease = async (rskTransactionHelpers, btcClient, callbacks = {})
await callbacks.releaseBtcCallback(rskTxHelper);
}

// Waiting to make sure that the pegout tx is in the bitcoin mempool before mining the required blocks for confirmation.
await waitForBtcMempoolToGetTxs(btcClient);

// From the btc network side, mine `RSK_TO_BTC_MINIMUM_ACCEPTABLE_CONFIRMATIONS + 1` blocks, 1 for the pegout funds to be mined and reflected in the recipient address,
// and `RSK_TO_BTC_MINIMUM_ACCEPTABLE_CONFIRMATIONS` more to have enough confirmation for the change balance to be reflected back in the bridge (like a pegin)
await btcClient.mine(RSK_TO_BTC_MINIMUM_ACCEPTABLE_CONFIRMATIONS + 1);

// Make pegnatories register the change utxo back in the bridge
await rskTxHelper.updateBridge();
await mineAndSync(rskTransactionHelpers); // At this point the bridge should already have the change uxto registered
// After this point the bridge should already have the change uxto registered
await waitAndUpdateBridge(rskTxHelper);

};

Expand All @@ -221,7 +313,7 @@ const sendTxWithCheck = async (rskTxHelper, method, from, checkCallback) => {
const estimatedGas = await method.estimateGas({ from });
const txReceiptPromise = method.send({ from, value: 0, gasPrice: 0, gas: estimatedGas });

await wait(1000);
await waitForRskMempoolToGetTxs(rskTxHelper);
await mineAndSync(getRskTransactionHelpers());

return await txReceiptPromise;
Expand Down Expand Up @@ -344,5 +436,9 @@ module.exports = {
getUnlockedAddress,
getFedsPubKeys,
activateFork,
getLatestForkName
getLatestForkName,
getRskMempoolTransactionHashes,
waitForRskTxToBeInTheMempool,
waitForRskMempoolToGetTxs,
waitAndUpdateBridge,
};
4 changes: 2 additions & 2 deletions lib/tests/2wp.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { getRskTransactionHelpers, getRskTransactionHelper } = require('../rsk-tx
const { getDerivedRSKAddressInformation } = require('@rsksmart/btc-rsk-derivation');
const btcEthUnitConverter = require('@rsksmart/btc-eth-unit-converter');
const { sendTxToBridge, sendPegin, ensurePeginIsRegistered, donateToBridge } = require('../2wp-utils');
const { waitAndUpdateBridge } = require('../rsk-utils');

const DONATION_AMOUNT = 250;
const REJECTED_REASON = 1;
Expand Down Expand Up @@ -38,8 +39,7 @@ const execute = (description, getRskHost) => {
rskTxHelpers = getRskTransactionHelpers();

// Update the bridge to sync btc blockchains
await rskTxHelper.updateBridge();
await rskUtils.mineAndSync(rskTxHelpers);
await waitAndUpdateBridge(rskTxHelper);

// At the moment there are a lot of pegout tests that depend on the bridge to have enough balance.
// Those tests are not doing a pegin if needed, so we need to donate to the bridge to ensure it has enough balance.
Expand Down
88 changes: 88 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ const fundAddressAndGetData = async (btcTxHelper, addressToFund, amountToFundInB

const txId = await btcTxHelper.transferBtc(btcSenderAddressInformation, recipientsTransactionInformation);

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

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

await btcTxHelper.importAddress(addressToFund);
Expand Down Expand Up @@ -212,6 +215,88 @@ const retryWithCheck = async (method, check, maxAttempts = 5, delayInMillisecond
return result;
};

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

/**
*
* @param {BtcTransactionHelper} btcTxHelper
* @param {string} btcTxHash
*/
const waitForBtcTxToBeInMempool = async (btcTxHelper, btcTxHash) => {

const bitcoinMempoolHasTx = async () => {
const bitcoinMempool = await getBitcoinMempool(btcTxHelper);
const txIsInMempool = bitcoinMempool.includes(btcTxHash);
if(!txIsInMempool) {
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;
}
}
};

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;
};

await retryWithCheck(bitcoinMempoolHasTx, checkBitcoinMempoolHasTx, 5, 1000, onError);
};

/**
* Waits until the btc mempool has at least one tx.
* @param {BtcTransactionHelper} btcTxHelper
* @returns {boolean}
*/
const waitForBtcMempoolToGetTxs = async (btcTxHelper) => {

const initialBitcoinMempoolSize = (await getBitcoinMempool(btcTxHelper)).length;

console.debug(`The initial btc mempool size is ${initialBitcoinMempoolSize}.`);

const getBitcoinMempoolSize = async () => {
const bitcoinMempool = await getBitcoinMempool(btcTxHelper);
const bitcoinMempoolSize = bitcoinMempool.length;
console.debug(`The btc mempool has ${bitcoinMempoolSize} txs.`);
return bitcoinMempoolSize;
};

const checkBtcMempoolIsNotEmpty = async (bitcoinMempoolSize, currentAttempts) => {
console.debug(`The btc mempool has ${bitcoinMempoolSize} txs at attempt ${currentAttempts}.`);
return bitcoinMempoolSize > 0;
};

const onError = async (e) => {
console.error(`Un expected error while trying to get the btc mempool.`, e);
throw e;
};

return await retryWithCheck(getBitcoinMempoolSize, checkBtcMempoolIsNotEmpty, 5, 1000, onError);
}

module.exports = {
sequentialPromise: sequentialPromise,
mapPromiseAll: mapPromiseAll,
Expand All @@ -233,4 +318,7 @@ module.exports = {
},
removePrefix0x,
retryWithCheck,
getBitcoinMempool,
waitForBtcTxToBeInMempool,
waitForBtcMempoolToGetTxs,
}
8 changes: 4 additions & 4 deletions tests/01_03_01-lock_whitelist_pre_papyrus.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const { sendPegin, ensurePeginIsRegistered } = require('../lib/2wp-utils');
const { getDerivedRSKAddressInformation } = require('@rsksmart/btc-rsk-derivation');
const { btcToWeis, btcToSatoshis, satoshisToBtc } = require('@rsksmart/btc-eth-unit-converter');
const { getBridge, getLatestActiveForkName } = require('../lib/precompiled-abi-forks-util');
const { waitAndUpdateBridge } = require('../lib/rsk-utils');

let rskTxHelpers;
let btcTxHelper;
Expand Down Expand Up @@ -164,8 +165,7 @@ describe('Lock whitelisting', () => {
const federationBalanceAfterPegin = await btcTxHelper.getAddressBalance(federationAddress);
expect(Number(btcToSatoshis(federationBalanceAfterPegin))).to.equal(Number(btcToSatoshis(initialFederationBalance + AMOUNT_TO_TRY_TO_LOCK)), `Lock BTC federation ${federationAddress} credit`);

await rskTxHelper.updateBridge();
await rskUtils.mineAndSync(rskTxHelpers);
await waitAndUpdateBridge(rskTxHelper);

const initialBlockNumber = await rskTxHelper.getBlockNumber();
await rskUtils.mineAndSync(rskTxHelpers);
Expand Down Expand Up @@ -231,8 +231,8 @@ describe('Lock whitelisting', () => {
const federationBalanceAfterPegin = await btcTxHelper.getAddressBalance(federationAddress);
expect(federationBalanceAfterPegin).to.equal(initialFederationBalance + PEGIN_VALUE_IN_BTC, 'The federation address should have its balance increased by the pegin amount');

await rskTxHelper.updateBridge();
await rskUtils.mineAndSync(rskTxHelpers);
// Update the bridge to sync
await waitAndUpdateBridge(rskTxHelper);

const recipientRskAddressBalance = Number(await rskTxHelper.getBalance(recipientRskAddressInfo.address));
expect(recipientRskAddressBalance).to.equal(0, 'The recipient rsk address should not have any balance');
Expand Down
Loading

0 comments on commit e286330

Please sign in to comment.