Skip to content

Commit

Permalink
Merge pull request #41 from rsksmart/add-assertions-for-pegout-transa…
Browse files Browse the repository at this point in the history
…ction-created-event

Add assertions for pegout transaction created event
  • Loading branch information
marcos-iov authored Jul 9, 2024
2 parents 666999a + 8650fa9 commit 383432a
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 258 deletions.
52 changes: 35 additions & 17 deletions lib/assertions/2wp.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
const expect = require('chai').expect;
var { wait } = require('../utils');
var {wait, removePrefix0x} = require('../utils');
var bitcoin = require('peglib').bitcoin;
var rsk = require('peglib').rsk;
const { MAX_ESTIMATED_FEE_PER_PEGOUT, FEE_DIFFERENCE_PER_PEGOUT } = require('../constants');
const {MAX_ESTIMATED_FEE_PER_PEGOUT, FEE_DIFFERENCE_PER_PEGOUT} = require('../constants');
const {encodeOutpointValuesAsMap, decodeOutpointValues} = require("../varint");

const BTC_TX_FEE = bitcoin.btcToSatoshis(0.001);

/**
* @deprecated
* @param {object} btcClient
* @param {object} rskClient
* @param {object} pegClient
* @param {object} btcClient
* @param {object} rskClient
* @param {object} pegClient
* @returns {void}
*/
var assertBitcoinBalance = (btcClient, rskClient, pegClient) => (btcAddress, expectedBalance, message) => {
Expand All @@ -28,8 +29,8 @@ var assertLock = (btcClient, rskClient, pegClient) => (fromAddresses, outputs, o

var initialBlockNumber;
var initialFederationBalances, initialBtcBalance, initialRskBalance, expectedFederationBalances;
var federationAddresses = Object.keys(outputs.reduce((ad, output) => ({ ...ad, [output.address]: true }), {}));
amount = outputs.reduce((t, o) => t+o.amount, 0);
var federationAddresses = Object.keys(outputs.reduce((ad, output) => ({...ad, [output.address]: true}), {}));
amount = outputs.reduce((t, o) => t + o.amount, 0);

return Promise.resolve()
.then(() => btcClient.getAddressBalance(federationAddresses)).then((btcBalances) => {
Expand All @@ -45,12 +46,12 @@ var assertLock = (btcClient, rskClient, pegClient) => (fromAddresses, outputs, o
.then(() => btcClient.getAddressBalance(fromAddresses.btc)).then((btcBalances) => {
initialBtcBalance = btcBalances[fromAddresses.btc] || 0;
})
.then(async () => {
if (!isMultisig(fromAddresses)) {
.then(async () => {
if (!isMultisig(fromAddresses)) {
let currentRskBalance = await rskClient.eth.getBalance(fromAddresses.rsk);
const currentRskBalanceInSatoshis = rsk.weisToSatoshis(currentRskBalance);
initialRskBalance = currentRskBalanceInSatoshis;
}
}
})
.then(() => isMultisig(fromAddresses) ? btcClient.sendFromMultisigTo(fromAddresses, outputs, BTC_TX_FEE, 1) : btcClient.sendFromTo(fromAddresses.btc, outputs, BTC_TX_FEE, 1))
.then(() => mineAndUpdate(btcClient, rskClient, options.shouldMineAndUpdatePrematurely))
Expand All @@ -73,16 +74,15 @@ var assertLock = (btcClient, rskClient, pegClient) => (fromAddresses, outputs, o
.then(() => rskClient.evm.mine())
.then(() => rskClient.eth.getBlockNumber())
.then((currentBlockNumber) => {
expect(currentBlockNumber).to.equal(initialBlockNumber+1);
expect(currentBlockNumber).to.equal(initialBlockNumber + 1);
})
.then(async () => {
if (!isMultisig(fromAddresses)) {
let currentRskBalance = await rskClient.eth.getBalance(fromAddresses.rsk);
.then(async () => {
if (!isMultisig(fromAddresses)) {
let currentRskBalance = await rskClient.eth.getBalance(fromAddresses.rsk);
const currentRskBalanceInSatoshis = rsk.weisToSatoshis(currentRskBalance);
if (options.fails) {
expect(currentRskBalanceInSatoshis, 'Wrong RSK balance').to.equal(initialRskBalance);
}
else {
} else {
expect(currentRskBalanceInSatoshis, 'Wrong RSK balance').to.equal(Number(initialRskBalance) + Number(amount));
}
}
Expand Down Expand Up @@ -117,10 +117,28 @@ const assertCallToPegoutBatchingBridgeMethods = (rskClient) => async (expectedCo
expect(Number(nextPegoutCreationBlockNumber)).to.equal(expectedNextPegoutCreationBlockNumber);
}

const assertRejectedPeginEvent = (rejectedPeginTx, expectedRejectionReason, expectedPeginBtcHash, expectedRefundAmountInSatoshis) => {
const rejectedPeginEvent = rejectedPeginTx.events[0];
expect(rejectedPeginEvent).to.not.be.null;
expect(rejectedPeginEvent.arguments.btcTxHash).to.equal(expectedPeginBtcHash);
expect(rejectedPeginEvent.arguments.reason).to.equal(expectedRejectionReason);

const pegoutRequestedEvent = rejectedPeginTx.events[1];
expect(pegoutRequestedEvent).to.not.be.null;

const pegoutTransactionCreatedEvent = rejectedPeginTx.events[2];
expect(pegoutTransactionCreatedEvent).to.not.be.null;
const encodedUtxoOutpointValues = Buffer.from(removePrefix0x(pegoutTransactionCreatedEvent.arguments.utxoOutpointValues), 'hex');
const federationUtxoValues = encodeOutpointValuesAsMap([{"valueInSatoshis": expectedRefundAmountInSatoshis}]);
const outpointValues = decodeOutpointValues(encodedUtxoOutpointValues);
expect(outpointValues.every(value => value in federationUtxoValues)).to.be.true;
}

module.exports = {
with: (btcClient, rskClient, pegClient) => ({
assertBitcoinBalance: assertBitcoinBalance(btcClient, rskClient, pegClient),
assertLock: assertLock(btcClient, rskClient, pegClient),
}),
assertCallToPegoutBatchingBridgeMethods
assertCallToPegoutBatchingBridgeMethods,
assertRejectedPeginEvent
};
10 changes: 10 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ const PEGIN_REJECTION_REASONS = {
PEGIN_V1_INVALID_PAYLOAD_REASON: '4'
};

const PEGOUT_EVENTS = {
RELEASE_REQUEST_RECEIVED: "release_request_received",
RELEASE_REQUEST_REJECTED: "release_request_rejected",
RELEASE_REQUESTED: "release_requested",
BATCH_PEGOUT_CREATED: "batch_pegout_created",
PEGOUT_TRANSACTION_CREATED: "pegout_transaction_created",
PEGOUT_CONFIRMED: "pegout_confirmed"
}

module.exports = {
KEY_TYPE_BTC,
KEY_TYPE_RSK,
Expand All @@ -63,4 +72,5 @@ module.exports = {
GENESIS_FEDERATION_REDEEM_SCRIPT,
FEDERATION_ACTIVATION_AGE,
PEGIN_REJECTION_REASONS,
PEGOUT_EVENTS
};
23 changes: 17 additions & 6 deletions lib/rsk-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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 BridgeTransactionParser = require('bridge-transaction-parser-lovell700');
const { getRskTransactionHelpers } = require('../lib/rsk-tx-helper-provider');
const { wait, retryWithCheck, removePrefix0x } = require('./utils');
const { waitForBitcoinMempoolToGetTxs } = require('./btc-utils');
Expand Down Expand Up @@ -49,7 +50,7 @@ const waitForSync = async (rskTransactionHelpers) => {
* @param {Number} maxAttempts defaults to 80 attempts by block.
* @returns {Promise<Number>} the latest block number the same or greater than `blockNumber`.
*/
const waitForBlock = (rskClient, blockNumber, waitTime = 200, maxAttempts = 80) => {
const waitForBlock = (rskClient, blockNumber, waitTime = 200, maxAttempts = 200) => {
return new Promise((resolve, reject) => {
let attempts = 1;
let latestBlockNumber = -1;
Expand Down Expand Up @@ -374,20 +375,17 @@ const findEventInBlock = async (rskTxHelper, eventName, fromBlockHashOrBlockNumb
if(fromBlockHashOrBlockNumber > toBlockHashOrBlockNumber) {
throw new Error(`fromBlockHashOrBlockNumber: ${fromBlockHashOrBlockNumber} is greater than toBlockHashOrBlockNumber: ${toBlockHashOrBlockNumber}`);
}

const isFingerroot500AlreadyActive = await Runners.common.forks.fingerroot500.isAlreadyActive();
const bridgeTxParser = isFingerroot500AlreadyActive ? fingerrootBridgeTxParser : hopBridgeTxParser;

while(fromBlockHashOrBlockNumber <= toBlockHashOrBlockNumber) {
const bridgeTransactions = await bridgeTxParser.getBridgeTransactionsInThisBlock(rskTxHelper.getClient(), fromBlockHashOrBlockNumber);
const bridgeTransactions = await findBridgeTransactionsInThisBlock(rskTxHelper.getClient(), fromBlockHashOrBlockNumber);
for(let i = 0; i < bridgeTransactions.length; i++) {
const tx = bridgeTransactions[i];
const event = tx.events.find(event => {
return event.name === eventName;
});
if(event) {
if(check) {
const found = await check(event, fromBlockHashOrBlockNumber);
const found = await check(event, tx);
if(!found) {
continue;
}
Expand All @@ -400,6 +398,19 @@ const findEventInBlock = async (rskTxHelper, eventName, fromBlockHashOrBlockNumb
return null;
};

const findBridgeTransactionsInThisBlock = async (web3Client, blockHashOrBlockNumber) => {
const isLovell700AlreadyActive = await Runners.common.forks.lovell700.isAlreadyActive()

if (isLovell700AlreadyActive){
const bridgeTransactionParser = new BridgeTransactionParser(web3Client);
return bridgeTransactionParser.getBridgeTransactionsInThisBlock(blockHashOrBlockNumber);
}

const isFingerroot500AlreadyActive = await Runners.common.forks.fingerroot500.isAlreadyActive();
const bridgeTxParser = isFingerroot500AlreadyActive ? fingerrootBridgeTxParser : hopBridgeTxParser;
return bridgeTxParser.getBridgeTransactionsInThisBlock(web3Client, blockHashOrBlockNumber);
}

/**
* Imports the private key and unlocks the rskAddress, asserting that the rskAddress returned by the private key import is the same as the rskAddress provided.
* @param {RskTransactionHelper} rskTxHelper
Expand Down
32 changes: 25 additions & 7 deletions lib/tests/2wp.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const expect = require('chai').expect
const { ensure0x } = require('../utils');
const { ensure0x, removePrefix0x} = require('../utils');
const whitelistingAssertions = require('../assertions/whitelisting');
const rskUtils = require('../rsk-utils');
const CustomError = require('../CustomError');
Expand All @@ -10,6 +10,9 @@ const { getDerivedRSKAddressInformation } = require('@rsksmart/btc-rsk-derivatio
const btcEthUnitConverter = require('@rsksmart/btc-eth-unit-converter');
const { sendTxToBridge, sendPegin, ensurePeginIsRegistered, donateToBridge } = require('../2wp-utils');
const { waitAndUpdateBridge } = require('../rsk-utils');
const { decodeOutpointValues, encodeOutpointValuesAsMap } = require("../varint");
const {getBridgeState} = require("@rsksmart/bridge-state-data-parser");
const {PEGOUT_EVENTS} = require("../constants");

const DONATION_AMOUNT = 250;
const REJECTED_REASON = 1;
Expand Down Expand Up @@ -142,14 +145,16 @@ const execute = (description, getRskHost) => {
expect(unlocked, 'Account was not unlocked').to.be.true;

await rskUtils.sendFromCow(rskTxHelper, recipientRskAddressInfo.address, Number(btcEthUnitConverter.btcToWeis(INITIAL_RSK_BALANCE)));


const bridgeStateBeforePegout = await getBridgeState(rskTxHelper.getClient());
const activeFederationUtxosBeforePegout = bridgeStateBeforePegout.activeFederationUtxos;
const pegoutTransaction = await sendTxToBridge(rskTxHelper, PEGOUT_VALUE_IN_RBTC, recipientRskAddressInfo.address);

const isFingerroot500AlreadyActive = await Runners.common.forks.fingerroot500.isAlreadyActive();

const isIris300AlreadyActive = await Runners.common.forks.iris300.isAlreadyActive();
if (isIris300AlreadyActive) {
const pegoutRequestReceivedEvent = await rskUtils.findEventInBlock(rskTxHelper, 'release_request_received');
const pegoutRequestReceivedEvent = await rskUtils.findEventInBlock(rskTxHelper, PEGOUT_EVENTS.RELEASE_REQUEST_RECEIVED);
expect(pegoutRequestReceivedEvent).to.not.be.null;
const btcDestinationAddress = pegoutRequestReceivedEvent.arguments.btcDestinationAddress;
expect(pegoutRequestReceivedEvent.arguments.sender.toLowerCase()).to.equal(ensure0x(recipientRskAddressInfo.address));
Expand All @@ -165,21 +170,34 @@ const execute = (description, getRskHost) => {
const pegoutCreatedValidations = async (localRskTxHelper) => {
const isPapyrus200AlreadyActive = await Runners.common.forks.papyrus200.isAlreadyActive();
if (isPapyrus200AlreadyActive) {
const pegoutRequestedEvent = await rskUtils.findEventInBlock(localRskTxHelper, 'release_requested');
const pegoutRequestedEvent = await rskUtils.findEventInBlock(localRskTxHelper, PEGOUT_EVENTS.RELEASE_REQUESTED);
expect(pegoutRequestedEvent).to.not.be.null;
expect(Number(pegoutRequestedEvent.arguments.amount)).to.equal(pegoutValueInSatoshis);
}
const isHop400AlreadyActive = await Runners.common.forks.hop400.isAlreadyActive();
if (isHop400AlreadyActive) {
const batchPegoutCreatedEvent = await rskUtils.findEventInBlock(localRskTxHelper, 'batch_pegout_created');
const batchPegoutCreatedEvent = await rskUtils.findEventInBlock(localRskTxHelper, PEGOUT_EVENTS.BATCH_PEGOUT_CREATED);
expect(batchPegoutCreatedEvent).to.not.be.null;
expect(batchPegoutCreatedEvent.arguments.releaseRskTxHashes.includes(pegoutTransaction.transactionHash)).to.be.true;
}

const isLovell700AlreadyActive = await Runners.common.forks.lovell700.isAlreadyActive();
if (isLovell700AlreadyActive) {
const pegoutTransactionCreatedEvent = await rskUtils.findEventInBlock(localRskTxHelper, PEGOUT_EVENTS.PEGOUT_TRANSACTION_CREATED);
expect(pegoutTransactionCreatedEvent).to.not.be.null;
const encodedUtxoOutpointValues = Buffer.from(removePrefix0x(pegoutTransactionCreatedEvent.arguments.utxoOutpointValues), 'hex');

const federationUtxoValues = encodeOutpointValuesAsMap(activeFederationUtxosBeforePegout);

const outpointValues = decodeOutpointValues(encodedUtxoOutpointValues);

expect(outpointValues.every(value => value in federationUtxoValues)).to.be.true;
}
};

const pegoutConfirmedValidations = async (localRskTxHelper) => {
if (isFingerroot500AlreadyActive) {
const pegoutConfirmedEvent = await rskUtils.findEventInBlock(localRskTxHelper, 'pegout_confirmed');
const pegoutConfirmedEvent = await rskUtils.findEventInBlock(localRskTxHelper, PEGOUT_EVENTS.PEGOUT_CONFIRMED);
expect(pegoutConfirmedEvent).to.not.be.null;
}
};
Expand Down Expand Up @@ -217,7 +235,7 @@ const execute = (description, getRskHost) => {
const pegoutTransaction = await sendTxToBridge(rskTxHelper, PEGOUT_UNDER_MINIMUM_VALUE_IN_BTC, recipientRskAddressInfo.address);
const isIris300AlreadyActive = await Runners.common.forks.iris300.isAlreadyActive();
if (isIris300AlreadyActive) {
const pegoutRejectedEvent = await rskUtils.findEventInBlock(rskTxHelper, 'release_request_rejected');
const pegoutRejectedEvent = await rskUtils.findEventInBlock(rskTxHelper, PEGOUT_EVENTS.RELEASE_REQUEST_REJECTED);
expect(pegoutRejectedEvent).to.not.be.null;
const pegoutValueInSatoshis = Number(btcEthUnitConverter.btcToSatoshis(PEGOUT_UNDER_MINIMUM_VALUE_IN_BTC));
expect(Number(pegoutRejectedEvent.arguments.amount)).to.equal(pegoutValueInSatoshis);
Expand Down
86 changes: 86 additions & 0 deletions lib/varint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
class VarInt {
constructor(value) {
if (typeof value === 'number' || typeof value === 'bigint') {
this.value = BigInt(value);
this.originallyEncodedSize = this.getSizeInBytes();
} else if (Buffer.isBuffer(value)) {
this.value = this.decode(value);
this.originallyEncodedSize = this.getSizeInBytes();
} else {
throw new Error('Invalid input: value should be a number or buffer');
}
}

getOriginalSizeInBytes() {
return this.originallyEncodedSize;
}

getSizeInBytes() {
return VarInt.sizeOf(this.value);
}

static sizeOf(value) {
if (value < 0) return 9;
if (value < 253) return 1;
if (value <= 0xFFFF) return 3;
if (value <= 0xFFFFFFFF) return 5;
return 9;
}

encode() {
let bytes;
switch (this.getSizeInBytes()) {
case 1:
return Buffer.from([Number(this.value)]);
case 3:
return Buffer.from([253, Number(this.value & 0xFF), Number((this.value >> 8) & 0xFF)]);
case 5:
bytes = Buffer.alloc(5);
bytes[0] = 254;
bytes.writeUInt32LE(Number(this.value), 1);
return bytes;
case 9:
bytes = Buffer.alloc(9);
bytes[0] = 255;
bytes.writeBigUInt64LE(this.value, 1);
return bytes;
default:
throw new Error('Invalid size for encoding');
}
}

decode(buffer) {
const first = buffer[0];
if (first < 253) {
return BigInt(first);
} else if (first === 253) {
return BigInt(buffer.readUInt16LE(1));
} else if (first === 254) {
return BigInt(buffer.readUInt32LE(1));
} else {
return buffer.readBigUInt64LE(1);
}
}
}

const decodeOutpointValues = (encodedUtxoOutpointValues) => {
let offset = 0;
let idx = 0;
const outpointValues = [];
while (encodedUtxoOutpointValues.length > offset) {
let utxoOutpointValue = new VarInt(encodedUtxoOutpointValues, offset);
outpointValues.push(utxoOutpointValue.value);
offset += utxoOutpointValue.getSizeInBytes();
idx++;
}
return outpointValues;
}

const encodeOutpointValuesAsMap = (utxos) => {
return utxos.reduce((map, utxo) => {
map[utxo.valueInSatoshis] = Buffer.from(new VarInt(utxo.valueInSatoshis).encode()).toString("hex");
return map;
}, {});
}

module.exports = { VarInt, decodeOutpointValues, encodeOutpointValuesAsMap };
Loading

0 comments on commit 383432a

Please sign in to comment.