Skip to content

Commit

Permalink
Adds 'should create multiple pegouts with mixed values same and above…
Browse files Browse the repository at this point in the history
… minimum' test
  • Loading branch information
jeremy-then committed Oct 31, 2024
1 parent 4f507e4 commit 2ac0319
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 25 deletions.
7 changes: 5 additions & 2 deletions lib/2wp-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,19 @@ const assertRefundUtxosSameAsPeginUtxos = async(rskTxHelper, btcTxHelper, peginT
* @param {BN} amountInWeisBN
* @param {string} rskFromAddress
* @param {boolean} mine If true, mines 1 block after sending the transaction. If false, it will not mine the tx and will return undefined. Defaults to true.
* @returns {web3.eth.TransactionReceipt | undefined}
* @returns {Promise<web3.eth.TransactionReceipt | txPromise>} the rsk tx receipt if `mine` is true, otherwise the tx promise.
*/
const sendTxToBridge = async (rskTxHelper, amountInWeisBN, rskFromAddress, mine = true) => {

const txPromise = rskTxHelper.sendTransaction({
from: rskFromAddress,
to: BRIDGE_ADDRESS,
value: amountInWeisBN,
gasPrice: TO_BRIDGE_GAS_PRICE,
});

if(!mine) {
return;
return txPromise;
}

// Wait for the rsk tx to be in the rsk mempool before mining
Expand All @@ -78,6 +80,7 @@ const sendTxToBridge = async (rskTxHelper, amountInWeisBN, rskFromAddress, mine
await mineAndSync(getRskTransactionHelpers());
const result = await txPromise;
return result;

};

/**
Expand Down
25 changes: 25 additions & 0 deletions lib/rsk-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,30 @@ const setFeePerKb = async (rskTxHelper, feePerKbInSatoshis) => {

};

/**
* Waits for the rsk mempool to get `atLeastExpectedCount` txs. Attempting `maxAttempts` times, checking every `waitTimeInMilliseconds`.
* @param {RskTransactionHelper} rskTxHelper
* @param {number} atLeastExpectedCount
* @param {number} waitTimeInMilliseconds defaults to 500 milliseconds
* @param {number} maxAttempts defaults to 20 attempts
* @returns {Promise<void>}
* @throws {Error} if the rsk mempool doesn't get at least `atLeastExpectedCount` txs after `maxAttempts` attempts
*/
const waitForRskMempoolToGetAtLeastThisManyTxs = async (rskTxHelper, atLeastExpectedCount, waitTimeInMilliseconds = 500, maxAttempts = 20) => {
let attempts = 1;
while(attempts <= maxAttempts) {
const rskMempoolTxs = await getRskMempoolTransactionHashes(rskTxHelper);
logger.debug(`[${waitForRskMempoolToGetAtLeastThisManyTxs.name}] rsk mempool txs: ${rskMempoolTxs.length}`);
if(rskMempoolTxs.length >= atLeastExpectedCount) {
logger.debug(`[${waitForRskMempoolToGetAtLeastThisManyTxs.name}] rsk mempool has ${rskMempoolTxs.length} (at least expected was ${atLeastExpectedCount}) txs after ${attempts} attempts`);
return;
}
await wait(waitTimeInMilliseconds);
attempts++;
}
throw new Error(`[${waitForRskMempoolToGetAtLeastThisManyTxs.name}] rsk mempool didn't get at least ${atLeastExpectedCount} txs after ${maxAttempts} attempts`);
};

module.exports = {
mineAndSync,
waitForBlock,
Expand All @@ -548,4 +572,5 @@ module.exports = {
sendTransaction,
getPegoutEventsInBlockRange,
setFeePerKb,
waitForRskMempoolToGetAtLeastThisManyTxs,
};
210 changes: 187 additions & 23 deletions lib/tests/2wp.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ const { getBridge } = require('../precompiled-abi-forks-util');
const { getBtcClient } = require('../btc-client-provider');
const { getRskTransactionHelper, getRskTransactionHelpers } = require('../rsk-tx-helper-provider');
const { satoshisToBtc, btcToSatoshis, satoshisToWeis } = require('@rsksmart/btc-eth-unit-converter');
const { findEventInBlock, triggerRelease, getPegoutEventsInBlockRange, setFeePerKb, sendTransaction } = require('../rsk-utils');
const {
findEventInBlock,
triggerRelease,
getPegoutEventsInBlockRange,
setFeePerKb,
sendTransaction,
getRskMempoolTransactionHashes,
waitForRskMempoolToGetAtLeastThisManyTxs,
} = require('../rsk-utils');
const BridgeTransactionParser = require('@rsksmart/bridge-transaction-parser');
const {
PEGIN_REJECTION_REASONS,
Expand All @@ -28,7 +36,7 @@ const { sendPegin,
createExpectedReleaseRequestRejectedEvent,
} = require('../2wp-utils');
const { getBtcAddressBalanceInSatoshis } = require('../btc-utils');
const { ensure0x, removePrefix0x } = require('../utils');
const { ensure0x, removePrefix0x, wait } = require('../utils');
const { getBridgeState } = require('@rsksmart/bridge-state-data-parser');
const bitcoinJsLib = require('bitcoinjs-lib');
const { deployCallReleaseBtcContract } = require('../contractDeployer');
Expand Down Expand Up @@ -902,6 +910,130 @@ const execute = (description, getRskHost) => {

});

it('should create multiple pegouts with mixed values same and above minimum', async () => {

// Arrange

const senderRecipientInfo1 = await createSenderRecipientInfo(rskTxHelper, btcTxHelper);
await fundRskAccountThroughAPegin(rskTxHelper, btcTxHelper, senderRecipientInfo1.btcSenderAddressInfo);

const senderRecipientInfo2 = await createSenderRecipientInfo(rskTxHelper, btcTxHelper);
await fundRskAccountThroughAPegin(rskTxHelper, btcTxHelper, senderRecipientInfo2.btcSenderAddressInfo);

const senderRecipientInfo3 = await createSenderRecipientInfo(rskTxHelper, btcTxHelper);
await fundRskAccountThroughAPegin(rskTxHelper, btcTxHelper, senderRecipientInfo3.btcSenderAddressInfo);

const initial2wpBalances = await get2wpBalances(rskTxHelper, btcTxHelper);

const initialSender1AddressBalanceInSatoshis = await getBtcAddressBalanceInSatoshis(btcTxHelper, senderRecipientInfo1.btcSenderAddressInfo.address);
const initialSender2AddressBalanceInSatoshis = await getBtcAddressBalanceInSatoshis(btcTxHelper, senderRecipientInfo2.btcSenderAddressInfo.address);
const initialSender3AddressBalanceInSatoshis = await getBtcAddressBalanceInSatoshis(btcTxHelper, senderRecipientInfo3.btcSenderAddressInfo.address);

const pegout1ValueInSatoshis = MINIMUM_PEGOUT_AMOUNT_IN_SATOSHIS;
const pegout2ValueInSatoshis = MINIMUM_PEGOUT_AMOUNT_IN_SATOSHIS + 100_000;
const pegout3ValueInSatoshis = MINIMUM_PEGOUT_AMOUNT_IN_SATOSHIS + 150_000;
const totalPegoutValueInSatoshis = pegout1ValueInSatoshis + pegout2ValueInSatoshis + pegout3ValueInSatoshis;

const rskMemPoolTxHashes1 = await getRskMempoolTransactionHashes(rskTxHelper);
const initialRskMempoolTxHashesSize = rskMemPoolTxHashes1.length;

// Act

const shouldMine = false;
const pegoutTransaction1Promise = sendTxToBridge(rskTxHelper, new BN(satoshisToWeis(pegout1ValueInSatoshis)), senderRecipientInfo1.rskRecipientRskAddressInfo.address, shouldMine);
const pegoutTransaction2Promise = sendTxToBridge(rskTxHelper, new BN(satoshisToWeis(pegout2ValueInSatoshis)), senderRecipientInfo2.rskRecipientRskAddressInfo.address, shouldMine);
const pegoutTransaction3Promise = sendTxToBridge(rskTxHelper, new BN(satoshisToWeis(pegout3ValueInSatoshis)), senderRecipientInfo3.rskRecipientRskAddressInfo.address, shouldMine);

// Waiting for our 3 txs to reach the mempool before trying to mine them. + 3 because we are sending 3 pegouts.
const atLeastExpectedCount = initialRskMempoolTxHashesSize + 3;
await waitForRskMempoolToGetAtLeastThisManyTxs(rskTxHelper, atLeastExpectedCount);

// Now our 3 pegout transaction requests will get mined together.
await rskTxHelper.mine();

// After mining, we can get the transaction receipts.
const pegoutTransaction1 = await pegoutTransaction1Promise;
const pegoutTransaction2 = await pegoutTransaction2Promise;
const pegoutTransaction3 = await pegoutTransaction3Promise;

// Assert

let bridgeStateAfterPegoutCreation;

// Callback to get the bridge state after the pegout is created
const pegoutCreatedCallback = async () => {
bridgeStateAfterPegoutCreation = await getBridgeState(rskTxHelper.getClient());
};

const callbacks = {
pegoutCreatedCallback
};

await triggerRelease(rskTxHelpers, btcTxHelper, callbacks);

// Checking all the pegout events are emitted and in order
const blockNumberAfterPegoutRelease = await rskTxHelper.getBlockNumber();
const pegoutsEvents = await getPegoutEventsInBlockRange(rskTxHelper, pegoutTransaction1.blockNumber, blockNumberAfterPegoutRelease);

// The release_request_received event of the first pegout request
const rskSender1Address = rskTxHelper.getClient().utils.toChecksumAddress(ensure0x(senderRecipientInfo1.rskRecipientRskAddressInfo.address));
const releaseRequestReceivedEvent1 = pegoutsEvents.find(event => event.arguments.sender === rskSender1Address);
assertReleaseRequestReceivedEvent(releaseRequestReceivedEvent1, rskSender1Address, senderRecipientInfo1.btcSenderAddressInfo.address, pegout1ValueInSatoshis);

// The release_request_received event of the second pegout request
const rskSender2Address = rskTxHelper.getClient().utils.toChecksumAddress(ensure0x(senderRecipientInfo2.rskRecipientRskAddressInfo.address));
const releaseRequestReceivedEvent2 = pegoutsEvents.find(event => event.arguments.sender === rskSender2Address);
assertReleaseRequestReceivedEvent(releaseRequestReceivedEvent2, rskSender2Address, senderRecipientInfo2.btcSenderAddressInfo.address, pegout2ValueInSatoshis);

// The release_request_received event of the third pegout request
const rskSender3Address = rskTxHelper.getClient().utils.toChecksumAddress(ensure0x(senderRecipientInfo3.rskRecipientRskAddressInfo.address));
const releaseRequestReceivedEvent3 = pegoutsEvents.find(event => event.arguments.sender === rskSender3Address);
assertReleaseRequestReceivedEvent(releaseRequestReceivedEvent3, rskSender3Address, senderRecipientInfo3.btcSenderAddressInfo.address, pegout3ValueInSatoshis);

const pegoutWaitingForConfirmationWhenPegoutWasCreated = bridgeStateAfterPegoutCreation.pegoutsWaitingForConfirmations[0];
const btcTransaction = bitcoinJsLib.Transaction.fromHex(pegoutWaitingForConfirmationWhenPegoutWasCreated.btcRawTx);
const pegoutCreationRskTransactionHash = ensure0x(pegoutWaitingForConfirmationWhenPegoutWasCreated.rskTxHash.padStart(64, '0'));

// Release requested events
const releaseRequestedEvent = pegoutsEvents[3];
assertReleaseRequestedEvent(releaseRequestedEvent, pegoutCreationRskTransactionHash, btcTransaction.getId(), totalPegoutValueInSatoshis);

const batchPegoutCreatedEvent = pegoutsEvents[4];
assertBatchPegoutCreatedEvent(batchPegoutCreatedEvent, btcTransaction.getId(), [pegoutTransaction1.transactionHash, pegoutTransaction2.transactionHash, pegoutTransaction3.transactionHash]);

// pegout_confirmed event
const pegoutConfirmedEvent = pegoutsEvents[5];
assertPegoutConfirmedEvent(pegoutConfirmedEvent, btcTransaction.getId(), pegoutWaitingForConfirmationWhenPegoutWasCreated.pegoutCreationBlockNumber);

// add_signature events
const addSignatureEvents = pegoutsEvents.slice(6, pegoutsEvents.length - 1);
assertAddSignatureEvents(addSignatureEvents, releaseRequestedEvent);

// release_btc event
const releaseBtcEvent = pegoutsEvents[pegoutsEvents.length - 1];
assertReleaseBtcEvent(releaseBtcEvent, releaseRequestedEvent);

await assert2wpBalanceAfterSuccessfulPegout(initial2wpBalances, totalPegoutValueInSatoshis);

const releaseBtcTransaction = bitcoinJsLib.Transaction.fromHex(removePrefix0x(releaseBtcEvent.arguments.btcRawTransaction));

// Asserting actual value received for sender 1
const finalSenderAddressBalanceInSatoshis1 = await getBtcAddressBalanceInSatoshis(btcTxHelper, senderRecipientInfo1.btcSenderAddressInfo.address);
const sender1Utxo = getAddressUtxo(releaseBtcTransaction.outs, senderRecipientInfo1.btcSenderAddressInfo.address);
expect(finalSenderAddressBalanceInSatoshis1).to.be.equal(initialSender1AddressBalanceInSatoshis + sender1Utxo.value);

// Asserting actual value received for sender 2
const finalSenderAddressBalanceInSatoshis2 = await getBtcAddressBalanceInSatoshis(btcTxHelper, senderRecipientInfo2.btcSenderAddressInfo.address);
const serder2Utxo = getAddressUtxo(releaseBtcTransaction.outs, senderRecipientInfo2.btcSenderAddressInfo.address);
expect(finalSenderAddressBalanceInSatoshis2).to.be.equal(initialSender2AddressBalanceInSatoshis + serder2Utxo.value);

// Asserting actual value received for sender 3
const finalSenderAddressBalanceInSatoshis3 = await getBtcAddressBalanceInSatoshis(btcTxHelper, senderRecipientInfo3.btcSenderAddressInfo.address);
const serder3Utxo = getAddressUtxo(releaseBtcTransaction.outs, senderRecipientInfo3.btcSenderAddressInfo.address);
expect(finalSenderAddressBalanceInSatoshis3).to.be.equal(initialSender3AddressBalanceInSatoshis + serder3Utxo.value);

});

});

};
Expand Down Expand Up @@ -1008,6 +1140,44 @@ const assertBtcPeginTxHashProcessed = async (btcPeginTxHash) => {
expect(isBtcTxHashAlreadyProcessed).to.be.true;
};

