From cf73641711d6a988d0fa4f0d6f713cb8f7a72610 Mon Sep 17 00:00:00 2001 From: jeremy-then Date: Tue, 21 Jan 2025 19:00:09 -0400 Subject: [PATCH] Adds migrate utxos test --- lib/btc-utils.js | 2 +- tests/00_00_04-change-federation.js | 69 ++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/lib/btc-utils.js b/lib/btc-utils.js index 5e97e144..67369185 100644 --- a/lib/btc-utils.js +++ b/lib/btc-utils.js @@ -107,7 +107,7 @@ const waitForBitcoinTxToBeInMempool = async (btcTxHelper, btcTxHash, maxAttempts const isTxInMempool = bitcoinMempool.includes(btcTxHash); if(!isTxInMempool) { logger.debug(`[${waitForBitcoinTxToBeInMempool.name}::${bitcoinMempoolHasTx.name}] 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); + const tx = await btcTxHelper.getTransaction(btcTxHash); if(tx) { logger.debug(`[${waitForBitcoinTxToBeInMempool.name}::${bitcoinMempoolHasTx.name}] The btc tx (${btcTxHash}) was already mined.`); return true; diff --git a/tests/00_00_04-change-federation.js b/tests/00_00_04-change-federation.js index 3b904637..5d26f8db 100644 --- a/tests/00_00_04-change-federation.js +++ b/tests/00_00_04-change-federation.js @@ -29,6 +29,7 @@ const { svpFundTxSignedStorageIndex, svpSpendTxHashUnsignedStorageIndex, svpSpendTxWaitingForSignaturesStorageIndex, + FUNDS_MIGRATION_AGE_SINCE_ACTIVATION_BEGIN, } = require('../lib/constants/federation-constants'); const { createSenderRecipientInfo, @@ -112,6 +113,8 @@ describe('Change federation', async function() { let expectedNewFederationErpRedeemScript; let minimumPeginValueInSatoshis; let expectedFlyoverAddress; + let bridgeStateBeforeActivation; + let svpSpendBtcTransaction; before(async () => { @@ -391,6 +394,7 @@ describe('Change federation', async function() { it('should register the SVP Spend transaction and finish the SVP process', async () => { + bridgeStateBeforeActivation = await getBridgeState(rskTxHelper.getClient()); const blockNumberBeforeUpdateCollections = await rskTxHelper.getBlockNumber(); const expectedCountOfSignatures = Math.floor(newFederationPublicKeys.length / 2) + 1; @@ -399,10 +403,10 @@ describe('Change federation', async function() { // Finding the SVP Spend transaction release_btc event const blockNumberAfterRelease = await rskTxHelper.getBlockNumber(); const releaseBtcEvent = await rskUtils.findEventInBlock(rskTxHelper, PEGOUT_EVENTS.RELEASE_BTC.name, blockNumberBeforeUpdateCollections, blockNumberAfterRelease); - const releaseBtcTransaction = bitcoinJsLib.Transaction.fromHex(removePrefix0x(releaseBtcEvent.arguments.btcRawTransaction)); + svpSpendBtcTransaction = bitcoinJsLib.Transaction.fromHex(removePrefix0x(releaseBtcEvent.arguments.btcRawTransaction)); // Mining the SVP Spend transaction in bitcoin to register it in the Bridge - await waitForBitcoinTxToBeInMempool(btcTxHelper, releaseBtcTransaction.getId()); + await waitForBitcoinTxToBeInMempool(btcTxHelper, svpSpendBtcTransaction.getId()); await btcTxHelper.mine(BTC_TO_RSK_MINIMUM_CONFIRMATIONS); await rskUtils.waitAndUpdateBridge(rskTxHelper); @@ -443,6 +447,62 @@ describe('Change federation', async function() { }); + it('should migrate utxos', async () => { + + const initialBridgeState = await getBridgeState(rskTxHelper.getClient()); + + expect(initialBridgeState.activeFederationUtxos.length).to.be.equal(0, 'There should not be any utxos in the active federation yet.'); + expect(initialBridgeState.pegoutsWaitingForConfirmations.length).to.be.equal(0, 'No pegout should be waiting for confirmations.'); + expect(initialBridgeState.pegoutsWaitingForSignatures.length).to.be.equal(0, 'No pegout should be waiting for signatures.'); + + // Mining to activate the migration age + await rskTxHelper.mine(FUNDS_MIGRATION_AGE_SINCE_ACTIVATION_BEGIN); + + // Start migration + await rskUtils.waitAndUpdateBridge(rskTxHelper); + const bridgeStateAfterUpdatingCollections = await getBridgeState(rskTxHelper.getClient()); + expect(bridgeStateAfterUpdatingCollections.pegoutsWaitingForConfirmations.length).to.be.equal(1, 'There should be one pegout waiting for confirmations.'); + + await rskTxHelper.mine(BTC_TO_RSK_MINIMUM_CONFIRMATIONS); + await rskUtils.waitAndUpdateBridge(rskTxHelper); + const bridgeStateAfterMiningPegout = await getBridgeState(rskTxHelper.getClient()); + expect(bridgeStateAfterMiningPegout.pegoutsWaitingForConfirmations.length).to.be.equal(0, 'No pegout should be waiting for confirmations.'); + expect(bridgeStateAfterMiningPegout.pegoutsWaitingForSignatures.length).to.be.equal(1, 'There should be one pegout waiting for signatures.'); + + const expectedCountOfSignatures = Math.floor(initialFederationPublicKeys.length / 2) + 1; + await waitForExpectedCountOfAddSignatureEventsToBeEmitted(rskTxHelper, latestBlockNumber, expectedCountOfSignatures); + + // Finding the migration transaction release_btc event + const blockNumberAfterRelease = await rskTxHelper.getBlockNumber(); + const releaseBtcEvent = await rskUtils.findEventInBlock(rskTxHelper, PEGOUT_EVENTS.RELEASE_BTC.name, latestBlockNumber, blockNumberAfterRelease); + const migrationReleaseBtcTransaction = bitcoinJsLib.Transaction.fromHex(removePrefix0x(releaseBtcEvent.arguments.btcRawTransaction)); + + // The SVP Fund transaction utxo is not included in the bridge utxo list but should be included in the migration transaction inputs. + // So we need to assert that the Bridge doesn't have it but the migration transaction does. + const inputsTxHashes = migrationReleaseBtcTransaction.ins.map(input => input.hash.reverse().toString('hex')); + const missingFromBridgeStateInputHashes = findMissingHashes(bridgeStateBeforeActivation.activeFederationUtxos, inputsTxHashes); + expect(missingFromBridgeStateInputHashes.length).to.be.equal(1, 'There should be one missing hash, the utxo that was sent to the Proposed federation in the SVP Spend transaction.'); + const actualSvpSpendTxHash = missingFromBridgeStateInputHashes[0]; + expect(actualSvpSpendTxHash).to.be.equal(svpSpendBtcTransaction.getId(), 'The missing hash should be the SVP Spend transaction hash.'); + + const destinationAddress = bitcoinJsLib.address.fromOutputScript(migrationReleaseBtcTransaction.outs[0].script, btcTxHelper.btcConfig.network); + expect(destinationAddress).to.be.equal(expectedNewFederationAddress, 'The destination address of the migration transaction should be the new federation address.'); + + // Mining the migration transaction in bitcoin and registering it in the Bridge + await waitForBitcoinTxToBeInMempool(btcTxHelper, migrationReleaseBtcTransaction.getId()); + await btcTxHelper.mine(BTC_TO_RSK_MINIMUM_CONFIRMATIONS); + await rskUtils.waitAndUpdateBridge(rskTxHelper); + + const finalBridgeState = await getBridgeState(rskTxHelper.getClient()); + + const registeredMigrationTxUtxo = finalBridgeState.activeFederationUtxos[0]; + expect(registeredMigrationTxUtxo, 'The migration tx should be registered in the Bridge by now.').to.not.be.undefined; + + const utxoToNewFederation = migrationReleaseBtcTransaction.outs[0]; + expect(registeredMigrationTxUtxo.valueInSatoshis).to.be.equal(utxoToNewFederation.value, 'The migration tx registered utxo value should be the same as the utxo to the new federation.'); + + }); + it('should complete retiring the old federation', async () => { const blocksToMineToRetireFederation = FUNDS_MIGRATION_AGE_SINCE_ACTIVATION_END; @@ -585,3 +645,8 @@ const waitForExpectedCountOfAddSignatureEventsToBeEmitted = async (rskTxHelper, } }; + +function findMissingHashes(activeFederationUtxos, inputsTxHashes) { + const activeUtxoHashes = activeFederationUtxos.map(utxo => utxo.btcTxHash); + return inputsTxHashes.filter(hash => !activeUtxoHashes.includes(hash)); +};