From e47fa7fa5adc7c1bd6f50ca44a14f314c8ccce63 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:23:24 +0000 Subject: [PATCH] feat(peg): refactor min peg value tests --- .../java/co/rsk/peg/MinimumPegValueTest.java | 189 ++++++++++++++++++ .../peg/ReleaseTransactionBuilderTest.java | 158 +-------------- 2 files changed, 190 insertions(+), 157 deletions(-) create mode 100644 rskj-core/src/test/java/co/rsk/peg/MinimumPegValueTest.java diff --git a/rskj-core/src/test/java/co/rsk/peg/MinimumPegValueTest.java b/rskj-core/src/test/java/co/rsk/peg/MinimumPegValueTest.java new file mode 100644 index 00000000000..28a71134868 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/peg/MinimumPegValueTest.java @@ -0,0 +1,189 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.peg; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import co.rsk.bitcoinj.core.Address; +import co.rsk.bitcoinj.core.BtcECKey; +import co.rsk.bitcoinj.core.Coin; +import co.rsk.bitcoinj.core.Context; +import co.rsk.bitcoinj.core.NetworkParameters; +import co.rsk.bitcoinj.core.Sha256Hash; +import co.rsk.bitcoinj.core.UTXO; +import co.rsk.bitcoinj.wallet.Wallet; +import co.rsk.peg.constants.BridgeConstants; +import co.rsk.peg.constants.BridgeMainNetConstants; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import co.rsk.peg.federation.*; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ConsensusRule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class MinimumPegValueTest { + private ActivationConfig.ForBlock activations; + private NetworkParameters networkParameters; + private BridgeConstants bridgeMainNetConstants; + + @BeforeEach + void setup() { + activations = mock(ActivationConfig.ForBlock.class); + bridgeMainNetConstants = BridgeMainNetConstants.getInstance(); + networkParameters = bridgeMainNetConstants.getBtcParams(); + } + + @ParameterizedTest() + @MethodSource("providePegMinimumParameters") + void checkProposedPegValues_whenOneInputofMinPeginValueAndOneOutputOfMinPegoutValue_shouldBuildTransactionSuccessfully( + Coin feePerKb, Coin minPeginValue, Coin minPegoutValue) { + // build federation + Federation fed = new P2shErpFederationBuilder().withNetworkParameters(networkParameters).build(); + // list of peg-out requests with the given minimum peg-out value + List entries = Arrays.asList( + createTestEntry(1000, minPegoutValue)); + // list of utxos that contains one utxo with the minimum peg-in value + List utxos = Arrays.asList( + new UTXO(getUTXOHash("utxo"), 0, minPeginValue, 0, false, fed.getP2SHScript())); + // the federation wallet, with flyover support + Wallet fedWallet = BridgeUtils.getFederationSpendWallet( + new Context(networkParameters), + fed, + utxos, + true, + mock(BridgeStorageProvider.class)); + // build release transaction builder for current fed + when(activations.isActive(ConsensusRule.RSKIP201)).thenReturn(true); + ReleaseTransactionBuilder releaseTransactionBuilder = new ReleaseTransactionBuilder( + networkParameters, + fedWallet, + fed.getAddress(), + feePerKb, + activations); + + // build batch peg-out transaction + ReleaseTransactionBuilder.BuildResult result = releaseTransactionBuilder.buildBatchedPegouts(entries); + + // the proposed feePerKb and peg values are compatible + assertEquals(ReleaseTransactionBuilder.Response.SUCCESS, result.getResponseCode()); + } + + @ParameterizedTest() + @MethodSource("providePegMinimumParameters") + void checkProposedPegValues_whenOneInputOfTenTimesMinPeginValueAndTenOutputsOfMinPegoutValue_shouldBuildTransactionSuccessfully( + Coin feePerKb, Coin minPeginValue, Coin minPegoutValue) { + // build federation + Federation fed = new P2shErpFederationBuilder().withNetworkParameters(networkParameters).build(); + // list of peg-out requests with the given minimum peg-out value + List entries = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + entries.add(createTestEntry(1000 + i, minPegoutValue)); + } + // list of utxos that contains one utxo with the minimum peg-in value + List utxos = Arrays.asList( + new UTXO(getUTXOHash("utxo"), 0, minPeginValue.times(10), 0, false, fed.getP2SHScript())); + // the federation wallet, with flyover support + Wallet fedWallet = BridgeUtils.getFederationSpendWallet( + new Context(networkParameters), + fed, + utxos, + true, + mock(BridgeStorageProvider.class)); + // build release transaction builder for current fed + when(activations.isActive(ConsensusRule.RSKIP201)).thenReturn(true); + ReleaseTransactionBuilder releaseTransactionBuilder = new ReleaseTransactionBuilder( + networkParameters, + fedWallet, + fed.getAddress(), + feePerKb, + activations); + + // build batch peg-out transaction + ReleaseTransactionBuilder.BuildResult result = releaseTransactionBuilder.buildBatchedPegouts(entries); + + // the proposed feePerKb and peg values are compatible + assertEquals(ReleaseTransactionBuilder.Response.SUCCESS, result.getResponseCode()); + } + + @ParameterizedTest() + @MethodSource("providePegMinimumParameters") + void checkProposedPegValues_whenTenInputOfMinPeginValueAndTenOutputsOfMinPegoutValue_shouldBuildTransactionSuccessfully( + Coin feePerKb, Coin minPeginValue, Coin minPegoutValue) { + // build federation + Federation fed = new P2shErpFederationBuilder().withNetworkParameters(networkParameters).build(); + // list of peg-out requests with the given minimum peg-out value + List entries = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + entries.add(createTestEntry(1000 + i, minPegoutValue)); + } + // list of utxos that contains one utxo with the minimum peg-in value + List utxos = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + utxos.add( + new UTXO(getUTXOHash("utxo" + i), 0, minPeginValue, 0, false, fed.getP2SHScript())); + } + // the federation wallet, with flyover support + Wallet fedWallet = BridgeUtils.getFederationSpendWallet( + new Context(networkParameters), + fed, + utxos, + true, + mock(BridgeStorageProvider.class)); + // build release transaction builder for current fed + when(activations.isActive(ConsensusRule.RSKIP201)).thenReturn(true); + ReleaseTransactionBuilder releaseTransactionBuilder = new ReleaseTransactionBuilder( + networkParameters, + fedWallet, + fed.getAddress(), + feePerKb, + activations); + + // build batch peg-out transaction + ReleaseTransactionBuilder.BuildResult result = releaseTransactionBuilder.buildBatchedPegouts(entries); + + // the proposed feePerKb and peg values are compatible + assertEquals(ReleaseTransactionBuilder.Response.SUCCESS, result.getResponseCode()); + } + + private static Stream providePegMinimumParameters() { + return Stream.of( + Arguments.of(Coin.valueOf(24_000L), Coin.valueOf(100_000L), Coin.valueOf(80_000L))); + } + + private Address getAddress(int pk) { + return BtcECKey.fromPrivate(BigInteger.valueOf(pk)) + .toAddress(NetworkParameters.fromID(NetworkParameters.ID_MAINNET)); + } + + private Sha256Hash getUTXOHash(String generator) { + return Sha256Hash.of(generator.getBytes(StandardCharsets.UTF_8)); + } + + private ReleaseRequestQueue.Entry createTestEntry(int addressPk, Coin amount) { + return new ReleaseRequestQueue.Entry(getAddress(addressPk), amount); + } +} diff --git a/rskj-core/src/test/java/co/rsk/peg/ReleaseTransactionBuilderTest.java b/rskj-core/src/test/java/co/rsk/peg/ReleaseTransactionBuilderTest.java index db14d050fc6..9b162326b33 100644 --- a/rskj-core/src/test/java/co/rsk/peg/ReleaseTransactionBuilderTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/ReleaseTransactionBuilderTest.java @@ -37,16 +37,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; + import co.rsk.peg.federation.*; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -874,159 +871,6 @@ void test_VerifyTXFeeIsSpentEquallyForBatchedPegouts_three_pegouts() { assertEquals(inputsValue.minus(totalPegoutAmount), changeOutput.getValue()); } - @ParameterizedTest() - @MethodSource("providePegoutMinimumParameters") - void buildBatchedPegouts_whenSpendableUXTO_shouldBuildTransactionSuccessfully(Coin feePerKb, Coin minPegoutValue) { - // build federation with 9 members - Integer[] memberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600, 700, 800, 900 }; - Federation fed = FederationTestUtils.getFederation(memberPKs); - // list of peg-out requests with the given minimum peg-out value - List entries = Arrays.asList( - createTestEntry(1000, minPegoutValue)); - // list of utxos that contains one utxo with a value of 1 BTC - List utxos = Arrays.asList( - new UTXO(mockUTXOHash("utxo"), 0, Coin.COIN, 0, false, fed.getP2SHScript())); - // the federation wallet, without flyover support - Wallet fedWallet = BridgeUtils.getFederationSpendWallet( - Context.getOrCreate(networkParameters), - fed, - utxos, - false, - mock(BridgeStorageProvider.class) - ); - // build release transaction builder for current fed - when(activations.isActive(ConsensusRule.RSKIP201)).thenReturn(true); - ReleaseTransactionBuilder releaseTransactionBuilder = new ReleaseTransactionBuilder( - networkParameters, - fedWallet, - fed.getAddress(), - feePerKb, - activations - ); - - // build batch peg-out transaction - ReleaseTransactionBuilder.BuildResult result = releaseTransactionBuilder.buildBatchedPegouts(entries); - - // the proposed feePerKb and minPegoutValue are compatible - assertEquals(ReleaseTransactionBuilder.Response.SUCCESS, result.getResponseCode()); - } - - @ParameterizedTest() - @MethodSource("providePegoutMinimumParameters") - void buildBatchedPegouts_whenUnspendableUXTOs_shouldBuildTransactionSuccessfully(Coin feePerKb, Coin minPegoutValue) { - // build federation with 9 members - Integer[] memberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600, 700, 800, 900 }; - Federation fed = FederationTestUtils.getFederation(memberPKs); - // list of peg-out requests with the given minimum peg-out value - List entries = Arrays.asList( - createTestEntry(1000, minPegoutValue)); - // this is a bit hacky, but this is to ensure the minPegoutValue chosen - // is not dust with the current wallet implementation. Either way this would be - // checked later on when building the transaction, but I think is good to have this explicitly - BtcTransaction tx = new BtcTransaction(networkParameters); - tx.addOutput(entries.get(0).getAmount(), entries.get(0).getDestination()); - assertFalse(tx.getOutput(0).isDust()); - // now we need to calculate the min value that an UTXO can use to be spent. This would be the least - // amount needed by the change ouput back to the federation address. This could also be the mininum - // pegin value, but since a change output is also an UTXO that the current wallet implementation can use - // then we will find and use that value. - Coin minSpendableUTXOValue = Coin.SATOSHI; - tx.clearOutputs(); - tx.addOutput(minSpendableUTXOValue, fed.getP2SHScript()); - while (tx.getOutput(0).isDust()) { - minSpendableUTXOValue = minSpendableUTXOValue.add(Coin.SATOSHI); - tx.clearOutputs(); - tx.addOutput(minSpendableUTXOValue, fed.getP2SHScript()); - } - // list of utxos that contains N mimUnspendableUTXO to cover the minPegoutValue - List utxos = new ArrayList<>(); - Coin[] neededUTXOsInCoin = minPegoutValue.divideAndRemainder(minSpendableUTXOValue.getValue()); - long neededUTXOs = neededUTXOsInCoin[0].getValue(); - if (!neededUTXOsInCoin[1].isZero()) { - neededUTXOs++; - } - for (int i = 0; i < neededUTXOs; i++) { - utxos.add( - new UTXO(mockUTXOHash("utxo " + i), 0, minSpendableUTXOValue, 0, false, fed.getP2SHScript())); - } - // the federation wallet, without flyover support - Wallet fedWallet = BridgeUtils.getFederationSpendWallet( - Context.getOrCreate(networkParameters), - fed, - utxos, - false, - mock(BridgeStorageProvider.class) - ); - // build release transaction builder for current fed - when(activations.isActive(ConsensusRule.RSKIP201)).thenReturn(true); - ReleaseTransactionBuilder releaseTransactionBuilder = new ReleaseTransactionBuilder( - networkParameters, - fedWallet, - fed.getAddress(), - feePerKb, - activations - ); - - // build batch peg-out transaction - ReleaseTransactionBuilder.BuildResult result = releaseTransactionBuilder.buildBatchedPegouts(entries); - - // the proposed feePerKb and minPegoutValue are compatible - assertEquals(ReleaseTransactionBuilder.Response.SUCCESS, result.getResponseCode()); - } - - @ParameterizedTest() - @MethodSource("providePegoutMinimumParameters") - void buildBatchedPegouts_whenTenUXTOs_shouldBuildTransactionSuccessfully(Coin feePerKb, Coin minPegoutValue) { - // build federation with 9 members - Integer[] memberPKs = new Integer[]{ 100, 200, 300, 400, 500, 600, 700, 800, 900 }; - Federation fed = FederationTestUtils.getFederation(memberPKs); - // list of peg-out requests with the given minimum peg-out value - List entries = Arrays.asList( - createTestEntry(1000, minPegoutValue)); - // this is a bit hacky, but this is to ensure the minPegoutValue chosen - // is not dust with the current wallet implementation. Either way this would be - // checked later on when building the transaction, but I think is good to have this explicitly - BtcTransaction tx = new BtcTransaction(networkParameters); - tx.addOutput(entries.get(0).getAmount(), entries.get(0).getDestination()); - assertFalse(tx.getOutput(0).isDust()); - // list of utxos that contains 10 utxos to cover the minPegoutValue - Coin utxoValue = minPegoutValue.div(10); - List utxos = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - utxos.add( - new UTXO(mockUTXOHash("utxo " + i), 0, utxoValue, 0, false, fed.getP2SHScript())); - } - // the federation wallet, without flyover support - Wallet fedWallet = BridgeUtils.getFederationSpendWallet( - Context.getOrCreate(networkParameters), - fed, - utxos, - false, - mock(BridgeStorageProvider.class) - ); - // build release transaction builder for current fed - when(activations.isActive(ConsensusRule.RSKIP201)).thenReturn(true); - ReleaseTransactionBuilder releaseTransactionBuilder = new ReleaseTransactionBuilder( - networkParameters, - fedWallet, - fed.getAddress(), - feePerKb, - activations - ); - - // build batch peg-out transaction - ReleaseTransactionBuilder.BuildResult result = releaseTransactionBuilder.buildBatchedPegouts(entries); - - // the proposed feePerKb and minPegoutValue are compatible - assertEquals(ReleaseTransactionBuilder.Response.SUCCESS, result.getResponseCode()); - } - - private static Stream providePegoutMinimumParameters() { - return Stream.of( - Arguments.of(Coin.valueOf(24_000L), Coin.valueOf(180_000L)) - ); - } - private void test_buildEmptyWalletTo_ok(boolean isRSKIPActive, int expectedTxVersion) throws InsufficientMoneyException, UTXOProviderException { when(activations.isActive(ConsensusRule.RSKIP201)).thenReturn(isRSKIPActive);