const assertReleaseRequestReceivedEvent = (releaseRequestReceivedEvent, rskSenderAddress, btcRecipientAddress, pegoutValueInSatoshis) => {
expect(releaseRequestReceivedEvent.arguments.sender).to.be.equal(rskSenderAddress);
expect(Number(releaseRequestReceivedEvent.arguments.amount)).to.be.equal(pegoutValueInSatoshis);
expect(releaseRequestReceivedEvent.arguments.btcDestinationAddress).to.be.equal(btcRecipientAddress);
};

const assertReleaseRequestedEvent = (releaseRequestedEvent, pegoutCreationRskTransactionHash, btcTxHash, pegoutValueInSatoshis) => {
expect(releaseRequestedEvent.arguments.rskTxHash).to.be.equal(pegoutCreationRskTransactionHash);
expect(removePrefix0x(releaseRequestedEvent.arguments.btcTxHash)).to.be.equal(btcTxHash);
expect(Number(releaseRequestedEvent.arguments.amount)).to.be.equal(pegoutValueInSatoshis);
};

const assertBatchPegoutCreatedEvent = (batchPegoutCreatedEvent, btcTxHash, pegoutRequestReceivedTransactionHashes) => {
expect(removePrefix0x(batchPegoutCreatedEvent.arguments.btcTxHash)).to.be.equal(btcTxHash);
const releaseRskTxHashes = batchPegoutCreatedEvent.arguments.releaseRskTxHashes;
const allPegoutRequestReceivedTxHashesAreInBatch = pegoutRequestReceivedTransactionHashes.every(hash => releaseRskTxHashes.includes(removePrefix0x(hash)));
expect(allPegoutRequestReceivedTxHashesAreInBatch).to.be.true;
};

