Skip to content

Conversation

@julianlen
Copy link
Contributor

Description

Motivation and Context

How Has This Been Tested?

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • Tests for the changes have been added (for bug fixes / features)
  • Requires Activation Code (Hard Fork)
  • Other information:

@julianlen julianlen requested a review from a team as a code owner January 16, 2026 20:25
@julianlen julianlen marked this pull request as draft January 16, 2026 20:26
@github-actions
Copy link

github-actions bot commented Jan 16, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@julianlen julianlen marked this pull request as ready for review January 19, 2026 14:18
Copy link
Contributor

@marcos-iov marcos-iov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test setup looks in general. I think we can improve a couple of things but not using mocks and not passing unnecessary values to the different builders


@BeforeEach
void setUp() {
feePerKbSupport = mock(FeePerKbSupport.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance we could use a real object and not mock this? Like this

feePerKbSupport = new FeePerKbSupportImpl(feePerKbConstants, feePerKbStorageProvider);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

void setUp() {
feePerKbSupport = mock(FeePerKbSupport.class);

bridgeEventLogger = mock(BridgeEventLogger.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this? looks like it's only used to pass it to the BridgeSupportBuilder but that's not really needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// Arrange
long newFederationCreationBlockNumber = 5L;
Federation newFederation = standardMultiSigFederationBuilder
.withCreationTime(Instant.EPOCH)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to pass this? Does it make any difference? Let's try to kkep it as simple as possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
}

private List<UTXO> generateRetiringFederationUTXOs(Address retiringFederationAddress, int numberOfUtxos, int valuePerOutput) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually doing 2 things and that's not good. It creates and returns the UTXOs but also sets them in the storage which is actually a hidden side effect of the method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return block;
}

private void callUpdateCollections(BridgeSupport bridgeSupport) throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider having this in a util class. Not the actual call, but the creation of the updateCollections transaction probably.

Could be in BridgeSupportTestUtil I guess, or feel free to propose another idea

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

bridgeSupport.updateCollections(updateCollectionsTx);
}

private static List<UTXO> getOldFederationUtxos(int numberOfUtxos, int valuePerOutput, Address oldFederationAddress) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just creates UTXOs, doesn't matter if it's for a old/new or even for a federation.

Probably best to have a util to create UTXOs, I think there is already one at least in RskTestUtils class. Or maybe we should have a UTXOBuilder class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In BitcoinTestUtils there is a createUTXOs method. However, it doesn't receive the amount as a parameter. I can create another method in BitcoinTestUtils that also receives the amount. I'm not sure a builder is necessary in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created another public static List<UTXO> createUTXOs(int numberOfUtxos, Coin value, Address receiver) method in BitcionTestUtils

return oldFederationUtxos;
}

public static Repository createRepository() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we already have this in some test utils classes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it is repeated in 3 different classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(added to the document)

}