const assertPegoutConfirmedEvent = (pegoutConfirmedEvent, btcTxHash, pegoutCreationBlockNumber) => {
expect(removePrefix0x(pegoutConfirmedEvent.arguments.btcTxHash)).to.be.equal(btcTxHash);
expect(pegoutConfirmedEvent.arguments.pegoutCreationRskBlockNumber).to.be.equal(pegoutCreationBlockNumber);
};

const assertAddSignatureEvent = (addSignatureEvent, releaseRequestedEvent) => {
expect(addSignatureEvent.arguments.releaseRskTxHash).to.be.equal(releaseRequestedEvent.arguments.rskTxHash);
};

const assertAddSignatureEvents = (addSignatureEvents, releaseRequestedEvent) => {
addSignatureEvents.forEach(addSignatureEvent => {
assertAddSignatureEvent(addSignatureEvent, releaseRequestedEvent);
});
};

const assertReleaseBtcEvent = (releaseBtcEvent, releaseRequestedEvent) => {
expect(releaseBtcEvent.arguments.releaseRskTxHash).to.be.equal(releaseRequestedEvent.arguments.rskTxHash);
};

const assertSuccessfulPegoutEventsAreEmitted = async (pegoutsEvents, pegoutRequestReceivedTransactionHash, senderRecipientInfo, pegoutValueInSatoshis, bridgeStateAfterPegoutCreation) => {

const pegoutWaitingForConfirmationWhenPegoutWasCreated = bridgeStateAfterPegoutCreation.pegoutsWaitingForConfirmations[0];
Expand All @@ -1016,40 +1186,27 @@ const assertSuccessfulPegoutEventsAreEmitted = async (pegoutsEvents, pegoutReque

// release_request_received event
const releaseRequestReceivedEvent = pegoutsEvents[0];
expect(releaseRequestReceivedEvent.arguments.sender).to.be.equal(rskSenderAddress);
expect(Number(releaseRequestReceivedEvent.arguments.amount)).to.be.equal(pegoutValueInSatoshis);
expect(releaseRequestReceivedEvent.arguments.btcDestinationAddress).to.be.equal(senderRecipientInfo.btcSenderAddressInfo.address);
assertReleaseRequestReceivedEvent(releaseRequestReceivedEvent, rskSenderAddress, senderRecipientInfo.btcSenderAddressInfo.address, pegoutValueInSatoshis);

// release_requested event
const pegoutCreationRskTransactionHash = ensure0x(pegoutWaitingForConfirmationWhenPegoutWasCreated.rskTxHash.padStart(64, '0'));
const releaseRequestedEvent = pegoutsEvents[1];
expect(releaseRequestedEvent.arguments.rskTxHash).to.be.equal(pegoutCreationRskTransactionHash);
expect(removePrefix0x(releaseRequestedEvent.arguments.btcTxHash)).to.be.equal(btcTransaction.getId());
expect(Number(releaseRequestReceivedEvent.arguments.amount)).to.be.equal(pegoutValueInSatoshis);
assertReleaseRequestedEvent(releaseRequestedEvent, pegoutCreationRskTransactionHash, btcTransaction.getId(), pegoutValueInSatoshis);

// batch_pegout_created event
const batchPegoutCreatedEvent = pegoutsEvents[2];
expect(removePrefix0x(batchPegoutCreatedEvent.arguments.btcTxHash)).to.be.equal(btcTransaction.getId());
expect(batchPegoutCreatedEvent.arguments.releaseRskTxHashes.includes(removePrefix0x(pegoutRequestReceivedTransactionHash))).to.be.true;

assertBatchPegoutCreatedEvent(batchPegoutCreatedEvent, btcTransaction.getId(), [pegoutRequestReceivedTransactionHash]);

// pegout_confirmed event
const pegoutConfirmedEvent = pegoutsEvents[3];
expect(removePrefix0x(pegoutConfirmedEvent.arguments.btcTxHash)).to.be.equal(btcTransaction.getId());
expect(pegoutConfirmedEvent.arguments.pegoutCreationRskBlockNumber).to.be.equal(pegoutWaitingForConfirmationWhenPegoutWasCreated.pegoutCreationBlockNumber);
assertPegoutConfirmedEvent(pegoutConfirmedEvent, btcTransaction.getId(), pegoutWaitingForConfirmationWhenPegoutWasCreated.pegoutCreationBlockNumber);

// add_signature events
const addSignatureEvent1 = pegoutsEvents[4];
const addSignatureEvent2 = pegoutsEvents[5];
const addSignatureEvent3 = pegoutsEvents[6];

expect(addSignatureEvent1.arguments.releaseRskTxHash).to.be.equal(releaseRequestedEvent.arguments.rskTxHash);
expect(addSignatureEvent2.arguments.releaseRskTxHash).to.be.equal(releaseRequestedEvent.arguments.rskTxHash);
expect(addSignatureEvent3.arguments.releaseRskTxHash).to.be.equal(releaseRequestedEvent.arguments.rskTxHash);
const addSignatureEvents = pegoutsEvents.slice(4, pegoutsEvents.length - 1);
assertAddSignatureEvents(addSignatureEvents, releaseRequestedEvent);

// Final event, release_btc
const releaseBtcEvent = pegoutsEvents[7];
expect(releaseBtcEvent.arguments.releaseRskTxHash).to.be.equal(releaseRequestedEvent.arguments.rskTxHash);
const releaseBtcEvent = pegoutsEvents[pegoutsEvents.length - 1];
assertReleaseBtcEvent(releaseBtcEvent, releaseRequestedEvent);

};

Expand Down Expand Up @@ -1110,6 +1267,13 @@ const getReleaseRequestRejectedEventFromContractCallTxReceipt = (contractCallTxR
return releaseRequestRejectedEvent;
};

const getAddressUtxo = (outputs, address) => {
return outputs.find(output => {
const outputAddress = bitcoinJsLib.address.fromOutputScript(output.script, btcTxHelper.btcConfig.network);
return outputAddress === address;
});
};

module.exports = {
execute,
};

0 comments on commit 2ac0319

Please sign in to comment.