private Block getBlockInMigrationAge(long newFederationCreationBlockNumber) {
Block block = mock(Block.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a method createRskBlock in RskTestUtil, we could use that to avoid creating a mock here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// TODO(juli): // provider.setPegoutTxSigHash(pegoutTxSigHash.get()); <- idem
}

private Coin calculateUtxosTotalValue(List<UTXO> retiringFederationUtxos) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are just summing up the values of a UTXOs list, I wouldn't call the param retiringFederationUtxos. The method could receive any list of UTXOs regarding of where they come from

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

assertTheReleaseOutpointValuesAreCorrect(btcTransaction, numberOfUtxos, retiringFederationUtxos);

// TODO(juli): // provider.setPegoutTxSigHash(pegoutTxSigHash.get()); <- idem
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert that all available UTXOs were included in the migration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When processing a migration request, BridgeSupport calls provider.setPegoutTxSigHash(pegoutTxSigHash.get()). However, the value stays in the pegoutTxSigHashes set, which is a private variable, until save() is called, which moves the values in the pegoutTxSigHashes to the Repository. Is this out of the scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a way to check all the retiring federation utxos are included in the migration tx

@julianlen julianlen force-pushed the refactor-BridgeSupportProcessFundsMigrationTest branch from 5f6d1f0 to 137059b Compare January 19, 2026 15:24
…ectionTransaction to BridgeSupportTestUtil, deleted unnecessary variables in the BridgeSupportProcessFundsMigrationTest setup, created in BitcoinTestUtils a new method to create a list of UTXOs with a certain amount and receiver, used createRskBlock from RskTestUtil
@julianlen julianlen force-pushed the refactor-BridgeSupportProcessFundsMigrationTest branch from 137059b to 9bada07 Compare January 19, 2026 15:25
@julianlen julianlen requested a review from Copilot January 19, 2026 15:35
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the BridgeSupportProcessFundsMigrationTest test class by extracting common test utilities and consolidating duplicate code, improving test maintainability and reducing code duplication.

Changes:

  • Extracted common transaction building and UTXO creation utilities into shared helper classes
  • Added a new comprehensive test case for funds migration with proper setup
  • Removed duplicate utility methods across multiple test files

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
BridgeSupportProcessFundsMigrationTest.java Refactored test class with extracted utilities, added new test case, and included setup method with instance variables
BridgeSupportTestUtil.java Added buildUpdateCollectionsTx utility method for transaction creation
BitcoinTestUtils.java Added overloaded createUTXOs method accepting value and receiver parameters
BridgeSupportIT.java Removed duplicate createRepository method, now using shared utility
FederationChangeIT.java Updated to use shared buildUpdateCollectionsTx utility instead of local implementation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

if (activations.isActive(ConsensusRule.RSKIP146)) {
if (activations.isActive(ConsensusRule.RSKIP146)) { // Papyrus
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While 'Papyrus' appears to be a codename for RSKIP146, this comment lacks context for readers unfamiliar with the codebase. Consider expanding it to explain what Papyrus represents or the significance of this activation rule.

Suggested change
if (activations.isActive(ConsensusRule.RSKIP146)) { // Papyrus
if (activations.isActive(ConsensusRule.RSKIP146)) { // Papyrus (RSKIP146) network upgrade: enables logging and tracking of migrated pegouts after activation

Copilot uses AI. Check for mistakes.
);

if (activations.isActive(ConsensusRule.RSKIP376)){
if (activations.isActive(ConsensusRule.RSKIP376)){ // Arrowhead
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While 'Arrowhead' appears to be a codename for RSKIP376, this comment lacks context for readers unfamiliar with the codebase. Consider expanding it to explain what Arrowhead represents or the significance of this activation rule.

Suggested change
if (activations.isActive(ConsensusRule.RSKIP376)){ // Arrowhead
if (activations.isActive(ConsensusRule.RSKIP376)){ // Arrowhead (RSKIP376 upgrade): after activation, pegout BTC transactions must use version 2 instead of the legacy version

Copilot uses AI. Check for mistakes.

assertTheReleaseOutpointValuesAreCorrect(btcTransaction, numberOfUtxos, retiringFederationUtxos);

// TODO(juli): // provider.setPegoutTxSigHash(pegoutTxSigHash.get()); <- idem
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO comment contains unclear references ('juli', 'idem'). TODO comments should clearly describe what needs to be done, why it's needed, and provide enough context for any developer to understand and implement the task.

Suggested change
// TODO(juli): // provider.setPegoutTxSigHash(pegoutTxSigHash.get()); <- idem
// TODO: When pegout transaction sighash is stored for migrated funds, update this test to set the sighash on the provider and assert it is persisted correctly.

Copilot uses AI. Check for mistakes.
@julianlen julianlen requested a review from Copilot January 19, 2026 15:38
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…at all the retiringFederation utxos are actually included as input in the migration tx
@julianlen julianlen requested a review from Copilot January 21, 2026 18:55
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sonarqubecloud
Copy link

}

@Test
void processFundsMigration_withRetiringGenesisFederationAndNewMultisigFederation_inMigrationAge_shouldMigrateFunds() throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should not use all activations for testing this scenario. For example, the releaseOutpointsValues assertion does not make sense in this case

return new MutableRepository(new MutableTrieCache(new MutableTrieImpl(null, new Trie())));
}

public static Transaction buildUpdateCollectionsTx(byte chainId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not the best to receive a byte as argument, can't we pass the Constants and get the chainId from them?

var rskTx = CallTransaction.createCallTransaction(nonce, gasPrice.longValue(),
gasLimit.longValue(), PrecompiledContracts.BRIDGE_ADDR, value,
Bridge.UPDATE_COLLECTIONS, chainId);
var randomKey = BtcECKey.fromPrivate(Hex.decode("45c5b07fc1a6f58892615b7c31dca6c96db58c4bbc538a6b8a22999aaa860c32"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does this priv key came from? can we use a random seed instead?

Comment on lines +156 to +189
Federation retiringFederation = FederationTestUtils.getGenesisFederation(federationConstants);
int numberOfUtxos = 1;
Coin valuePerOutput = Coin.valueOf(1_500_000);
Address retiringFederationAddress = retiringFederation.getAddress();

List<UTXO> retiringFederationUtxos = createUTXOs(numberOfUtxos, valuePerOutput, retiringFederationAddress);
byte[] serializeUTXOList = BridgeSerializationUtils.serializeUTXOList(retiringFederationUtxos);
bridgeStorageAccessor.saveToRepository(OLD_FEDERATION_BTC_UTXOS_KEY.getKey(), serializeUTXOList);

long newFederationCreationBlockNumber = 5L;
Federation newFederation = standardMultiSigFederationBuilder
.withCreationBlockNumber(newFederationCreationBlockNumber)
.withNetworkParameters(networkParams)
.build();

federationStorageProvider.setOldFederation(retiringFederation);
federationStorageProvider.setNewFederation(newFederation);

Block rskCurrentBlock = getBlockInMigrationAge(newFederationCreationBlockNumber);
FederationSupport federationSupport = FederationSupportBuilder.builder()
.withFederationConstants(federationConstants)
.withFederationStorageProvider(federationStorageProvider)
.withRskExecutionBlock(rskCurrentBlock)
.withActivations(activations)
.build();

BridgeSupport bridgeSupport = BridgeSupportBuilder.builder()
.withBridgeConstants(bridgeMainnetConstants)
.withProvider(bridgeStorageProvider)
.withExecutionBlock(rskCurrentBlock)
.withActivations(activations)
.withFeePerKbSupport(feePerKbSupport)
.withFederationSupport(federationSupport)
.build();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed yet, but we should probably move all this to a setup function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totally agree

}
}

private Block getBlockInMigrationAge(long newFederationCreationBlockNumber) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this to a utility file as a "getBlockWithHeight" method?

@marcos-iov marcos-iov marked this pull request as draft January 27, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants