From aae352ca7200cec6cfbf39c5917a859ad3c2efa3 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Tue, 14 May 2024 01:42:41 -0400 Subject: [PATCH 01/20] Added RSKIP428 activation code --- .../org/ethereum/config/blockchain/upgrades/ConsensusRule.java | 1 + rskj-core/src/main/resources/expected.conf | 1 + rskj-core/src/main/resources/reference.conf | 1 + .../config/blockchain/upgrades/ActivationConfigTest.java | 1 + 4 files changed, 4 insertions(+) diff --git a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java index 9a9efecf657..f4a4375f5a3 100644 --- a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java +++ b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java @@ -93,6 +93,7 @@ public enum ConsensusRule { RSKIP412("rskip412"), // From EIP-3198 BASEFEE opcode RSKIP415("rskip415"), RSKIP417("rskip417"), + RSKIP428("rskip428"), ; private String configKey; diff --git a/rskj-core/src/main/resources/expected.conf b/rskj-core/src/main/resources/expected.conf index fbb975405ac..77f384ff6b7 100644 --- a/rskj-core/src/main/resources/expected.conf +++ b/rskj-core/src/main/resources/expected.conf @@ -93,6 +93,7 @@ blockchain = { rskip412 = rskip415 = rskip417 = + rskip428 = } } gc = { diff --git a/rskj-core/src/main/resources/reference.conf b/rskj-core/src/main/resources/reference.conf index ced28172cfe..2789bb1c7f1 100644 --- a/rskj-core/src/main/resources/reference.conf +++ b/rskj-core/src/main/resources/reference.conf @@ -79,6 +79,7 @@ blockchain = { rskip412 = arrowhead600 rskip415 = arrowhead600 rskip417 = arrowhead600 + rskip428 = lovell700 } } gc = { diff --git a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java index c33a2cad95f..00f4affaff2 100644 --- a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java +++ b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java @@ -120,6 +120,7 @@ class ActivationConfigTest { " rskip412: arrowhead600", " rskip415: arrowhead600", " rskip417: arrowhead600", + " rskip428: lovell700", "}" )); From be57b4dca37348d65a0197e2d232dedd4e69a299 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Tue, 14 May 2024 08:27:57 -0400 Subject: [PATCH 02/20] Create pegout_transaction_created event --- rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java b/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java index 1fab312b07a..4683068e18c 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java @@ -99,6 +99,12 @@ public enum BridgeEvents { new CallTransaction.Param(true, Fields.BTC_TX_HASH, SolidityType.getType(SolidityType.BYTES32)), new CallTransaction.Param(false, "pegoutCreationRskBlockNumber", SolidityType.getType(SolidityType.UINT256)) } + ), + PEGOUT_TRANSACTION_CREATED("pegout_transaction_created", + new CallTransaction.Param[]{ + new CallTransaction.Param(true, Fields.BTC_TX_HASH, SolidityType.getType(SolidityType.BYTES32)), + new CallTransaction.Param(false, Fields.UTXO_OUTPOINT_VALUES, SolidityType.getType(SolidityType.BYTES)) + } ); private String eventName; @@ -121,5 +127,6 @@ private static class Fields { private static final String BTC_TX_HASH = "btcTxHash"; private static final String RELEASE_RSK_TX_HASH = "releaseRskTxHash"; private static final String RELEASE_RSK_TX_HASHES = "releaseRskTxHashes"; + private static final String UTXO_OUTPOINT_VALUES = "utxoOutpointValues"; } } From d10c8101d1a8b7a0e63069394d036f313adc81a3 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Wed, 15 May 2024 01:39:27 -0400 Subject: [PATCH 03/20] - Create UtxoUtils class - Implement encode and decode outpoint value utils methods --- .../java/co/rsk/peg/bitcoin/UtxoUtils.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java new file mode 100644 index 00000000000..7e29cd02449 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java @@ -0,0 +1,40 @@ +package co.rsk.peg.bitcoin; + +import co.rsk.bitcoinj.core.Coin; +import co.rsk.bitcoinj.core.VarInt; +import com.google.common.primitives.Bytes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class UtxoUtils { + private UtxoUtils() {} + + public static List decodeOutpointValues(byte[] encodedOutpointValues){ + if (encodedOutpointValues == null || encodedOutpointValues.length == 0) { + return Collections.EMPTY_LIST; + } + + int offset = 0; + List outpointValues = new ArrayList<>(); + while (encodedOutpointValues.length > offset){ + VarInt entryEncoded = new VarInt(encodedOutpointValues, offset); + offset += entryEncoded.getSizeInBytes(); + outpointValues.add(Coin.valueOf(entryEncoded.value)); + } + return outpointValues; + } + + public static byte[] encodeOutpointValues(List outpointValues) { + if (outpointValues == null || outpointValues.isEmpty()) { + return new byte[]{}; + } + + List encodedOutpointValues = new ArrayList<>(); + for (Coin outpointValue : outpointValues) { + VarInt varIntOutpointValue = new VarInt(outpointValue.getValue()); + encodedOutpointValues.add(varIntOutpointValue.encode()); + } + return Bytes.concat(encodedOutpointValues.toArray(new byte[][]{{}})); + } +} From 01a975699c63a8e309c7611c8813591159844e24 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Thu, 16 May 2024 01:13:46 -0400 Subject: [PATCH 04/20] - Add logic to check if argument pass is valid for encode and decode util method - Add tests - Create a custom exception class for invalid outpoint value entries --- .../InvalidOutpointValueException.java | 12 + .../java/co/rsk/peg/bitcoin/UtxoUtils.java | 48 +++- .../co/rsk/peg/bitcoin/UtxoUtilsTest.java | 225 ++++++++++++++++++ 3 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 rskj-core/src/main/java/co/rsk/peg/bitcoin/InvalidOutpointValueException.java create mode 100644 rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/InvalidOutpointValueException.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/InvalidOutpointValueException.java new file mode 100644 index 00000000000..fef2f19f76d --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/InvalidOutpointValueException.java @@ -0,0 +1,12 @@ +package co.rsk.peg.bitcoin; + +public class InvalidOutpointValueException extends RuntimeException{ + + public InvalidOutpointValueException(String message) { + super(message); + } + + public InvalidOutpointValueException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java index 7e29cd02449..30b484dba95 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java @@ -6,25 +6,55 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.spongycastle.util.encoders.Hex; public class UtxoUtils { private UtxoUtils() {} + /** + * Decode a {@code byte[]} of encoded outpoint values. + * + * @param encodedOutpointValues + * @return {@code List} the list of outpoint values decoded in the same ordered they were before encoding. + * Or an {@code Collections.EMPTY_LIST} when {@code encodedOutpointValues} is {@code null} or {@code empty byte[]}. + */ public static List decodeOutpointValues(byte[] encodedOutpointValues){ if (encodedOutpointValues == null || encodedOutpointValues.length == 0) { - return Collections.EMPTY_LIST; + return Collections.emptyList(); } - int offset = 0; List outpointValues = new ArrayList<>(); + while (encodedOutpointValues.length > offset){ - VarInt entryEncoded = new VarInt(encodedOutpointValues, offset); - offset += entryEncoded.getSizeInBytes(); - outpointValues.add(Coin.valueOf(entryEncoded.value)); + VarInt valueAsVarInt; + try { + valueAsVarInt = new VarInt(encodedOutpointValues, offset); + } catch (Exception ex){ + throw new InvalidOutpointValueException( + String.format("Invalid value with invalid VarInt format: %s", + Hex.toHexString(encodedOutpointValues).toUpperCase() + ), + ex + ); + } + + offset += valueAsVarInt.getSizeInBytes(); + Coin outpointValue = Coin.valueOf(valueAsVarInt.value); + assertValidOutpointValue(outpointValue); + + outpointValues.add(outpointValue); } return outpointValues; + } + /** + * Encode a {@code List outpointValues) { if (outpointValues == null || outpointValues.isEmpty()) { return new byte[]{}; @@ -32,9 +62,17 @@ public static byte[] encodeOutpointValues(List outpointValues) { List encodedOutpointValues = new ArrayList<>(); for (Coin outpointValue : outpointValues) { + assertValidOutpointValue(outpointValue); VarInt varIntOutpointValue = new VarInt(outpointValue.getValue()); encodedOutpointValues.add(varIntOutpointValue.encode()); } return Bytes.concat(encodedOutpointValues.toArray(new byte[][]{{}})); } + + private static void assertValidOutpointValue(Coin outpointValue) { + if (outpointValue == null || outpointValue.isNegative()){ + throw new InvalidOutpointValueException(String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", + outpointValue)); + } + } } diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java new file mode 100644 index 00000000000..4d6cf0ea5da --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -0,0 +1,225 @@ +package co.rsk.peg.bitcoin; + +import static org.junit.jupiter.api.Assertions.*; + +import co.rsk.bitcoinj.core.Coin; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +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.spongycastle.util.encoders.Hex; + +class UtxoUtilsTest { + private final static Coin MAX_BTC = Coin.valueOf(21_000_000, 0); + + private static Stream validOutpointValues() { + List arguments = new ArrayList<>(); + + final byte[] encodedZeroValue = Hex.decode("00"); + List decodedZeroOutpointValue = Collections.singletonList(Coin.ZERO); + arguments.add(Arguments.of(encodedZeroValue, decodedZeroOutpointValue)); + + final byte[] encodedOneSatoshiValue = Hex.decode("01"); + List decodedOneSatoshiOutpointValue = Collections.singletonList(Coin.SATOSHI);; + arguments.add(Arguments.of(encodedOneSatoshiValue, decodedOneSatoshiOutpointValue)); + + final byte[] encodedManySatoshiValues = Hex.decode("01010101010101010101"); + List decodedManySatoshiOutpointValues = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + decodedManySatoshiOutpointValues.add(Coin.SATOSHI); + } + arguments.add(Arguments.of(encodedManySatoshiValues, decodedManySatoshiOutpointValues)); + + final byte[] encoded252Value = Hex.decode("FC"); + List decoded252OutpointValue = Collections.singletonList(Coin.valueOf(252));; + arguments.add(Arguments.of(encoded252Value, decoded252OutpointValue)); + + final byte[] encodedMultipleValues = Hex.decode("FCFCBBBBBBFD1934FE9145DC00"); + List decodedMultipleOutpointValues = new ArrayList<>(); + // 252 = FC in VarInt format + decodedMultipleOutpointValues.add(Coin.valueOf(252)); + decodedMultipleOutpointValues.add(Coin.valueOf(252)); + // 187 = BB in VarInt format + decodedMultipleOutpointValues.add(Coin.valueOf(187)); + decodedMultipleOutpointValues.add(Coin.valueOf(187)); + decodedMultipleOutpointValues.add(Coin.valueOf(187)); + // 13_337 = FE9145DC00 in VarInt format + decodedMultipleOutpointValues.add(Coin.valueOf(13_337)); + // 14_435_729 = FEDC4591 in VarInt format + decodedMultipleOutpointValues.add(Coin.valueOf(14_435_729)); + arguments.add(Arguments.of(encodedMultipleValues, decodedMultipleOutpointValues)); + + final byte[] encodedMaxBtcValue = Hex.decode("FF0040075AF0750700"); + List decodedMaxBtcOutpointValue = Collections.singletonList(MAX_BTC); + arguments.add(Arguments.of(encodedMaxBtcValue, decodedMaxBtcOutpointValue)); + + final byte[] encodedManyMaxBtcValue = Hex.decode("FF0040075AF0750700FF0040075AF0750700FF0040075AF0750700"); + List decodedManyMaxBtcOutpointValues = new ArrayList<>(); + decodedManyMaxBtcOutpointValues.add(MAX_BTC); + decodedManyMaxBtcOutpointValues.add(MAX_BTC); + decodedManyMaxBtcOutpointValues.add(MAX_BTC); + arguments.add(Arguments.of(encodedManyMaxBtcValue, decodedManyMaxBtcOutpointValues)); + + final byte[] encodedSurpassMaxBtcOutpointValue = Hex.decode("FF0140075AF0750700"); + List decodedSurpassMaxBtcOutpointValue = Collections.singletonList(MAX_BTC.add(Coin.SATOSHI)); + arguments.add(Arguments.of(encodedSurpassMaxBtcOutpointValue, decodedSurpassMaxBtcOutpointValue)); + + final byte[] encodedMaxLongOutpointValue = Hex.decode("FFFFFFFFFFFFFFFF7F"); + List decodedMaxLongOutpointValue = Collections.singletonList(Coin.valueOf(Long.MAX_VALUE)); + arguments.add(Arguments.of(encodedMaxLongOutpointValue, decodedMaxLongOutpointValue)); + + String bigOutpointValuesAsHex = Stream.iterate("FCFCBBBBBBFD1934FE9145DC00", s -> s) + .limit(1000).collect(Collectors.joining()); + final byte[] bigOutpointValueArray = Hex.decode(bigOutpointValuesAsHex); + List decodedBigOutpointValueList = new ArrayList<>(); + for (int i = 0; i < 1000; i++) { + // 252 = FC in VarInt format + decodedBigOutpointValueList.add(Coin.valueOf(252)); + decodedBigOutpointValueList.add(Coin.valueOf(252)); + // 187 = BB in VarInt format + decodedBigOutpointValueList.add(Coin.valueOf(187)); + decodedBigOutpointValueList.add(Coin.valueOf(187)); + decodedBigOutpointValueList.add(Coin.valueOf(187)); + // 13_337 = FE9145DC00 in VarInt format + decodedBigOutpointValueList.add(Coin.valueOf(13_337)); + // 14_435_729 = FEDC4591 in VarInt format + decodedBigOutpointValueList.add(Coin.valueOf(14_435_729)); + } + arguments.add(Arguments.of(bigOutpointValueArray, decodedBigOutpointValueList)); + + final byte[] emptyArray = new byte[]{}; + List emptyList = Collections.EMPTY_LIST; + arguments.add(Arguments.of(emptyArray, emptyList)); + + return arguments.stream(); + } + + @ParameterizedTest + @MethodSource("validOutpointValues") + void decodeOutpointValues(byte[] encodedValues, List expectedDecodedValues) { + // act + List decodeOutpointValues = UtxoUtils.decodeOutpointValues(encodedValues); + + // assert + assertArrayEquals(expectedDecodedValues.toArray(), decodeOutpointValues.toArray()); + } + + @Test + void decodeOutpointValues_nullEncodedValues_shouldReturnEmptyList() { + // act + List decodeOutpointValues = UtxoUtils.decodeOutpointValues(null); + + // assert + List expectedDecodedValues = Collections.EMPTY_LIST; + assertArrayEquals(expectedDecodedValues.toArray(), decodeOutpointValues.toArray()); + } + + @ParameterizedTest + @MethodSource("validOutpointValues") + void encodeOutpointValues(byte[] expectedEncodedOutpointValues, List outpointValues) { + // act + byte[] encodeOutpointValues = UtxoUtils.encodeOutpointValues(outpointValues); + + // assert + assertArrayEquals(expectedEncodedOutpointValues, encodeOutpointValues); + } + + @Test + void decodeOutpointValues_nullOutpointValues_shouldReturnEmptyArray() { + // act + List outpointValues = UtxoUtils.decodeOutpointValues(null); + + // assert + List expectedDecodedValues = Collections.EMPTY_LIST; + assertArrayEquals(expectedDecodedValues.toArray(), outpointValues.toArray()); + } + + private static Stream invalidOutpointValues() { + List arguments = new ArrayList<>(); + + List negativeOutpointValues = Arrays.asList(Coin.valueOf(-10), Coin.valueOf(-1000), Coin.valueOf(-100)); + String expectedMessageForNegativeOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -10); + arguments.add(Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); + + List negativeAndPositiveOutpointValues = Arrays.asList(Coin.valueOf(200), Coin.valueOf(-100), Coin.valueOf(300)); + String expectedMessageForNegativeAndPositiveOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -100); + arguments.add(Arguments.of(negativeAndPositiveOutpointValues, expectedMessageForNegativeAndPositiveOutpointValues)); + + return arguments.stream(); + } + + @ParameterizedTest + @MethodSource("invalidOutpointValues") + void encodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueException(List outpointValues, String expectedMessage) { + // act + InvalidOutpointValueException invalidOutpointValueException = assertThrows( + InvalidOutpointValueException.class, + () -> UtxoUtils.encodeOutpointValues(outpointValues)); + String actualMessage = invalidOutpointValueException.getMessage(); + + // assert + assertEquals(expectedMessage, actualMessage); + } + + private static Stream invalidEncodedOutpointValues() { + List arguments = new ArrayList<>(); + + // {-100, -200, -300} + final byte[] negativeOutpointValues = Hex.decode("FF9CFFFFFFFFFFFFFFFF38FFFFFFFFFFFFFFFFD4FEFFFFFFFFFFFF"); + String expectedMessageForNegativeOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -100); + arguments.add(Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); + + // {100, 200, 300, -400} + final byte[] negativeAndPositiveOutpointValues = Hex.decode("64C8FD2C01FF70FEFFFFFFFFFFFF"); + String expectedMessageForNegativeAndPositiveOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -400); + arguments.add(Arguments.of(negativeAndPositiveOutpointValues, expectedMessageForNegativeAndPositiveOutpointValues)); + + return arguments.stream(); + } + + @ParameterizedTest + @MethodSource("invalidEncodedOutpointValues") + void decodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueException(byte[] encodedOutpointValues, String expectedMessage) { + // act + InvalidOutpointValueException invalidOutpointValueException = assertThrows( + InvalidOutpointValueException.class, + () -> UtxoUtils.decodeOutpointValues(encodedOutpointValues)); + String actualMessage = invalidOutpointValueException.getMessage(); + + // assert + assertEquals(expectedMessage, actualMessage); + } + + private static Stream invalidVarIntFormatOutpointValues() { + List arguments = new ArrayList<>(); + + final byte[] invalidOutpointValues = Hex.decode("FC9145DC00FAFF00FE"); + String expectedMessageForInvalidOutpointValues = String.format("Invalid value with invalid VarInt format: %s", "FC9145DC00FAFF00FE"); + arguments.add(Arguments.of(invalidOutpointValues, expectedMessageForInvalidOutpointValues)); + + final byte[] anotherInvalidOutpointValues = Hex.decode("FB8267DC00FCFF00FE"); + String expectedMessageForAnotherInvalidOutpointValue = String.format("Invalid value with invalid VarInt format: %s", "FB8267DC00FCFF00FE"); + arguments.add(Arguments.of(anotherInvalidOutpointValues, expectedMessageForAnotherInvalidOutpointValue)); + + return arguments.stream(); + } + + @ParameterizedTest + @MethodSource("invalidVarIntFormatOutpointValues") + void decodeOutpointValues_invalidVarIntFormatOutpointValues_shouldThrowInvalidOutpointValueException(byte[] encodedOutpointValues, String expectedMessage) { + // act + InvalidOutpointValueException invalidOutpointValueException = assertThrows( + InvalidOutpointValueException.class, + () -> UtxoUtils.decodeOutpointValues(encodedOutpointValues)); + String actualMessage = invalidOutpointValueException.getMessage(); + + // assert + assertEquals(expectedMessage, actualMessage); + } +} \ No newline at end of file From f90a0658c131234da425e293fefe74b07c6e60bf Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Thu, 16 May 2024 02:11:55 -0400 Subject: [PATCH 05/20] Fix sonar issues --- .../co/rsk/peg/bitcoin/UtxoUtilsTest.java | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java index 4d6cf0ea5da..9dbca223634 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -170,35 +170,16 @@ void encodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueE private static Stream invalidEncodedOutpointValues() { List arguments = new ArrayList<>(); - // {-100, -200, -300} + // -100, -200, -300 final byte[] negativeOutpointValues = Hex.decode("FF9CFFFFFFFFFFFFFFFF38FFFFFFFFFFFFFFFFD4FEFFFFFFFFFFFF"); String expectedMessageForNegativeOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -100); arguments.add(Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); - // {100, 200, 300, -400} + // 100, 200, 300, -400 final byte[] negativeAndPositiveOutpointValues = Hex.decode("64C8FD2C01FF70FEFFFFFFFFFFFF"); String expectedMessageForNegativeAndPositiveOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -400); arguments.add(Arguments.of(negativeAndPositiveOutpointValues, expectedMessageForNegativeAndPositiveOutpointValues)); - return arguments.stream(); - } - - @ParameterizedTest - @MethodSource("invalidEncodedOutpointValues") - void decodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueException(byte[] encodedOutpointValues, String expectedMessage) { - // act - InvalidOutpointValueException invalidOutpointValueException = assertThrows( - InvalidOutpointValueException.class, - () -> UtxoUtils.decodeOutpointValues(encodedOutpointValues)); - String actualMessage = invalidOutpointValueException.getMessage(); - - // assert - assertEquals(expectedMessage, actualMessage); - } - - private static Stream invalidVarIntFormatOutpointValues() { - List arguments = new ArrayList<>(); - final byte[] invalidOutpointValues = Hex.decode("FC9145DC00FAFF00FE"); String expectedMessageForInvalidOutpointValues = String.format("Invalid value with invalid VarInt format: %s", "FC9145DC00FAFF00FE"); arguments.add(Arguments.of(invalidOutpointValues, expectedMessageForInvalidOutpointValues)); @@ -211,8 +192,8 @@ private static Stream invalidVarIntFormatOutpointValues() { } @ParameterizedTest - @MethodSource("invalidVarIntFormatOutpointValues") - void decodeOutpointValues_invalidVarIntFormatOutpointValues_shouldThrowInvalidOutpointValueException(byte[] encodedOutpointValues, String expectedMessage) { + @MethodSource("invalidEncodedOutpointValues") + void decodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueException(byte[] encodedOutpointValues, String expectedMessage) { // act InvalidOutpointValueException invalidOutpointValueException = assertThrows( InvalidOutpointValueException.class, @@ -222,4 +203,4 @@ void decodeOutpointValues_invalidVarIntFormatOutpointValues_shouldThrowInvalidOu // assert assertEquals(expectedMessage, actualMessage); } -} \ No newline at end of file +} From d8cddfe352ce4958a7b55086fe46a69144ebf23b Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Thu, 16 May 2024 08:53:44 -0400 Subject: [PATCH 06/20] Apply format to all the changed files. --- .../InvalidOutpointValueException.java | 2 +- .../java/co/rsk/peg/bitcoin/UtxoUtils.java | 27 ++-- .../co/rsk/peg/bitcoin/UtxoUtilsTest.java | 119 +++++++++++------- 3 files changed, 89 insertions(+), 59 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/InvalidOutpointValueException.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/InvalidOutpointValueException.java index fef2f19f76d..1047599ef7b 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/InvalidOutpointValueException.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/InvalidOutpointValueException.java @@ -1,6 +1,6 @@ package co.rsk.peg.bitcoin; -public class InvalidOutpointValueException extends RuntimeException{ +public class InvalidOutpointValueException extends RuntimeException { public InvalidOutpointValueException(String message) { super(message); diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java index 30b484dba95..292523f35f4 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java @@ -9,27 +9,30 @@ import org.spongycastle.util.encoders.Hex; public class UtxoUtils { - private UtxoUtils() {} + + private UtxoUtils() { + } /** * Decode a {@code byte[]} of encoded outpoint values. * * @param encodedOutpointValues - * @return {@code List} the list of outpoint values decoded in the same ordered they were before encoding. - * Or an {@code Collections.EMPTY_LIST} when {@code encodedOutpointValues} is {@code null} or {@code empty byte[]}. + * @return {@code List} the list of outpoint values decoded in the same ordered they were + * before encoding. Or an {@code Collections.EMPTY_LIST} when {@code encodedOutpointValues} is + * {@code null} or {@code empty byte[]}. */ - public static List decodeOutpointValues(byte[] encodedOutpointValues){ + public static List decodeOutpointValues(byte[] encodedOutpointValues) { if (encodedOutpointValues == null || encodedOutpointValues.length == 0) { return Collections.emptyList(); } int offset = 0; List outpointValues = new ArrayList<>(); - while (encodedOutpointValues.length > offset){ + while (encodedOutpointValues.length > offset) { VarInt valueAsVarInt; try { valueAsVarInt = new VarInt(encodedOutpointValues, offset); - } catch (Exception ex){ + } catch (Exception ex) { throw new InvalidOutpointValueException( String.format("Invalid value with invalid VarInt format: %s", Hex.toHexString(encodedOutpointValues).toUpperCase() @@ -52,8 +55,9 @@ public static List decodeOutpointValues(byte[] encodedOutpointValues){ * Encode a {@code List outpointValues) { if (outpointValues == null || outpointValues.isEmpty()) { @@ -66,12 +70,13 @@ public static byte[] encodeOutpointValues(List outpointValues) { VarInt varIntOutpointValue = new VarInt(outpointValue.getValue()); encodedOutpointValues.add(varIntOutpointValue.encode()); } - return Bytes.concat(encodedOutpointValues.toArray(new byte[][]{{}})); + return Bytes.concat(encodedOutpointValues.toArray(new byte[][]{})); } private static void assertValidOutpointValue(Coin outpointValue) { - if (outpointValue == null || outpointValue.isNegative()){ - throw new InvalidOutpointValueException(String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", + if (outpointValue == null || outpointValue.isNegative()) { + throw new InvalidOutpointValueException(String.format( + "Invalid outpoint value: %s. Negative and null values are not allowed.", outpointValue)); } } diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java index 9dbca223634..d6e4b9315aa 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -1,6 +1,8 @@ package co.rsk.peg.bitcoin; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import co.rsk.bitcoinj.core.Coin; import java.util.ArrayList; @@ -16,6 +18,7 @@ import org.spongycastle.util.encoders.Hex; class UtxoUtilsTest { + private final static Coin MAX_BTC = Coin.valueOf(21_000_000, 0); private static Stream validOutpointValues() { @@ -26,7 +29,8 @@ private static Stream validOutpointValues() { arguments.add(Arguments.of(encodedZeroValue, decodedZeroOutpointValue)); final byte[] encodedOneSatoshiValue = Hex.decode("01"); - List decodedOneSatoshiOutpointValue = Collections.singletonList(Coin.SATOSHI);; + List decodedOneSatoshiOutpointValue = Collections.singletonList(Coin.SATOSHI); + ; arguments.add(Arguments.of(encodedOneSatoshiValue, decodedOneSatoshiOutpointValue)); final byte[] encodedManySatoshiValues = Hex.decode("01010101010101010101"); @@ -37,7 +41,8 @@ private static Stream validOutpointValues() { arguments.add(Arguments.of(encodedManySatoshiValues, decodedManySatoshiOutpointValues)); final byte[] encoded252Value = Hex.decode("FC"); - List decoded252OutpointValue = Collections.singletonList(Coin.valueOf(252));; + List decoded252OutpointValue = Collections.singletonList(Coin.valueOf(252)); + ; arguments.add(Arguments.of(encoded252Value, decoded252OutpointValue)); final byte[] encodedMultipleValues = Hex.decode("FCFCBBBBBBFD1934FE9145DC00"); @@ -59,7 +64,8 @@ private static Stream validOutpointValues() { List decodedMaxBtcOutpointValue = Collections.singletonList(MAX_BTC); arguments.add(Arguments.of(encodedMaxBtcValue, decodedMaxBtcOutpointValue)); - final byte[] encodedManyMaxBtcValue = Hex.decode("FF0040075AF0750700FF0040075AF0750700FF0040075AF0750700"); + final byte[] encodedManyMaxBtcValue = Hex.decode( + "FF0040075AF0750700FF0040075AF0750700FF0040075AF0750700"); List decodedManyMaxBtcOutpointValues = new ArrayList<>(); decodedManyMaxBtcOutpointValues.add(MAX_BTC); decodedManyMaxBtcOutpointValues.add(MAX_BTC); @@ -67,11 +73,14 @@ private static Stream validOutpointValues() { arguments.add(Arguments.of(encodedManyMaxBtcValue, decodedManyMaxBtcOutpointValues)); final byte[] encodedSurpassMaxBtcOutpointValue = Hex.decode("FF0140075AF0750700"); - List decodedSurpassMaxBtcOutpointValue = Collections.singletonList(MAX_BTC.add(Coin.SATOSHI)); - arguments.add(Arguments.of(encodedSurpassMaxBtcOutpointValue, decodedSurpassMaxBtcOutpointValue)); + List decodedSurpassMaxBtcOutpointValue = Collections.singletonList( + MAX_BTC.add(Coin.SATOSHI)); + arguments.add( + Arguments.of(encodedSurpassMaxBtcOutpointValue, decodedSurpassMaxBtcOutpointValue)); final byte[] encodedMaxLongOutpointValue = Hex.decode("FFFFFFFFFFFFFFFF7F"); - List decodedMaxLongOutpointValue = Collections.singletonList(Coin.valueOf(Long.MAX_VALUE)); + List decodedMaxLongOutpointValue = Collections.singletonList( + Coin.valueOf(Long.MAX_VALUE)); arguments.add(Arguments.of(encodedMaxLongOutpointValue, decodedMaxLongOutpointValue)); String bigOutpointValuesAsHex = Stream.iterate("FCFCBBBBBBFD1934FE9145DC00", s -> s) @@ -100,6 +109,58 @@ private static Stream validOutpointValues() { return arguments.stream(); } + private static Stream invalidOutpointValues() { + List arguments = new ArrayList<>(); + + List negativeOutpointValues = Arrays.asList(Coin.valueOf(-10), Coin.valueOf(-1000), + Coin.valueOf(-100)); + String expectedMessageForNegativeOutpointValues = String.format( + "Invalid outpoint value: %s. Negative and null values are not allowed.", -10); + arguments.add( + Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); + + List negativeAndPositiveOutpointValues = Arrays.asList(Coin.valueOf(200), + Coin.valueOf(-100), Coin.valueOf(300)); + String expectedMessageForNegativeAndPositiveOutpointValues = String.format( + "Invalid outpoint value: %s. Negative and null values are not allowed.", -100); + arguments.add(Arguments.of(negativeAndPositiveOutpointValues, + expectedMessageForNegativeAndPositiveOutpointValues)); + + return arguments.stream(); + } + + private static Stream invalidEncodedOutpointValues() { + List arguments = new ArrayList<>(); + + // -100, -200, -300 + final byte[] negativeOutpointValues = Hex.decode( + "FF9CFFFFFFFFFFFFFFFF38FFFFFFFFFFFFFFFFD4FEFFFFFFFFFFFF"); + String expectedMessageForNegativeOutpointValues = String.format( + "Invalid outpoint value: %s. Negative and null values are not allowed.", -100); + arguments.add( + Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); + + // 100, 200, 300, -400 + final byte[] negativeAndPositiveOutpointValues = Hex.decode("64C8FD2C01FF70FEFFFFFFFFFFFF"); + String expectedMessageForNegativeAndPositiveOutpointValues = String.format( + "Invalid outpoint value: %s. Negative and null values are not allowed.", -400); + arguments.add(Arguments.of(negativeAndPositiveOutpointValues, + expectedMessageForNegativeAndPositiveOutpointValues)); + + final byte[] invalidOutpointValues = Hex.decode("FC9145DC00FAFF00FE"); + String expectedMessageForInvalidOutpointValues = String.format( + "Invalid value with invalid VarInt format: %s", "FC9145DC00FAFF00FE"); + arguments.add(Arguments.of(invalidOutpointValues, expectedMessageForInvalidOutpointValues)); + + final byte[] anotherInvalidOutpointValues = Hex.decode("FB8267DC00FCFF00FE"); + String expectedMessageForAnotherInvalidOutpointValue = String.format( + "Invalid value with invalid VarInt format: %s", "FB8267DC00FCFF00FE"); + arguments.add(Arguments.of(anotherInvalidOutpointValues, + expectedMessageForAnotherInvalidOutpointValue)); + + return arguments.stream(); + } + @ParameterizedTest @MethodSource("validOutpointValues") void decodeOutpointValues(byte[] encodedValues, List expectedDecodedValues) { @@ -140,23 +201,10 @@ void decodeOutpointValues_nullOutpointValues_shouldReturnEmptyArray() { assertArrayEquals(expectedDecodedValues.toArray(), outpointValues.toArray()); } - private static Stream invalidOutpointValues() { - List arguments = new ArrayList<>(); - - List negativeOutpointValues = Arrays.asList(Coin.valueOf(-10), Coin.valueOf(-1000), Coin.valueOf(-100)); - String expectedMessageForNegativeOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -10); - arguments.add(Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); - - List negativeAndPositiveOutpointValues = Arrays.asList(Coin.valueOf(200), Coin.valueOf(-100), Coin.valueOf(300)); - String expectedMessageForNegativeAndPositiveOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -100); - arguments.add(Arguments.of(negativeAndPositiveOutpointValues, expectedMessageForNegativeAndPositiveOutpointValues)); - - return arguments.stream(); - } - @ParameterizedTest @MethodSource("invalidOutpointValues") - void encodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueException(List outpointValues, String expectedMessage) { + void encodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueException( + List outpointValues, String expectedMessage) { // act InvalidOutpointValueException invalidOutpointValueException = assertThrows( InvalidOutpointValueException.class, @@ -167,33 +215,10 @@ void encodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueE assertEquals(expectedMessage, actualMessage); } - private static Stream invalidEncodedOutpointValues() { - List arguments = new ArrayList<>(); - - // -100, -200, -300 - final byte[] negativeOutpointValues = Hex.decode("FF9CFFFFFFFFFFFFFFFF38FFFFFFFFFFFFFFFFD4FEFFFFFFFFFFFF"); - String expectedMessageForNegativeOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -100); - arguments.add(Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); - - // 100, 200, 300, -400 - final byte[] negativeAndPositiveOutpointValues = Hex.decode("64C8FD2C01FF70FEFFFFFFFFFFFF"); - String expectedMessageForNegativeAndPositiveOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -400); - arguments.add(Arguments.of(negativeAndPositiveOutpointValues, expectedMessageForNegativeAndPositiveOutpointValues)); - - final byte[] invalidOutpointValues = Hex.decode("FC9145DC00FAFF00FE"); - String expectedMessageForInvalidOutpointValues = String.format("Invalid value with invalid VarInt format: %s", "FC9145DC00FAFF00FE"); - arguments.add(Arguments.of(invalidOutpointValues, expectedMessageForInvalidOutpointValues)); - - final byte[] anotherInvalidOutpointValues = Hex.decode("FB8267DC00FCFF00FE"); - String expectedMessageForAnotherInvalidOutpointValue = String.format("Invalid value with invalid VarInt format: %s", "FB8267DC00FCFF00FE"); - arguments.add(Arguments.of(anotherInvalidOutpointValues, expectedMessageForAnotherInvalidOutpointValue)); - - return arguments.stream(); - } - @ParameterizedTest @MethodSource("invalidEncodedOutpointValues") - void decodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueException(byte[] encodedOutpointValues, String expectedMessage) { + void decodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueException( + byte[] encodedOutpointValues, String expectedMessage) { // act InvalidOutpointValueException invalidOutpointValueException = assertThrows( InvalidOutpointValueException.class, From 420202eac4b50d7a70e9d8094e1e9794a67d8e05 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Thu, 16 May 2024 16:16:26 -0400 Subject: [PATCH 07/20] - Rearrange provider arguments method - Get rid of use of `Bytes` dependency class --- .../java/co/rsk/peg/bitcoin/UtxoUtils.java | 21 ++- .../co/rsk/peg/bitcoin/UtxoUtilsTest.java | 138 ++++++++---------- 2 files changed, 76 insertions(+), 83 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java index 292523f35f4..c004726551a 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java @@ -2,13 +2,15 @@ import co.rsk.bitcoinj.core.Coin; import co.rsk.bitcoinj.core.VarInt; -import com.google.common.primitives.Bytes; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.spongycastle.util.encoders.Hex; -public class UtxoUtils { +public final class UtxoUtils { private UtxoUtils() { } @@ -64,13 +66,22 @@ public static byte[] encodeOutpointValues(List outpointValues) { return new byte[]{}; } - List encodedOutpointValues = new ArrayList<>(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (Coin outpointValue : outpointValues) { assertValidOutpointValue(outpointValue); VarInt varIntOutpointValue = new VarInt(outpointValue.getValue()); - encodedOutpointValues.add(varIntOutpointValue.encode()); + try { + outputStream.write(varIntOutpointValue.encode()); + } catch (IOException ex) { + throw new InvalidOutpointValueException( + String.format("I/O exception for value: %s", + outpointValue + ), + ex + ); + } } - return Bytes.concat(encodedOutpointValues.toArray(new byte[][]{})); + return outputStream.toByteArray(); } private static void assertValidOutpointValue(Coin outpointValue) { diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java index d6e4b9315aa..668e36cea90 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -5,10 +5,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import co.rsk.bitcoinj.core.Coin; +import co.rsk.config.BridgeMainNetConstants; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -19,92 +22,71 @@ class UtxoUtilsTest { - private final static Coin MAX_BTC = Coin.valueOf(21_000_000, 0); + private final static Coin MAX_BTC = BridgeMainNetConstants.getInstance().getMaxRbtc(); + + private static List coinListOf(long ... valuesInSatoshis) { + return Arrays.stream(valuesInSatoshis) + .mapToObj(Coin::valueOf) + .collect(Collectors.toList()); + } private static Stream validOutpointValues() { List arguments = new ArrayList<>(); - final byte[] encodedZeroValue = Hex.decode("00"); - List decodedZeroOutpointValue = Collections.singletonList(Coin.ZERO); - arguments.add(Arguments.of(encodedZeroValue, decodedZeroOutpointValue)); - - final byte[] encodedOneSatoshiValue = Hex.decode("01"); - List decodedOneSatoshiOutpointValue = Collections.singletonList(Coin.SATOSHI); - ; - arguments.add(Arguments.of(encodedOneSatoshiValue, decodedOneSatoshiOutpointValue)); - - final byte[] encodedManySatoshiValues = Hex.decode("01010101010101010101"); - List decodedManySatoshiOutpointValues = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - decodedManySatoshiOutpointValues.add(Coin.SATOSHI); - } - arguments.add(Arguments.of(encodedManySatoshiValues, decodedManySatoshiOutpointValues)); - - final byte[] encoded252Value = Hex.decode("FC"); - List decoded252OutpointValue = Collections.singletonList(Coin.valueOf(252)); - ; - arguments.add(Arguments.of(encoded252Value, decoded252OutpointValue)); - - final byte[] encodedMultipleValues = Hex.decode("FCFCBBBBBBFD1934FE9145DC00"); - List decodedMultipleOutpointValues = new ArrayList<>(); - // 252 = FC in VarInt format - decodedMultipleOutpointValues.add(Coin.valueOf(252)); - decodedMultipleOutpointValues.add(Coin.valueOf(252)); - // 187 = BB in VarInt format - decodedMultipleOutpointValues.add(Coin.valueOf(187)); - decodedMultipleOutpointValues.add(Coin.valueOf(187)); - decodedMultipleOutpointValues.add(Coin.valueOf(187)); - // 13_337 = FE9145DC00 in VarInt format - decodedMultipleOutpointValues.add(Coin.valueOf(13_337)); - // 14_435_729 = FEDC4591 in VarInt format - decodedMultipleOutpointValues.add(Coin.valueOf(14_435_729)); - arguments.add(Arguments.of(encodedMultipleValues, decodedMultipleOutpointValues)); - - final byte[] encodedMaxBtcValue = Hex.decode("FF0040075AF0750700"); - List decodedMaxBtcOutpointValue = Collections.singletonList(MAX_BTC); - arguments.add(Arguments.of(encodedMaxBtcValue, decodedMaxBtcOutpointValue)); - - final byte[] encodedManyMaxBtcValue = Hex.decode( - "FF0040075AF0750700FF0040075AF0750700FF0040075AF0750700"); - List decodedManyMaxBtcOutpointValues = new ArrayList<>(); - decodedManyMaxBtcOutpointValues.add(MAX_BTC); - decodedManyMaxBtcOutpointValues.add(MAX_BTC); - decodedManyMaxBtcOutpointValues.add(MAX_BTC); - arguments.add(Arguments.of(encodedManyMaxBtcValue, decodedManyMaxBtcOutpointValues)); - - final byte[] encodedSurpassMaxBtcOutpointValue = Hex.decode("FF0140075AF0750700"); - List decodedSurpassMaxBtcOutpointValue = Collections.singletonList( - MAX_BTC.add(Coin.SATOSHI)); - arguments.add( - Arguments.of(encodedSurpassMaxBtcOutpointValue, decodedSurpassMaxBtcOutpointValue)); - - final byte[] encodedMaxLongOutpointValue = Hex.decode("FFFFFFFFFFFFFFFF7F"); - List decodedMaxLongOutpointValue = Collections.singletonList( - Coin.valueOf(Long.MAX_VALUE)); - arguments.add(Arguments.of(encodedMaxLongOutpointValue, decodedMaxLongOutpointValue)); - - String bigOutpointValuesAsHex = Stream.iterate("FCFCBBBBBBFD1934FE9145DC00", s -> s) - .limit(1000).collect(Collectors.joining()); - final byte[] bigOutpointValueArray = Hex.decode(bigOutpointValuesAsHex); - List decodedBigOutpointValueList = new ArrayList<>(); - for (int i = 0; i < 1000; i++) { + arguments.add(Arguments.of(Hex.decode("00"), Collections.singletonList(Coin.ZERO))); + + arguments.add(Arguments.of(Hex.decode("01"), Collections.singletonList(Coin.SATOSHI))); + + arguments.add(Arguments.of(Hex.decode("01010101010101010101"), Stream.generate(() -> Coin.SATOSHI).limit(10).collect(Collectors.toList()))); + + arguments.add(Arguments.of(Hex.decode("FC"), coinListOf(252))); + + arguments.add(Arguments.of(Hex.decode("FCFCBBBBBBFD1934FE9145DC00"), + coinListOf( + // 252 = FC in VarInt format + 252, + 252, + // 187 = BB in VarInt format + 187, + 187, + 187, + // 13_337 = FE9145DC00 in VarInt format + 13_337, + // 14_435_729 = FEDC4591 in VarInt format + 14_435_729 + )) + ); + + arguments.add(Arguments.of(Hex.decode("FF0040075AF0750700"), Collections.singletonList(MAX_BTC))); + + arguments.add(Arguments.of(Hex.decode("FF0040075AF0750700FF0040075AF0750700FF0040075AF0750700"), Arrays.asList(MAX_BTC, MAX_BTC, MAX_BTC))); + + arguments.add(Arguments.of(Hex.decode("FF0140075AF0750700"), Collections.singletonList(MAX_BTC.add(Coin.SATOSHI)))); + + arguments.add(Arguments.of(Hex.decode("FFFFFFFFFFFFFFFF7F"), coinListOf(Long.MAX_VALUE))); + + final byte[] bigOutpointValueArray = Hex.decode(Stream.iterate("FCFCBBBBBBFD1934FE9145DC00", + UnaryOperator.identity()) + .limit(1000).collect(Collectors.joining())); + List bigListOfOutpointValues = Stream.generate(() -> coinListOf( // 252 = FC in VarInt format - decodedBigOutpointValueList.add(Coin.valueOf(252)); - decodedBigOutpointValueList.add(Coin.valueOf(252)); + 252, + 252, // 187 = BB in VarInt format - decodedBigOutpointValueList.add(Coin.valueOf(187)); - decodedBigOutpointValueList.add(Coin.valueOf(187)); - decodedBigOutpointValueList.add(Coin.valueOf(187)); + 187, + 187, + 187, // 13_337 = FE9145DC00 in VarInt format - decodedBigOutpointValueList.add(Coin.valueOf(13_337)); + 13_337, // 14_435_729 = FEDC4591 in VarInt format - decodedBigOutpointValueList.add(Coin.valueOf(14_435_729)); - } - arguments.add(Arguments.of(bigOutpointValueArray, decodedBigOutpointValueList)); - - final byte[] emptyArray = new byte[]{}; - List emptyList = Collections.EMPTY_LIST; - arguments.add(Arguments.of(emptyArray, emptyList)); + 14_435_729) + ).limit(1000).flatMap(Collection::stream).collect(Collectors.toList()); + arguments.add(Arguments.of( + bigOutpointValueArray, + bigListOfOutpointValues + )); + + arguments.add(Arguments.of(new byte[]{}, Collections.EMPTY_LIST)); return arguments.stream(); } From 8578953971d6417dd2878ee563c062eeebd3dc59 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Fri, 17 May 2024 12:00:35 -0400 Subject: [PATCH 08/20] - Fix typos - Reorganize tests and provider args methods. --- .../java/co/rsk/peg/bitcoin/UtxoUtils.java | 10 +- .../co/rsk/peg/bitcoin/UtxoUtilsTest.java | 155 +++++++----------- 2 files changed, 64 insertions(+), 101 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java index c004726551a..95acbd69905 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java @@ -19,8 +19,8 @@ private UtxoUtils() { * Decode a {@code byte[]} of encoded outpoint values. * * @param encodedOutpointValues - * @return {@code List} the list of outpoint values decoded in the same ordered they were - * before encoding. Or an {@code Collections.EMPTY_LIST} when {@code encodedOutpointValues} is + * @return {@code List} the list of outpoint values decoded preserving + * the order of the entries. Or an {@code Collections.EMPTY_LIST} when {@code encodedOutpointValues} is * {@code null} or {@code empty byte[]}. */ public static List decodeOutpointValues(byte[] encodedOutpointValues) { @@ -45,7 +45,7 @@ public static List decodeOutpointValues(byte[] encodedOutpointValues) { offset += valueAsVarInt.getSizeInBytes(); Coin outpointValue = Coin.valueOf(valueAsVarInt.value); - assertValidOutpointValue(outpointValue); + validateOutpointValue(outpointValue); outpointValues.add(outpointValue); } @@ -68,7 +68,7 @@ public static byte[] encodeOutpointValues(List outpointValues) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (Coin outpointValue : outpointValues) { - assertValidOutpointValue(outpointValue); + validateOutpointValue(outpointValue); VarInt varIntOutpointValue = new VarInt(outpointValue.getValue()); try { outputStream.write(varIntOutpointValue.encode()); @@ -84,7 +84,7 @@ public static byte[] encodeOutpointValues(List outpointValues) { return outputStream.toByteArray(); } - private static void assertValidOutpointValue(Coin outpointValue) { + private static void validateOutpointValue(Coin outpointValue) { if (outpointValue == null || outpointValue.isNegative()) { throw new InvalidOutpointValueException(String.format( "Invalid outpoint value: %s. Negative and null values are not allowed.", diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java index 668e36cea90..d50bc0a795d 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -41,21 +41,8 @@ private static Stream validOutpointValues() { arguments.add(Arguments.of(Hex.decode("FC"), coinListOf(252))); - arguments.add(Arguments.of(Hex.decode("FCFCBBBBBBFD1934FE9145DC00"), - coinListOf( - // 252 = FC in VarInt format - 252, - 252, - // 187 = BB in VarInt format - 187, - 187, - 187, - // 13_337 = FE9145DC00 in VarInt format - 13_337, - // 14_435_729 = FEDC4591 in VarInt format - 14_435_729 - )) - ); + // 252 = FC, 187 = BB, 13_337 = FE9145DC00, 14_435_729 = FEDC4591 + arguments.add(Arguments.of(Hex.decode("FCFCBBBBBBFD1934FE9145DC00"), coinListOf(252, 252, 187, 187, 187, 13_337, 14_435_729))); arguments.add(Arguments.of(Hex.decode("FF0040075AF0750700"), Collections.singletonList(MAX_BTC))); @@ -65,22 +52,12 @@ private static Stream validOutpointValues() { arguments.add(Arguments.of(Hex.decode("FFFFFFFFFFFFFFFF7F"), coinListOf(Long.MAX_VALUE))); - final byte[] bigOutpointValueArray = Hex.decode(Stream.iterate("FCFCBBBBBBFD1934FE9145DC00", - UnaryOperator.identity()) - .limit(1000).collect(Collectors.joining())); - List bigListOfOutpointValues = Stream.generate(() -> coinListOf( - // 252 = FC in VarInt format - 252, - 252, - // 187 = BB in VarInt format - 187, - 187, - 187, - // 13_337 = FE9145DC00 in VarInt format - 13_337, - // 14_435_729 = FEDC4591 in VarInt format - 14_435_729) - ).limit(1000).flatMap(Collection::stream).collect(Collectors.toList()); + final byte[] bigOutpointValueArray = Hex.decode( + Stream.iterate("FCFCBBBBBBFD1934FE9145DC00", UnaryOperator.identity()).limit(1000) + .collect(Collectors.joining())); + List bigListOfOutpointValues = Stream.generate( + () -> coinListOf(252, 252, 187, 187, 187, 13_337, 14_435_729)).limit(1000) + .flatMap(Collection::stream).collect(Collectors.toList()); arguments.add(Arguments.of( bigOutpointValueArray, bigListOfOutpointValues @@ -91,58 +68,6 @@ private static Stream validOutpointValues() { return arguments.stream(); } - private static Stream invalidOutpointValues() { - List arguments = new ArrayList<>(); - - List negativeOutpointValues = Arrays.asList(Coin.valueOf(-10), Coin.valueOf(-1000), - Coin.valueOf(-100)); - String expectedMessageForNegativeOutpointValues = String.format( - "Invalid outpoint value: %s. Negative and null values are not allowed.", -10); - arguments.add( - Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); - - List negativeAndPositiveOutpointValues = Arrays.asList(Coin.valueOf(200), - Coin.valueOf(-100), Coin.valueOf(300)); - String expectedMessageForNegativeAndPositiveOutpointValues = String.format( - "Invalid outpoint value: %s. Negative and null values are not allowed.", -100); - arguments.add(Arguments.of(negativeAndPositiveOutpointValues, - expectedMessageForNegativeAndPositiveOutpointValues)); - - return arguments.stream(); - } - - private static Stream invalidEncodedOutpointValues() { - List arguments = new ArrayList<>(); - - // -100, -200, -300 - final byte[] negativeOutpointValues = Hex.decode( - "FF9CFFFFFFFFFFFFFFFF38FFFFFFFFFFFFFFFFD4FEFFFFFFFFFFFF"); - String expectedMessageForNegativeOutpointValues = String.format( - "Invalid outpoint value: %s. Negative and null values are not allowed.", -100); - arguments.add( - Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); - - // 100, 200, 300, -400 - final byte[] negativeAndPositiveOutpointValues = Hex.decode("64C8FD2C01FF70FEFFFFFFFFFFFF"); - String expectedMessageForNegativeAndPositiveOutpointValues = String.format( - "Invalid outpoint value: %s. Negative and null values are not allowed.", -400); - arguments.add(Arguments.of(negativeAndPositiveOutpointValues, - expectedMessageForNegativeAndPositiveOutpointValues)); - - final byte[] invalidOutpointValues = Hex.decode("FC9145DC00FAFF00FE"); - String expectedMessageForInvalidOutpointValues = String.format( - "Invalid value with invalid VarInt format: %s", "FC9145DC00FAFF00FE"); - arguments.add(Arguments.of(invalidOutpointValues, expectedMessageForInvalidOutpointValues)); - - final byte[] anotherInvalidOutpointValues = Hex.decode("FB8267DC00FCFF00FE"); - String expectedMessageForAnotherInvalidOutpointValue = String.format( - "Invalid value with invalid VarInt format: %s", "FB8267DC00FCFF00FE"); - arguments.add(Arguments.of(anotherInvalidOutpointValues, - expectedMessageForAnotherInvalidOutpointValue)); - - return arguments.stream(); - } - @ParameterizedTest @MethodSource("validOutpointValues") void decodeOutpointValues(byte[] encodedValues, List expectedDecodedValues) { @@ -153,16 +78,6 @@ void decodeOutpointValues(byte[] encodedValues, List expectedDecodedValues assertArrayEquals(expectedDecodedValues.toArray(), decodeOutpointValues.toArray()); } - @Test - void decodeOutpointValues_nullEncodedValues_shouldReturnEmptyList() { - // act - List decodeOutpointValues = UtxoUtils.decodeOutpointValues(null); - - // assert - List expectedDecodedValues = Collections.EMPTY_LIST; - assertArrayEquals(expectedDecodedValues.toArray(), decodeOutpointValues.toArray()); - } - @ParameterizedTest @MethodSource("validOutpointValues") void encodeOutpointValues(byte[] expectedEncodedOutpointValues, List outpointValues) { @@ -174,13 +89,23 @@ void encodeOutpointValues(byte[] expectedEncodedOutpointValues, List outpo } @Test - void decodeOutpointValues_nullOutpointValues_shouldReturnEmptyArray() { + void decodeOutpointValues_null_shouldReturnEmptyList() { // act - List outpointValues = UtxoUtils.decodeOutpointValues(null); + List decodeOutpointValues = UtxoUtils.decodeOutpointValues(null); // assert List expectedDecodedValues = Collections.EMPTY_LIST; - assertArrayEquals(expectedDecodedValues.toArray(), outpointValues.toArray()); + assertArrayEquals(expectedDecodedValues.toArray(), decodeOutpointValues.toArray()); + } + + @Test + void encodeOutpointValues_null_shouldReturnEmptyArray() { + // act + byte[] outpointValues = UtxoUtils.encodeOutpointValues(null); + + // assert + byte[] expectedEncodedValues = new byte[]{}; + assertArrayEquals(expectedEncodedValues, outpointValues); } @ParameterizedTest @@ -197,6 +122,20 @@ void encodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueE assertEquals(expectedMessage, actualMessage); } + private static Stream invalidOutpointValues() { + List arguments = new ArrayList<>(); + + List negativeOutpointValues = Arrays.asList(Coin.valueOf(-10), Coin.valueOf(-1000), Coin.valueOf(-100)); + String expectedMessageForNegativeOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -10); + arguments.add(Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); + + List negativeAndPositiveOutpointValues = Arrays.asList(Coin.valueOf(200), Coin.valueOf(-100), Coin.valueOf(300)); + String expectedMessageForNegativeAndPositiveOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -100); + arguments.add(Arguments.of(negativeAndPositiveOutpointValues, expectedMessageForNegativeAndPositiveOutpointValues)); + + return arguments.stream(); + } + @ParameterizedTest @MethodSource("invalidEncodedOutpointValues") void decodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueException( @@ -210,4 +149,28 @@ void decodeOutpointValues_invalidOutpointValues_shouldThrowInvalidOutpointValueE // assert assertEquals(expectedMessage, actualMessage); } + + private static Stream invalidEncodedOutpointValues() { + List arguments = new ArrayList<>(); + + // -100, -200, -300 + final byte[] negativeOutpointValues = Hex.decode("FF9CFFFFFFFFFFFFFFFF38FFFFFFFFFFFFFFFFD4FEFFFFFFFFFFFF"); + String expectedMessageForNegativeOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -100); + arguments.add(Arguments.of(negativeOutpointValues, expectedMessageForNegativeOutpointValues)); + + // 100, 200, 300, -400 + final byte[] negativeAndPositiveOutpointValues = Hex.decode("64C8FD2C01FF70FEFFFFFFFFFFFF"); + String expectedMessageForNegativeAndPositiveOutpointValues = String.format("Invalid outpoint value: %s. Negative and null values are not allowed.", -400); + arguments.add(Arguments.of(negativeAndPositiveOutpointValues, expectedMessageForNegativeAndPositiveOutpointValues)); + + final byte[] invalidOutpointValues = Hex.decode("FC9145DC00FAFF00FE"); + String expectedMessageForInvalidOutpointValues = String.format("Invalid value with invalid VarInt format: %s", "FC9145DC00FAFF00FE"); + arguments.add(Arguments.of(invalidOutpointValues, expectedMessageForInvalidOutpointValues)); + + final byte[] anotherInvalidOutpointValues = Hex.decode("FB8267DC00FCFF00FE"); + String expectedMessageForAnotherInvalidOutpointValue = String.format("Invalid value with invalid VarInt format: %s", "FB8267DC00FCFF00FE"); + arguments.add(Arguments.of(anotherInvalidOutpointValues, expectedMessageForAnotherInvalidOutpointValue)); + + return arguments.stream(); + } } From f1d33bb4fb4ba7298d6a0cb96771d83f1f0032c4 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Fri, 17 May 2024 01:16:47 -0400 Subject: [PATCH 09/20] Implement pegout_transaction_created event --- .../co/rsk/peg/utils/BridgeEventLogger.java | 4 ++ .../rsk/peg/utils/BridgeEventLoggerImpl.java | 16 +++++++ .../co/rsk/peg/bitcoin/UtxoTestUtils.java | 14 ++++++ .../co/rsk/peg/bitcoin/UtxoUtilsTest.java | 7 +-- .../peg/utils/BridgeEventLoggerImplTest.java | 48 +++++++++++++++++++ .../rsk/peg/utils/BridgeEventLoggerTest.java | 8 ++++ .../upgrades/ActivationConfigsForTest.java | 3 ++ 7 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoTestUtils.java diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java index 7893b704b5a..6184db4d756 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java @@ -78,4 +78,8 @@ default void logBatchPegoutCreated(Sha256Hash btcTxHash, List rskTxHa default void logPegoutConfirmed(Sha256Hash btcTxHash, long pegoutCreationRskBlockNumber) { throw new UnsupportedOperationException(); } + + default void logPegoutTransactionCreated(Sha256Hash btcTxHash, List outpointValues) { + throw new UnsupportedOperationException(); + } } diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java index 13fce24bff6..e4d7b7e4b15 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java @@ -22,6 +22,7 @@ import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; import co.rsk.peg.BridgeEvents; +import co.rsk.peg.bitcoin.UtxoUtils; import co.rsk.peg.federation.Federation; import co.rsk.peg.federation.FederationMember; import co.rsk.peg.pegin.RejectedPeginReason; @@ -245,6 +246,21 @@ public void logPegoutConfirmed(Sha256Hash btcTxHash, long pegoutCreationRskBlock this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); } + @Override + public void logPegoutTransactionCreated(Sha256Hash btcTxHash, List outpointValues) { + if (btcTxHash == null){ + throw new IllegalArgumentException("btcTxHash param cannot be null"); + } + + CallTransaction.Function event = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); + byte[][] encodedTopicsInBytes = event.encodeEventTopics(btcTxHash.getBytes()); + List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[] serializedOutpointValues = UtxoUtils.encodeOutpointValues(outpointValues); + byte[] encodedData = event.encodeEventData(serializedOutpointValues); + this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + } + private byte[] flatKeys(List keys, Function parser) { List pubKeys = keys.stream() .map(parser) diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoTestUtils.java new file mode 100644 index 00000000000..6e3f60ffdf0 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoTestUtils.java @@ -0,0 +1,14 @@ +package co.rsk.peg.bitcoin; + +import co.rsk.bitcoinj.core.Coin; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public final class UtxoTestUtils { + public static List coinListOf(long ... values) { + return Arrays.stream(values) + .mapToObj(Coin::valueOf) + .collect(Collectors.toList()); + } +} diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java index d50bc0a795d..3a9d1f94ce5 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -1,5 +1,6 @@ package co.rsk.peg.bitcoin; +import static co.rsk.peg.bitcoin.UtxoTestUtils.coinListOf; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -24,12 +25,6 @@ class UtxoUtilsTest { private final static Coin MAX_BTC = BridgeMainNetConstants.getInstance().getMaxRbtc(); - private static List coinListOf(long ... valuesInSatoshis) { - return Arrays.stream(valuesInSatoshis) - .mapToObj(Coin::valueOf) - .collect(Collectors.toList()); - } - private static Stream validOutpointValues() { List arguments = new ArrayList<>(); diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index 52d6befc0da..427d5bb04c1 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -25,9 +25,13 @@ import co.rsk.crypto.Keccak256; import co.rsk.peg.*; import co.rsk.peg.bitcoin.BitcoinTestUtils; +import co.rsk.peg.bitcoin.InvalidOutpointValueException; +import co.rsk.peg.bitcoin.UtxoUtils; import co.rsk.peg.federation.*; import co.rsk.peg.PegTestUtils; import co.rsk.peg.pegin.RejectedPeginReason; +import java.util.ArrayList; +import java.util.Collections; import org.bouncycastle.util.encoders.Hex; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; @@ -52,9 +56,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static co.rsk.peg.bitcoin.UtxoTestUtils.coinListOf; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -509,6 +515,48 @@ void logPegoutConfirmed() { assertEvent(eventLogs, 0, BridgeEvents.PEGOUT_CONFIRMED.getEvent(), new Object[]{btcTx.getHash().getBytes()}, new Object[]{pegoutCreationRskBlockNumber}); } + private static Stream logPegoutTransactionCreatedValidArgProvider() { + List args = new ArrayList<>(); + + args.add(Arguments.of(Sha256Hash.ZERO_HASH, Collections.singletonList(Coin.SATOSHI))); + args.add(Arguments.of(BitcoinTestUtils.createHash(5), coinListOf(500_000, 400_000, 1_000))); + args.add(Arguments.of(BitcoinTestUtils.createHash(10), Collections.singletonList(Coin.ZERO))); + args.add(Arguments.of(BitcoinTestUtils.createHash(10), Collections.EMPTY_LIST)); + args.add(Arguments.of(BitcoinTestUtils.createHash(15), null)); + + return args.stream(); + } + + @ParameterizedTest() + @MethodSource("logPegoutTransactionCreatedValidArgProvider") + void logPegoutTransactionCreated_ok(Sha256Hash btcTxHash, List outpointValues) { + eventLogger.logPegoutTransactionCreated(btcTxHash, outpointValues); + commonAssertLogs(eventLogs); + + assertTopics(2, eventLogs); + assertEvent(eventLogs, 0, BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(), new Object[]{btcTxHash.getBytes()}, new Object[]{ + UtxoUtils.encodeOutpointValues(outpointValues)} + ); + } + + private static Stream logPegoutTransactionCreatedInvalidArgProvider() { + List args = new ArrayList<>(); + + args.add(Arguments.of(null, Collections.singletonList(null), IllegalArgumentException.class)); + args.add(Arguments.of(null, Collections.singletonList(Coin.SATOSHI), IllegalArgumentException.class)); + args.add(Arguments.of(Sha256Hash.ZERO_HASH, Collections.singletonList(null), InvalidOutpointValueException.class)); + args.add(Arguments.of(BitcoinTestUtils.createHash(1), coinListOf(-100), InvalidOutpointValueException.class)); + args.add(Arguments.of(BitcoinTestUtils.createHash(2), coinListOf(100, -200, 300), InvalidOutpointValueException.class)); + + return args.stream(); + } + + @ParameterizedTest() + @MethodSource("logPegoutTransactionCreatedInvalidArgProvider") + void logPegoutTransactionCreated_invalidBtcTxHashOrOutpointValues_shouldFail(Sha256Hash btcTxHash, List outpointValues, Class expectedException) { + assertThrows(expectedException, () -> eventLogger.logPegoutTransactionCreated(btcTxHash, outpointValues)); + } + /********************************** * ------- UTILS ------- * *********************************/ diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java index ee648482ed4..952f38832d4 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.*; import co.rsk.peg.pegin.RejectedPeginReason; +import java.util.Arrays; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -103,4 +104,11 @@ void logPegoutConfirmed() { }); } + @Test + void logPegoutTransactionCreated() { + Sha256Hash btcTxHash = btcTxMock.getHash(); + assertThrows(UnsupportedOperationException.class, () -> { + eventLogger.logPegoutTransactionCreated(btcTxHash, Arrays.asList(Coin.COIN, Coin.SATOSHI, Coin.FIFTY_COINS)); + }); + } } diff --git a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java index 9cb7092aeed..a1b080a2b57 100644 --- a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java +++ b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java @@ -193,6 +193,9 @@ private static List getArrowhead600Rskips() { private static List getLovell700Rskips() { List rskips = new ArrayList<>(); + rskips.addAll(Arrays.asList( + ConsensusRule.RSKIP428 + )); return rskips; } From 5b47985f0a156c38f85a7ad516e78e8a24535cb3 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Tue, 21 May 2024 21:48:31 -0400 Subject: [PATCH 10/20] Use variable instead of passing values directly to method being tested --- .../co/rsk/peg/utils/BridgeEventLoggerImplTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index 427d5bb04c1..a890e16cf8e 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -37,6 +37,7 @@ import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.*; +import org.ethereum.core.CallTransaction.Function; import org.ethereum.crypto.ECKey; import org.ethereum.vm.LogInfo; import org.ethereum.vm.PrecompiledContracts; @@ -534,9 +535,12 @@ void logPegoutTransactionCreated_ok(Sha256Hash btcTxHash, List outpointVal commonAssertLogs(eventLogs); assertTopics(2, eventLogs); - assertEvent(eventLogs, 0, BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(), new Object[]{btcTxHash.getBytes()}, new Object[]{ - UtxoUtils.encodeOutpointValues(outpointValues)} - ); + + int index = 0; + Function expectedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); + Object[] topics = {btcTxHash.getBytes()}; + Object[] params = {UtxoUtils.encodeOutpointValues(outpointValues)}; + assertEvent(eventLogs, index, expectedEvent, topics, params); } private static Stream logPegoutTransactionCreatedInvalidArgProvider() { From 6ab1f7594eee09ae59220588756ad3d8fca7aa36 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Wed, 22 May 2024 11:01:17 -0400 Subject: [PATCH 11/20] Move coinListOf method to BitcoinTestUtils --- .../java/co/rsk/peg/bitcoin/BitcoinTestUtils.java | 7 +++++++ .../java/co/rsk/peg/bitcoin/UtxoTestUtils.java | 14 -------------- .../java/co/rsk/peg/bitcoin/UtxoUtilsTest.java | 2 +- .../rsk/peg/utils/BridgeEventLoggerImplTest.java | 2 +- 4 files changed, 9 insertions(+), 16 deletions(-) delete mode 100644 rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoTestUtils.java diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java index 6b9ef4d52b2..cec1e22ccaa 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java @@ -2,6 +2,7 @@ import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; +import co.rsk.bitcoinj.core.Coin; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.bitcoinj.core.Sha256Hash; import co.rsk.bitcoinj.core.TransactionInput; @@ -71,4 +72,10 @@ public static List extractSignaturesFromTxInput(Transac } return signatures; } + + public static List coinListOf(long ... values) { + return Arrays.stream(values) + .mapToObj(Coin::valueOf) + .collect(Collectors.toList()); + } } diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoTestUtils.java deleted file mode 100644 index 6e3f60ffdf0..00000000000 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoTestUtils.java +++ /dev/null @@ -1,14 +0,0 @@ -package co.rsk.peg.bitcoin; - -import co.rsk.bitcoinj.core.Coin; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public final class UtxoTestUtils { - public static List coinListOf(long ... values) { - return Arrays.stream(values) - .mapToObj(Coin::valueOf) - .collect(Collectors.toList()); - } -} diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java index 3a9d1f94ce5..96b4bd70f28 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -1,6 +1,6 @@ package co.rsk.peg.bitcoin; -import static co.rsk.peg.bitcoin.UtxoTestUtils.coinListOf; +import static co.rsk.peg.bitcoin.BitcoinTestUtils.coinListOf; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index a890e16cf8e..36ecb5f5f58 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -57,7 +57,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static co.rsk.peg.bitcoin.UtxoTestUtils.coinListOf; +import static co.rsk.peg.bitcoin.BitcoinTestUtils.coinListOf; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; From faa9b74a7a80c2c55a1129349a80f7d8674bb00f Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Mon, 20 May 2024 04:03:05 -0400 Subject: [PATCH 12/20] Emit new pegout_transaction_created event in all the places where pegouts are created --- .../main/java/co/rsk/peg/BridgeSupport.java | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java index 6334164c704..065405816c3 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -1041,7 +1041,7 @@ private void migrateFunds( PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations = provider.getPegoutsWaitingForConfirmations(); Pair> createResult = createMigrationTransaction(retiringFederationWallet, activeFederationAddress); - BtcTransaction btcTx = createResult.getLeft(); + BtcTransaction migrationTransaction = createResult.getLeft(); List selectedUTXOs = createResult.getRight(); logger.debug( @@ -1054,21 +1054,26 @@ private void migrateFunds( Coin amountMigrated = selectedUTXOs.stream() .map(UTXO::getValue) .reduce(Coin.ZERO, Coin::add); - pegoutsWaitingForConfirmations.add(btcTx, rskExecutionBlock.getNumber(), rskTxHash); + pegoutsWaitingForConfirmations.add(migrationTransaction, rskExecutionBlock.getNumber(), rskTxHash); // Log the Release request logger.debug( "[migrateFunds] release requested. rskTXHash: {}, btcTxHash: {}, amount: {}", rskTxHash, - btcTx.getHash(), + migrationTransaction.getHash(), amountMigrated ); - eventLogger.logReleaseBtcRequested(rskTxHash.getBytes(), btcTx, amountMigrated); + eventLogger.logReleaseBtcRequested(rskTxHash.getBytes(), migrationTransaction, amountMigrated); } else { - pegoutsWaitingForConfirmations.add(btcTx, rskExecutionBlock.getNumber()); + pegoutsWaitingForConfirmations.add(migrationTransaction, rskExecutionBlock.getNumber()); + } + + if (activations.isActive(RSKIP428)) { + List outpointValues = extractOutpointValues(migrationTransaction); + eventLogger.logPegoutTransactionCreated(migrationTransaction.getHash(), outpointValues); } // Store pegoutTxSigHash to be able to identify the tx type - savePegoutTxSigHash(btcTx); + savePegoutTxSigHash(migrationTransaction); // Mark UTXOs as spent availableUTXOs.removeIf(utxo -> selectedUTXOs.stream().anyMatch(selectedUtxo -> @@ -1076,6 +1081,10 @@ private void migrateFunds( )); } + private List extractOutpointValues(BtcTransaction generatedTransaction) { + return generatedTransaction.getInputs().stream().map(TransactionInput::getValue).collect(Collectors.toList()); + } + /** * Processes the current btc release request queue * and tries to build btc transactions using (and marking as spent) @@ -1222,9 +1231,9 @@ private void processPegoutsInBatch( logger.info("[processPegoutsInBatch] pegouts processed with btcTx hash {} and response code {}", result.getBtcTx().getHash(), result.getResponseCode()); - BtcTransaction generatedTransaction = result.getBtcTx(); - addToPegoutsWaitingForConfirmations(generatedTransaction, pegoutsWaitingForConfirmations, rskTx.getHash(), totalPegoutValue); - savePegoutTxSigHash(generatedTransaction); + BtcTransaction batchPegoutTransaction = result.getBtcTx(); + addToPegoutsWaitingForConfirmations(batchPegoutTransaction, pegoutsWaitingForConfirmations, rskTx.getHash(), totalPegoutValue); + savePegoutTxSigHash(batchPegoutTransaction); // Remove batched requests from the queue after successfully batching pegouts pegoutRequests.removeEntries(pegoutEntries); @@ -1234,10 +1243,15 @@ private void processPegoutsInBatch( logger.debug("[processPegoutsInBatch] used {} UTXOs for this pegout", selectedUTXOs.size()); availableUTXOs.removeAll(selectedUTXOs); - eventLogger.logBatchPegoutCreated(generatedTransaction.getHash(), + eventLogger.logBatchPegoutCreated(batchPegoutTransaction.getHash(), pegoutEntries.stream().map(ReleaseRequestQueue.Entry::getRskTxHash).collect(Collectors.toList())); - adjustBalancesIfChangeOutputWasDust(generatedTransaction, totalPegoutValue, wallet); + if (activations.isActive(RSKIP428)) { + List outpointValues = extractOutpointValues(batchPegoutTransaction); + eventLogger.logPegoutTransactionCreated(batchPegoutTransaction.getHash(), outpointValues); + } + + adjustBalancesIfChangeOutputWasDust(batchPegoutTransaction, totalPegoutValue, wallet); } // update next Pegout height even if there were no request in queue @@ -3221,11 +3235,17 @@ private void generateRejectionRelease( ReleaseTransactionBuilder.BuildResult buildReturnResult = txBuilder.buildEmptyWalletTo(btcRefundAddress); if (buildReturnResult.getResponseCode() == ReleaseTransactionBuilder.Response.SUCCESS) { + BtcTransaction refundPegoutTransaction = buildReturnResult.getBtcTx(); if (activations.isActive(ConsensusRule.RSKIP146)) { - provider.getPegoutsWaitingForConfirmations().add(buildReturnResult.getBtcTx(), rskExecutionBlock.getNumber(), rskTxHash); - eventLogger.logReleaseBtcRequested(rskTxHash.getBytes(), buildReturnResult.getBtcTx(), totalAmount); + provider.getPegoutsWaitingForConfirmations().add(refundPegoutTransaction, rskExecutionBlock.getNumber(), rskTxHash); + eventLogger.logReleaseBtcRequested(rskTxHash.getBytes(), refundPegoutTransaction, totalAmount); + + if (activations.isActive(RSKIP428)) { + List outpointValues = extractOutpointValues(refundPegoutTransaction); + eventLogger.logPegoutTransactionCreated(refundPegoutTransaction.getHash(), outpointValues); + } } else { - provider.getPegoutsWaitingForConfirmations().add(buildReturnResult.getBtcTx(), rskExecutionBlock.getNumber()); + provider.getPegoutsWaitingForConfirmations().add(refundPegoutTransaction, rskExecutionBlock.getNumber()); } logger.info( "[generateRejectionRelease] Rejecting peg-in tx built successfully: Refund to address: {}. RskTxHash: {}. Value {}.", From 9676d3287fe6ca0e8370c33d993d29fa1c3c9b4b Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Mon, 20 May 2024 04:59:15 -0400 Subject: [PATCH 13/20] Add tests for migration and batch pegouts --- ...portPegoutTransactionCreatedEventTest.java | 328 ++++++++++++++++++ .../test/java/co/rsk/peg/PegTestUtils.java | 3 +- 2 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java new file mode 100644 index 00000000000..3035c33ca84 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java @@ -0,0 +1,328 @@ +package co.rsk.peg; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import co.rsk.bitcoinj.core.BtcTransaction; +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.TransactionInput; +import co.rsk.bitcoinj.core.TransactionOutput; +import co.rsk.bitcoinj.core.UTXO; +import co.rsk.bitcoinj.wallet.Wallet; +import co.rsk.blockchain.utils.BlockGenerator; +import co.rsk.config.BridgeConstants; +import co.rsk.config.BridgeMainNetConstants; +import co.rsk.crypto.Keccak256; +import co.rsk.peg.ReleaseRequestQueue.Entry; +import co.rsk.peg.federation.Federation; +import co.rsk.peg.federation.FederationArgs; +import co.rsk.peg.federation.FederationFactory; +import co.rsk.peg.federation.FederationTestUtils; +import co.rsk.peg.utils.BridgeEventLogger; +import co.rsk.test.builders.BridgeSupportBuilder; +import java.io.IOException; +import java.time.Instant; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; +import org.ethereum.config.blockchain.upgrades.ConsensusRule; +import org.ethereum.core.Transaction; +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 BridgeSupportPegoutTransactionCreatedEventTest { + + private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); + private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); + + private BridgeStorageProvider provider; + private BridgeEventLogger eventLogger; + + @BeforeEach + void init() throws IOException { + provider = mock(BridgeStorageProvider.class); + eventLogger = mock(BridgeEventLogger.class); + + when(provider.getFeePerKb()) + .thenReturn(Coin.MILLICOIN); + + when(provider.getPegoutsWaitingForSignatures()) + .thenReturn(new TreeMap<>()); + + when(provider.getPegoutsWaitingForConfirmations()) + .thenReturn(new PegoutsWaitingForConfirmations(new HashSet<>())); + } + + private static Stream pegoutTransactionCreatedEventArgsProvider() { + return Stream.of( + Arguments.of(ActivationConfigsForTest.arrowhead600().forBlock(0)), + Arguments.of(ActivationConfigsForTest.lovell700().forBlock(0)) + ); + } + + private List extractOutpointValues(BtcTransaction generatedTransaction) { + return generatedTransaction.getInputs().stream().map(TransactionInput::getValue).collect( + Collectors.toList()); + } + + @ParameterizedTest + @MethodSource("pegoutTransactionCreatedEventArgsProvider") + void test_pegoutTransactionCreatedEvent_when_pegout_batch_is_created(ActivationConfig.ForBlock activations) throws IOException { + // Arrange + List fedUTXOs = PegTestUtils.createUTXOs( + 10, + bridgeMainnetConstants.getGenesisFederation().getAddress() + ); + when(provider.getNewFederationBtcUTXOs()) + .thenReturn(fedUTXOs); + + List pegoutRequests = PegTestUtils.createReleaseRequestQueueEntries(3); + when(provider.getReleaseRequestQueue()) + .thenReturn(new ReleaseRequestQueue(pegoutRequests)); + + PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations = provider.getPegoutsWaitingForConfirmations(); + + BridgeSupport bridgeSupport = new BridgeSupportBuilder() + .withBridgeConstants(bridgeMainnetConstants) + .withProvider(provider) + .withEventLogger(eventLogger) + .withActivations(activations) + .build(); + + Keccak256 pegoutCreationRskTxHash = PegTestUtils.createHash3(1); + + Transaction executionRskTx = mock(Transaction.class); + when(executionRskTx.getHash()).thenReturn(pegoutCreationRskTxHash); + + // Act + bridgeSupport.updateCollections(executionRskTx); + + // Assertions + + // Assert one pegout tx was added to pegoutsWaitingForConfirmations from the creation of a pegout batch + assertEquals(1, pegoutsWaitingForConfirmations.getEntries().size()); + + PegoutsWaitingForConfirmations.Entry pegoutEntry = pegoutsWaitingForConfirmations.getEntries().stream().findFirst().get(); + + BtcTransaction pegoutBatchTransaction = pegoutEntry.getBtcTransaction(); + + Sha256Hash btcTxHash = pegoutBatchTransaction.getHash(); + + List outpointValues = extractOutpointValues(pegoutBatchTransaction); + + List pegoutRequestRskTxHashes = pegoutRequests.stream().map(Entry::getRskTxHash).collect( + Collectors.toList()); + Coin totalTransactionInputAmount = pegoutRequests.stream().map(Entry::getAmount).reduce(Coin.ZERO, Coin::add); + + verify(eventLogger, times(1)).logBatchPegoutCreated(btcTxHash, pegoutRequestRskTxHashes); + verify(eventLogger, times(1)).logReleaseBtcRequested(pegoutCreationRskTxHash.getBytes(), pegoutBatchTransaction, totalTransactionInputAmount); + + if (activations.isActive(ConsensusRule.RSKIP428)){ + verify(eventLogger, times(1)).logPegoutTransactionCreated(btcTxHash, outpointValues); + } else { + verify(eventLogger, never()).logPegoutTransactionCreated(any(), any()); + } + } + + @ParameterizedTest + @MethodSource("pegoutTransactionCreatedEventArgsProvider") + void test_pegoutTransactionCreatedEvent_when_migration_tx_is_created(ActivationConfig.ForBlock activations) throws IOException { + // Arrange + when(provider.getReleaseRequestQueue()) + .thenReturn(new ReleaseRequestQueue(Collections.emptyList())); + + PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations = provider.getPegoutsWaitingForConfirmations(); + + Federation oldFederation = bridgeMainnetConstants.getGenesisFederation(); + long newFedCreationBlockNumber = 5L; + + FederationArgs newFederationArgs = new FederationArgs(FederationTestUtils.getFederationMembers(1), + Instant.EPOCH, newFedCreationBlockNumber, btcMainnetParams); + Federation newFederation = FederationFactory.buildStandardMultiSigFederation(newFederationArgs); + when(provider.getOldFederation()) + .thenReturn(oldFederation); + when(provider.getNewFederation()) + .thenReturn(newFederation); + + // Utxos to migrate + List utxos = PegTestUtils.createUTXOs(10, oldFederation.getAddress()); + Coin totalTransactionInputAmount = utxos.stream().map(UTXO::getValue).reduce(Coin.ZERO, Coin::add); + when(provider.getOldFederationBtcUTXOs()) + .thenReturn(utxos); + + // Advance blockchain to migration phase. Migration phase starts 1 block after migration age is reached. + long migrationAge = bridgeMainnetConstants.getFederationActivationAge(activations) + + bridgeMainnetConstants.getFundsMigrationAgeSinceActivationBegin() + + newFedCreationBlockNumber + 1; + BlockGenerator blockGenerator = new BlockGenerator(); + org.ethereum.core.Block rskCurrentBlock = blockGenerator.createBlock(migrationAge, 1); + + BridgeSupport bridgeSupport = new BridgeSupportBuilder() + .withBridgeConstants(bridgeMainnetConstants) + .withProvider(provider) + .withEventLogger(eventLogger) + .withExecutionBlock(rskCurrentBlock) + .withActivations(activations) + .build(); + + Keccak256 pegoutCreationRskTxHash = PegTestUtils.createHash3(1); + Transaction executionRskTx = mock(Transaction.class); + when(executionRskTx.getHash()).thenReturn(pegoutCreationRskTxHash); + + // Act + bridgeSupport.updateCollections(executionRskTx); + + // Assertions + + // Assert one migration tx was added to pegoutsWaitingForConfirmations from the creation of a pegout batch + assertEquals(1, pegoutsWaitingForConfirmations.getEntries().size()); + + PegoutsWaitingForConfirmations.Entry pegoutEntry = pegoutsWaitingForConfirmations. + getEntries(). + stream(). + findFirst(). + get(); + + BtcTransaction migrationTransaction = pegoutEntry.getBtcTransaction(); + Sha256Hash btcTxHash = migrationTransaction.getHash(); + + List outpointValues = extractOutpointValues(migrationTransaction); + + verify(eventLogger, never()).logBatchPegoutCreated(any(), any()); + verify(eventLogger, times(1)).logReleaseBtcRequested(pegoutCreationRskTxHash.getBytes(), migrationTransaction, totalTransactionInputAmount); + + if (activations.isActive(ConsensusRule.RSKIP428)){ + verify(eventLogger, times(1)).logPegoutTransactionCreated(btcTxHash, outpointValues); + } else { + verify(eventLogger, never()).logPegoutTransactionCreated(any(), any()); + } + } + + @ParameterizedTest + @MethodSource("pegoutTransactionCreatedEventArgsProvider") + void test_pegoutTransactionCreatedEvent_when_migration_and_pegout_batch_tx_are_created(ActivationConfig.ForBlock activations) throws IOException { + // Arrange + PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations = provider.getPegoutsWaitingForConfirmations(); + + List pegoutRequests = PegTestUtils.createReleaseRequestQueueEntries(3); + when(provider.getReleaseRequestQueue()) + .thenReturn(new ReleaseRequestQueue(pegoutRequests)); + + Federation oldFederation = bridgeMainnetConstants.getGenesisFederation(); + + long newFedCreationBlockNumber = 5L; + FederationArgs newFederationArgs = new FederationArgs(FederationTestUtils.getFederationMembers(1), + Instant.EPOCH, newFedCreationBlockNumber, btcMainnetParams); + Federation newFederation = FederationFactory.buildStandardMultiSigFederation( + newFederationArgs); + when(provider.getOldFederation()) + .thenReturn(oldFederation); + when(provider.getNewFederation()) + .thenReturn(newFederation); + + // Utxos to migrate + List utxos = PegTestUtils.createUTXOs(10, oldFederation.getAddress()); + when(provider.getOldFederationBtcUTXOs()) + .thenReturn(utxos); + Coin migrationTotalAmount = utxos.stream().map(UTXO::getValue).reduce(Coin.ZERO, Coin::add); + + List utxosNew = PegTestUtils.createUTXOs(10, newFederation.getAddress()); + when(provider.getNewFederationBtcUTXOs()) + .thenReturn(utxosNew); + + // Advance blockchain to migration phase. Migration phase starts 1 block after migration age is reached. + long migrationAge = bridgeMainnetConstants.getFederationActivationAge(activations) + + bridgeMainnetConstants.getFundsMigrationAgeSinceActivationBegin() + + newFedCreationBlockNumber + 1; + + BlockGenerator blockGenerator = new BlockGenerator(); + org.ethereum.core.Block rskCurrentBlock = blockGenerator.createBlock(migrationAge, 1); + + BridgeSupport bridgeSupport = new BridgeSupportBuilder() + .withBridgeConstants(bridgeMainnetConstants) + .withProvider(provider) + .withEventLogger(eventLogger) + .withExecutionBlock(rskCurrentBlock) + .withActivations(activations) + .build(); + + Transaction rskTx = mock(Transaction.class); + when(rskTx.getHash()).thenReturn(PegTestUtils.createHash3(1)); + + // Act + bridgeSupport.updateCollections(rskTx); + + // Assertions + + // Assert two pegouts was added to pegoutsWaitingForConfirmations from the creation of each pegout batch for the migration and pegout + assertEquals(2, pegoutsWaitingForConfirmations.getEntries().size()); + + Optional migrationEntry = Optional.empty(); + Optional pegoutEntry = Optional.empty(); + + // Get new fed wallet to identify the migration tx + Wallet fedWallet = BridgeUtils.getFederationNoSpendWallet( + new Context(btcMainnetParams), + newFederation, + false, + null + ); + + // If all outputs are sent to the active fed then it's the migration tx; if not, it's the peg-out batch + for (PegoutsWaitingForConfirmations.Entry entry : pegoutsWaitingForConfirmations.getEntries()) { + List walletOutputs = entry.getBtcTransaction().getWalletOutputs(fedWallet); + if (walletOutputs.size() == entry.getBtcTransaction().getOutputs().size()){ + migrationEntry = Optional.of(entry); + } else { + pegoutEntry = Optional.of(entry); + } + } + assertTrue(migrationEntry.isPresent()); + assertTrue(pegoutEntry.isPresent()); + + BtcTransaction pegoutBatchTx = pegoutEntry.get().getBtcTransaction(); + Keccak256 pegoutBatchCreationRskTxHash = pegoutEntry.get().getPegoutCreationRskTxHash(); + Sha256Hash pegoutBatchBtcTxHash = pegoutBatchTx.getHash(); + + Coin pegoutBatchTotalAmount = pegoutRequests.stream().map(Entry::getAmount).reduce(Coin.ZERO, Coin::add); + List pegoutBatchTxOutpointValues = extractOutpointValues(pegoutBatchTx); + + List pegoutRequestRskTxHashes = pegoutRequests.stream().map(Entry::getRskTxHash).collect( + Collectors.toList()); + + verify(eventLogger, times(1)).logBatchPegoutCreated(pegoutBatchBtcTxHash, pegoutRequestRskTxHashes); + verify(eventLogger, times(1)).logReleaseBtcRequested(pegoutBatchCreationRskTxHash.getBytes(), pegoutBatchTx, pegoutBatchTotalAmount); + + Keccak256 migrationCreationRskTxHash = migrationEntry.get().getPegoutCreationRskTxHash(); + BtcTransaction migrationTx = migrationEntry.get().getBtcTransaction(); + Sha256Hash migrationTxHash = migrationTx.getHash(); + + List migrationTxOutpointValues = extractOutpointValues(migrationTx); + + verify(eventLogger, times(1)).logReleaseBtcRequested(migrationCreationRskTxHash.getBytes(), migrationTx, migrationTotalAmount); + + if (activations.isActive(ConsensusRule.RSKIP428)){ + verify(eventLogger, times(1)).logPegoutTransactionCreated(pegoutBatchBtcTxHash, pegoutBatchTxOutpointValues); + verify(eventLogger, times(1)).logPegoutTransactionCreated(migrationTxHash, migrationTxOutpointValues); + } else { + verify(eventLogger, never()).logPegoutTransactionCreated(any(), any()); + } + } +} diff --git a/rskj-core/src/test/java/co/rsk/peg/PegTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/PegTestUtils.java index 079224bbc6e..fe605d75ebe 100644 --- a/rskj-core/src/test/java/co/rsk/peg/PegTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/PegTestUtils.java @@ -260,7 +260,8 @@ public static List createReleaseRequestQueueEntries(i for (int i = 0; i < amount; i++) { ReleaseRequestQueue.Entry entry = new ReleaseRequestQueue.Entry( createRandomP2PKHBtcAddress(RegTestParams.get()), - Coin.COIN.add(Coin.valueOf(i)) + Coin.COIN.add(Coin.valueOf(i)), + createHash3(i) ); entries.add(entry); } From e841335490e5a3b9a77bd187fbdc1443c5395290 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Mon, 20 May 2024 21:04:06 -0400 Subject: [PATCH 14/20] - Add test for refund peg-out checking pegout_transaction_created event is being emitted - Refactor existing test of peg-out sighash - Create a test util method to extract the outpoint values from btc transaction --- ...portPegoutTransactionCreatedEventTest.java | 11 +- ...idgeSupportRegisterBtcTransactionTest.java | 189 +++++++++++++----- .../java/co/rsk/peg/PowpegMigrationTest.java | 76 ++++++- .../co/rsk/peg/bitcoin/BitcoinTestUtils.java | 6 + .../rsk/peg/utils/BridgeEventLoggerTest.java | 16 +- 5 files changed, 231 insertions(+), 67 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java index 3035c33ca84..ab5fbf7639c 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java @@ -1,5 +1,6 @@ package co.rsk.peg; +import static co.rsk.peg.bitcoin.BitcoinTestUtils.extractOutpointValues; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -14,7 +15,6 @@ import co.rsk.bitcoinj.core.Context; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.bitcoinj.core.Sha256Hash; -import co.rsk.bitcoinj.core.TransactionInput; import co.rsk.bitcoinj.core.TransactionOutput; import co.rsk.bitcoinj.core.UTXO; import co.rsk.bitcoinj.wallet.Wallet; @@ -77,11 +77,6 @@ private static Stream pegoutTransactionCreatedEventArgsProvider() { ); } - private List extractOutpointValues(BtcTransaction generatedTransaction) { - return generatedTransaction.getInputs().stream().map(TransactionInput::getValue).collect( - Collectors.toList()); - } - @ParameterizedTest @MethodSource("pegoutTransactionCreatedEventArgsProvider") void test_pegoutTransactionCreatedEvent_when_pegout_batch_is_created(ActivationConfig.ForBlock activations) throws IOException { @@ -129,10 +124,10 @@ void test_pegoutTransactionCreatedEvent_when_pegout_batch_is_created(ActivationC List pegoutRequestRskTxHashes = pegoutRequests.stream().map(Entry::getRskTxHash).collect( Collectors.toList()); - Coin totalTransactionInputAmount = pegoutRequests.stream().map(Entry::getAmount).reduce(Coin.ZERO, Coin::add); + Coin totalTransactionAmount = pegoutRequests.stream().map(Entry::getAmount).reduce(Coin.ZERO, Coin::add); verify(eventLogger, times(1)).logBatchPegoutCreated(btcTxHash, pegoutRequestRskTxHashes); - verify(eventLogger, times(1)).logReleaseBtcRequested(pegoutCreationRskTxHash.getBytes(), pegoutBatchTransaction, totalTransactionInputAmount); + verify(eventLogger, times(1)).logReleaseBtcRequested(pegoutCreationRskTxHash.getBytes(), pegoutBatchTransaction, totalTransactionAmount); if (activations.isActive(ConsensusRule.RSKIP428)){ verify(eventLogger, times(1)).logPegoutTransactionCreated(btcTxHash, outpointValues); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java index c707ae8d41d..130ba0f803b 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java @@ -34,12 +34,10 @@ import co.rsk.bitcoinj.store.BlockStoreException; import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; +import co.rsk.peg.PegoutsWaitingForConfirmations.Entry; import co.rsk.peg.bitcoin.BitcoinTestUtils; import co.rsk.peg.bitcoin.CoinbaseInformation; import co.rsk.peg.btcLockSender.BtcLockSenderProvider; -import co.rsk.peg.constants.BridgeConstants; -import co.rsk.peg.constants.BridgeMainNetConstants; -import co.rsk.peg.constants.BridgeRegTestConstants; import co.rsk.peg.federation.Federation; import co.rsk.peg.federation.FederationArgs; import co.rsk.peg.federation.FederationFactory; @@ -80,12 +78,27 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.io.IOException; +import java.math.BigInteger; +import java.time.Instant; +import java.util.*; +import java.util.stream.Stream; + +import static co.rsk.peg.BridgeSupportTestUtil.mockChainOfStoredBlocks; +import static co.rsk.peg.PegTestUtils.*; +import static co.rsk.peg.bitcoin.BitcoinTestUtils.extractOutpointValues; +import static co.rsk.peg.pegin.RejectedPeginReason.*; +import static co.rsk.peg.utils.UnrefundablePeginReason.LEGACY_PEGIN_UNDETERMINED_SENDER; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + class BridgeSupportRegisterBtcTransactionTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); private static final ActivationConfig.ForBlock fingerrootActivations = ActivationConfigsForTest.fingerroot500().forBlock(0); private static final ActivationConfig.ForBlock arrowhead600Activations = ActivationConfigsForTest.arrowhead600().forBlock(0); + private static final ActivationConfig.ForBlock lovell700Activations = ActivationConfigsForTest.lovell700().forBlock(0); private static final Coin minimumPeginTxValue = bridgeMainnetConstants.getMinimumPeginTxValue(ActivationConfigsForTest.all().forBlock(0)); private static final Coin belowMinimumPeginTxValue = minimumPeginTxValue.minus(Coin.SATOSHI); @@ -171,18 +184,31 @@ private void assertUnknownTxIsIgnored() throws IOException { Assertions.assertTrue(retiringFederationUtxos.isEmpty()); } - private void assertLegacyMultisigPeginIsRejectedAndRefunded(BtcTransaction btcTransaction, Coin sentAmount) throws IOException { + private void assertPeginIsRejectedAndRefunded(ActivationConfig.ForBlock activations, BtcTransaction btcTransaction, Coin sentAmount, RejectedPeginReason expectedRejectedPeginReason) throws IOException { verify(bridgeEventLogger, never()).logPeginBtc(any(), any(), any(), anyInt()); verify(bridgeEventLogger, never()).logUnrefundablePegin(any(), any()); Assertions.assertTrue(activeFederationUtxos.isEmpty()); Assertions.assertTrue(retiringFederationUtxos.isEmpty()); - verify(bridgeEventLogger, times(1)).logRejectedPegin(btcTransaction, LEGACY_PEGIN_MULTISIG_SENDER); - verify(bridgeEventLogger, times(1)).logReleaseBtcRequested(eq(rskTx.getHash().getBytes()), any(BtcTransaction.class), eq(sentAmount)); + Assertions.assertEquals(1, pegoutsWaitingForConfirmations.getEntries().size()); + Entry pegoutWaitingForConfirmationEntry = pegoutsWaitingForConfirmations.getEntries().stream().findFirst().get(); + BtcTransaction refundPegout = pegoutWaitingForConfirmationEntry.getBtcTransaction(); + Sha256Hash refundPegoutHash = refundPegout.getHash(); + List refundPegoutOutpointValues = extractOutpointValues(refundPegout); + + verify(bridgeEventLogger, times(1)).logRejectedPegin(btcTransaction, expectedRejectedPeginReason); + verify(bridgeEventLogger, times(1)).logReleaseBtcRequested(rskTx.getHash().getBytes(), refundPegout, sentAmount); + + if(activations == lovell700Activations) { + verify(bridgeEventLogger, times(1)).logPegoutTransactionCreated(refundPegoutHash, refundPegoutOutpointValues); + } else { + verify(bridgeEventLogger, never()).logPegoutTransactionCreated(any(), any()); + } verify(provider, times(1)).setHeightBtcTxhashAlreadyProcessed(btcTransaction.getHash(false), rskExecutionBlock.getNumber()); + verify(provider, never()).setPegoutTxSigHash(any()); + - Assertions.assertEquals(1, pegoutsWaitingForConfirmations.getEntries().size()); } // Before arrowhead600Activations is activated @@ -199,7 +225,7 @@ private void assertLegacyUndeterminedSenderPeginIsRejectedAsPeginV1InvalidPayloa verify(bridgeEventLogger, never()).logPeginBtc(any(), any(), any(), anyInt()); verify(bridgeEventLogger, never()).logReleaseBtcRequested(any(), any(), any()); - + verify(bridgeEventLogger, never()).logPegoutTransactionCreated(any(), any()); Assertions.assertTrue(activeFederationUtxos.isEmpty()); Assertions.assertTrue(retiringFederationUtxos.isEmpty()); @@ -225,6 +251,25 @@ private void assertLegacyUndeterminedSenderPeginIsRejected(BtcTransaction btcTra Assertions.assertTrue(pegoutsWaitingForConfirmations.getEntries().isEmpty()); } + private void assertInvalidPeginV1UndeterminedSenderIsRejected(BtcTransaction btcTransaction) throws IOException { + verify(bridgeEventLogger, times(1)).logRejectedPegin( + btcTransaction, PEGIN_V1_INVALID_PAYLOAD + ); + verify(bridgeEventLogger, times(1)).logUnrefundablePegin( + btcTransaction, + LEGACY_PEGIN_UNDETERMINED_SENDER + ); + + verify(bridgeEventLogger, never()).logPeginBtc(any(), any(), any(), anyInt()); + verify(bridgeEventLogger, never()).logReleaseBtcRequested(any(), any(), any()); + verify(bridgeEventLogger, never()).logPegoutTransactionCreated(any(), any()); + verify(provider, never()).setHeightBtcTxhashAlreadyProcessed(any(), anyLong()); + + Assertions.assertTrue(activeFederationUtxos.isEmpty()); + Assertions.assertTrue(retiringFederationUtxos.isEmpty()); + Assertions.assertTrue(pegoutsWaitingForConfirmations.getEntries().isEmpty()); + } + private static Stream common_args() { // before RSKIP379 activation return Stream.of( @@ -265,22 +310,26 @@ private static Stream common_args() { ); } - private static Stream pre_and_post_rskip379_args() { + private static Stream activationsAndShouldUsePegoutIndexArgs() { return Stream.of( // before RSKIP379 activation Arguments.of( fingerrootActivations, false ), - // after RSKIP379 activation but before blockNumber to start using Pegout Index + // after RSKIP379 activation but before using Pegout Index Arguments.of( arrowhead600Activations, false ), - // after RSKIP379 activation and after blockNumber to start using Pegout Index + // after RSKIP379 activation and after start using Pegout Index Arguments.of( arrowhead600Activations, true + ), + Arguments.of( + lovell700Activations, + true ) ); } @@ -971,7 +1020,7 @@ void pegin_multiple_outputs_to_active_fed_sum_amount_equal_to_minimum_pegin( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_to_active_and_retiring_fed( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1008,7 +1057,7 @@ void pegin_to_active_and_retiring_fed( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_to_active_fed_below_minimum_and_retiring_above_minimum( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1044,7 +1093,7 @@ void pegin_to_active_fed_below_minimum_and_retiring_above_minimum( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_to_active_and_retiring_fed_and_unknown_address( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1082,7 +1131,7 @@ void pegin_to_active_and_retiring_fed_and_unknown_address( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_v1_to_retiring_fed_can_be_registered( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1126,7 +1175,7 @@ void pegin_v1_to_retiring_fed_can_be_registered( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_v1_two_rsk_op_return_cannot_be_registered( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1182,7 +1231,7 @@ void pegin_v1_two_rsk_op_return_cannot_be_registered( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_v1_invalid_protocol_legacy_sender_to_active_fed_( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1225,7 +1274,7 @@ void pegin_v1_invalid_protocol_legacy_sender_to_active_fed_( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_v1_invalid_prefix_to_active_fed_can_be_registered( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1268,7 +1317,7 @@ void pegin_v1_invalid_prefix_to_active_fed_can_be_registered( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_v1_segwit_to_retiring_fed_can_be_registered( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1318,7 +1367,55 @@ void pegin_v1_segwit_to_retiring_fed_can_be_registered( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") + void pegin_v1_to_active_fed_with_invalid_payload_and_unknown_sender_cannot_be_processed( + ActivationConfig.ForBlock activations, + boolean shouldUsePegoutTxIndex + ) throws BlockStoreException, BridgeIllegalArgumentException, IOException { + // arrange + btcLockSenderProvider = mock(BtcLockSenderProvider.class); + when(btcLockSenderProvider.tryGetBtcLockSender(any())).thenReturn(Optional.empty()); + + int height = shouldUsePegoutTxIndex ? heightAtWhichToStartUsingPegoutIndex : 1; + + List signers = BitcoinTestUtils.getBtcEcKeysFromSeeds(new String[]{"key1", "key2", "key3"}, true); + + Federation unknownFed = createFederation(bridgeMainnetConstants, signers); + + Coin amountToSend = Coin.COIN; + BtcTransaction btcTransaction = new BtcTransaction(btcMainnetParams); + btcTransaction.addInput( + BitcoinTestUtils.createHash(1), + FIRST_OUTPUT_INDEX, + new Script(new byte[]{}) + ); + + btcTransaction.addOutput(amountToSend, activeFederation.getAddress()); + btcTransaction.addOutput(Coin.ZERO, PegTestUtils.createOpReturnScriptForRskWithCustomPayload(1, new byte[]{})); + + FederationTestUtils.addSignatures(unknownFed, signers, btcTransaction); + + PartialMerkleTree pmt = createPmtAndMockBlockStore(btcTransaction, height); + + // act + BridgeSupport bridgeSupport = buildBridgeSupport(activations); + bridgeSupport.registerBtcTransaction( + rskTx, + btcTransaction.bitcoinSerialize(), + height, + pmt.bitcoinSerialize() + ); + + // assert + if (activations == fingerrootActivations){ + assertLegacyUndeterminedSenderPeginIsRejectedAsPeginV1InvalidPayloadBeforeRSKIP379(btcTransaction); + } else { + assertInvalidPeginV1UndeterminedSenderIsRejected(btcTransaction); + } + } + + @ParameterizedTest + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_from_multisig_to_retiring_fed_can_be_refunded( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1355,11 +1452,11 @@ void pegin_from_multisig_to_retiring_fed_can_be_refunded( ); // assert - assertLegacyMultisigPeginIsRejectedAndRefunded(btcTransaction, amountToSend); + assertPeginIsRejectedAndRefunded(activations, btcTransaction, amountToSend, RejectedPeginReason.LEGACY_PEGIN_MULTISIG_SENDER); } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_to_retiring_fed_cannot_be_processed( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1399,15 +1496,15 @@ void pegin_to_retiring_fed_cannot_be_processed( ); // assert - if (activations == arrowhead600Activations){ - assertLegacyUndeterminedSenderPeginIsRejected(btcTransaction); - } else { + if (activations == fingerrootActivations){ assertLegacyUndeterminedSenderPeginIsRejectedAsPeginV1InvalidPayloadBeforeRSKIP379(btcTransaction); + } else { + assertLegacyUndeterminedSenderPeginIsRejected(btcTransaction); } } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void pegin_legacy_from_segwit_to_active_fed_cannot_be_processed( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1453,23 +1550,20 @@ void pegin_legacy_from_segwit_to_active_fed_cannot_be_processed( // assert // SINCE RSKIP379 ONLY TRANSACTIONS THAT REALLY ARE PROCESSED, REFUNDS OR REGISTER WILL BE MARK AS PROCESSED. - if (activations == arrowhead600Activations){ - assertLegacyUndeterminedSenderPeginIsRejected(btcTransaction); - } else { + if (activations == fingerrootActivations){ assertLegacyUndeterminedSenderPeginIsRejectedAsPeginV1InvalidPayloadBeforeRSKIP379(btcTransaction); + } else { + assertLegacyUndeterminedSenderPeginIsRejected(btcTransaction); } } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") - void invalid_pegin_v1_from_multisig_to_active_fed_cannot_be_processed( + @MethodSource("activationsAndShouldUsePegoutIndexArgs") + void invalid_pegin_v1_from_multisig_to_active_fed_can_be_refunded( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex ) throws BlockStoreException, BridgeIllegalArgumentException, IOException { // arrange - btcLockSenderProvider = mock(BtcLockSenderProvider.class); - when(btcLockSenderProvider.tryGetBtcLockSender(any())).thenReturn(Optional.empty()); - int height = shouldUsePegoutTxIndex ? heightAtWhichToStartUsingPegoutIndex : 1; List signers = BitcoinTestUtils.getBtcEcKeysFromSeeds(new String[]{"key1", "key2", "key3"}, true); @@ -1502,11 +1596,7 @@ void invalid_pegin_v1_from_multisig_to_active_fed_cannot_be_processed( ); // assert - if (activations == arrowhead600Activations){ - - } else { - assertLegacyUndeterminedSenderPeginIsRejectedAsPeginV1InvalidPayloadBeforeRSKIP379(btcTransaction); - } + assertPeginIsRejectedAndRefunded(activations, btcTransaction, Coin.COIN, PEGIN_V1_INVALID_PAYLOAD); } // Pegout tests @@ -1838,7 +1928,7 @@ void pegout_one_output_and_many_input( // Migration tests @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void migration_ok( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1929,7 +2019,7 @@ void migration_sighash_no_exists_in_provider() throws BlockStoreException, Bridg } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void migration_many_outputs_and_inputs( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -1997,7 +2087,7 @@ void migration_many_outputs_and_inputs( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void migration_many_outputs_and_one_input( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -2066,7 +2156,7 @@ void migration_many_outputs_and_one_input( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void migration_one_outputs_and_many_input( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -2261,7 +2351,7 @@ else if (activations == arrowhead600Activations && !shouldUsePegoutTxIndex) { } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void flyover_segwit_as_migration_utxo( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -2338,7 +2428,7 @@ void flyover_segwit_as_migration_utxo( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void flyover_segwit_as_migration_utxo_with_many_outputs_and_inputs( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -2424,7 +2514,7 @@ void flyover_segwit_as_migration_utxo_with_many_outputs_and_inputs( // old fed @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void old_fed_migration( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -2506,7 +2596,6 @@ void old_fed_migration( new co.rsk.bitcoinj.core.BtcBlock(btcRegTestsParams, 1, BitcoinTestUtils.createHash(1), blockMerkleRoot, 1, 1, 1, new ArrayList<>()); - mockChainOfStoredBlocks(btcBlockStore, btcBlock, height + BridgeSupportRegisterBtcTransactionTest.bridgeMainnetConstants.getBtc2RskMinimumAcceptableConfirmations(), height); // act @@ -2562,7 +2651,7 @@ void old_fed_migration( // retired fed @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void last_retired_fed_to_active_fed( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -2611,7 +2700,7 @@ void last_retired_fed_to_active_fed( } @ParameterizedTest - @MethodSource("pre_and_post_rskip379_args") + @MethodSource("activationsAndShouldUsePegoutIndexArgs") void no_last_retired_fed_in_storage_sending_funds_to_active_fed( ActivationConfig.ForBlock activations, boolean shouldUsePegoutTxIndex @@ -2661,7 +2750,7 @@ void no_last_retired_fed_in_storage_sending_funds_to_active_fed( Assertions.assertEquals(1, activeFederationUtxos.size()); Assertions.assertTrue(retiringFederationUtxos.isEmpty()); } else { - assertLegacyMultisigPeginIsRejectedAndRefunded(btcTransaction, Coin.COIN); + assertPeginIsRejectedAndRefunded(activations, btcTransaction, Coin.COIN, RejectedPeginReason.LEGACY_PEGIN_MULTISIG_SENDER); } } } diff --git a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java index 5594d4eb3e7..c126763eed6 100644 --- a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java @@ -10,6 +10,8 @@ import co.rsk.crypto.Keccak256; import co.rsk.db.MutableTrieCache; import co.rsk.db.MutableTrieImpl; +import co.rsk.peg.PegoutsWaitingForConfirmations.Entry; +import co.rsk.peg.bitcoin.BitcoinTestUtils; import co.rsk.peg.vote.ABICallSpec; import co.rsk.peg.bitcoin.BitcoinUtils; import co.rsk.peg.bitcoin.NonStandardErpRedeemScriptBuilder; @@ -473,9 +475,19 @@ private void testChangePowpeg( // Assert sigHashes were added for each new migration tx created bridgeSupport.save(); - bridgeStorageProvider.getPegoutsWaitingForConfirmations().getEntries().stream() - .map(PegoutsWaitingForConfirmations.Entry::getBtcTransaction) - .forEach(pegoutTx -> verifyPegoutTxSigHashIndex(activations, bridgeStorageProvider, pegoutTx)); + List pegoutsTxs = bridgeStorageProvider.getPegoutsWaitingForConfirmations() + .getEntries().stream() + .map(Entry::getBtcTransaction).collect(Collectors.toList()); + + pegoutsTxs.stream().peek( + pegoutTx -> verifyPegoutTxSigHashIndex(activations, bridgeStorageProvider, pegoutTx)); + + if (activations.isActive(ConsensusRule.RSKIP428)) { + pegoutsTxs.stream() + .peek(pegoutTx -> verifyPegoutTransactionCreatedEvent(bridgeEventLogger, pegoutTx)); + } else { + verify(bridgeEventLogger, never()).logPegoutTransactionCreated(any(), any()); + } verifyPegouts(bridgeStorageProvider); @@ -607,6 +619,10 @@ private void testChangePowpeg( } } + private void verifyPegoutTransactionCreatedEvent(BridgeEventLogger bridgeEventLogger, BtcTransaction pegoutTx) { + verify(bridgeEventLogger, times(1)).logPegoutTransactionCreated(pegoutTx.getHash(), BitcoinTestUtils.extractOutpointValues(pegoutTx)); + } + private void verifyPegoutTxSigHashIndex(ActivationConfig.ForBlock activations, BridgeStorageProvider bridgeStorageProvider, BtcTransaction pegoutTx) { Optional lastPegoutSigHash = BitcoinUtils.getFirstInputSigHash(pegoutTx); assertTrue(lastPegoutSigHash.isPresent()); @@ -763,6 +779,12 @@ private void testPegouts( // Assert sigHash was added verifyPegoutTxSigHashIndex(activations, bridgeStorageProvider, lastPegout.getBtcTransaction()); + if (activations.isActive(ConsensusRule.RSKIP428)) { + verifyPegoutTransactionCreatedEvent(bridgeEventLogger, lastPegout.getBtcTransaction()); + } else { + verify(bridgeEventLogger, never()).logPegoutTransactionCreated(any(), any()); + } + // Confirm the peg-outs blockNumber = blockNumber + bridgeConstants.getRsk2BtcMinimumAcceptableConfirmations(); Block confirmedPegoutBlock = mock(Block.class); @@ -1595,6 +1617,54 @@ void test_change_powpeg_from_p2shErpFederation_with_mainnet_powpeg_post_RSKIP_37 ); } + @Test + void test_change_powpeg_from_p2shErpFederation_with_mainnet_powpeg_post_RSKIP428_pegout_transaction_created_event() throws Exception { + ActivationConfig.ForBlock activations = ActivationConfigsForTest + .lovell700() + .forBlock(0); + + Address originalPowpegAddress = Address.fromBase58( + bridgeConstants.getBtcParams(), + "3AboaP7AAJs4us95cWHxK4oRELmb4y7Pa7" + ); + List utxos = createRandomUtxos(originalPowpegAddress); + + List> newPowPegKeys = new ArrayList<>(); + newPowPegKeys.add(Triple.of( + BtcECKey.fromPublicOnly(Hex.decode("020ace50bab1230f8002a0bfe619482af74b338cc9e4c956add228df47e6adae1c")), + ECKey.fromPublicOnly(Hex.decode("0305a99716bcdbb4c0686906e77daf8f7e59e769d1f358a88a23e3552376f14ed2")), + ECKey.fromPublicOnly(Hex.decode("02be1c54e8582e744d0d5d6a9b8e4a6d810029bcefc30e39b54688c4f1b718c0ee")) + )); + newPowPegKeys.add(Triple.of( + BtcECKey.fromPublicOnly(Hex.decode("0231a395e332dde8688800a0025cccc5771ea1aa874a633b8ab6e5c89d300c7c36")), + ECKey.fromPublicOnly(Hex.decode("02e3f03aa985357dc356c2a763b44310b22be3b960303a67cde948fcfba97f5309")), + ECKey.fromPublicOnly(Hex.decode("029963d972f8a4ccac4bad60ed8b20ec83f6a15ca7076e057cccb4a34eed1a14d0")) + )); + newPowPegKeys.add(Triple.of( + BtcECKey.fromPublicOnly(Hex.decode("025093f439fb8006fd29ab56605ffec9cdc840d16d2361004e1337a2f86d8bd2db")), + ECKey.fromPublicOnly(Hex.decode("02be5d357d62be7b2d42de0343d1297129a0a8b5f6b8bb8c46eefc9504db7b56e1")), + ECKey.fromPublicOnly(Hex.decode("032706b02f64b38b4ef7c75875aaf65de868c4aa0d2d042f724e16924fa13ffa6c")) + )); + + Address newPowPegAddress = Address.fromBase58( + bridgeConstants.getBtcParams(), + "3BqwgR9sxEsKUaApV6zJ5eU7DnabjjCvSU" + ); + + testChangePowpeg( + FederationType.p2shErp, + getMainnetPowpegKeys(), + originalPowpegAddress, + utxos, + FederationType.p2shErp, + newPowPegKeys, + newPowPegAddress, + bridgeConstants, + activations, + bridgeConstants.getFundsMigrationAgeSinceActivationEnd(activations) + ); + } + private enum FederationType { nonStandardErp, p2shErp, diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java index cec1e22ccaa..6b166260385 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java @@ -2,6 +2,7 @@ import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; +import co.rsk.bitcoinj.core.BtcTransaction; import co.rsk.bitcoinj.core.Coin; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.bitcoinj.core.Sha256Hash; @@ -78,4 +79,9 @@ public static List coinListOf(long ... values) { .mapToObj(Coin::valueOf) .collect(Collectors.toList()); } + + public static List extractOutpointValues(BtcTransaction btcTransaction) { + return btcTransaction.getInputs().stream().map(TransactionInput::getValue).collect( + Collectors.toList()); + } } diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java index 952f38832d4..fdcebfe6771 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java @@ -4,6 +4,7 @@ import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; import co.rsk.peg.PegTestUtils; + import static org.junit.jupiter.api.Assertions.*; import co.rsk.peg.pegin.RejectedPeginReason; @@ -33,7 +34,8 @@ void setup() { @Test void testLogLockBtc() { assertThrows(UnsupportedOperationException.class, () -> { - eventLogger.logLockBtc(mock(RskAddress.class), btcTxMock, mock(Address.class), Coin.SATOSHI); + eventLogger.logLockBtc(mock(RskAddress.class), btcTxMock, mock(Address.class), + Coin.SATOSHI); }); } @@ -62,7 +64,8 @@ void testLogRejectedPegin() { @Test void testLogUnrefundablePegin() { assertThrows(UnsupportedOperationException.class, () -> { - eventLogger.logUnrefundablePegin(btcTxMock, UnrefundablePeginReason.LEGACY_PEGIN_UNDETERMINED_SENDER); + eventLogger.logUnrefundablePegin(btcTxMock, + UnrefundablePeginReason.LEGACY_PEGIN_UNDETERMINED_SENDER); }); } @@ -70,7 +73,8 @@ void testLogUnrefundablePegin() { void testLogReleaseBtcRequestReceived() { String sender = "0x00000000000000000000000000000000000000"; String base58Address = "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn"; - Address btcDestinationAddress = Address.fromBase58(NetworkParameters.fromID(NetworkParameters.ID_REGTEST), base58Address); + Address btcDestinationAddress = Address.fromBase58( + NetworkParameters.fromID(NetworkParameters.ID_REGTEST), base58Address); Coin amount = Coin.COIN; assertThrows(UnsupportedOperationException.class, () -> { eventLogger.logReleaseBtcRequestReceived(sender, btcDestinationAddress, amount); @@ -107,8 +111,8 @@ void logPegoutConfirmed() { @Test void logPegoutTransactionCreated() { Sha256Hash btcTxHash = btcTxMock.getHash(); - assertThrows(UnsupportedOperationException.class, () -> { - eventLogger.logPegoutTransactionCreated(btcTxHash, Arrays.asList(Coin.COIN, Coin.SATOSHI, Coin.FIFTY_COINS)); - }); + assertThrows(UnsupportedOperationException.class, + () -> eventLogger.logPegoutTransactionCreated(btcTxHash, + Arrays.asList(Coin.COIN, Coin.SATOSHI, Coin.FIFTY_COINS))); } } From da6a3ea8d6bc69d49553804a5868fb3e805a74ec Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Tue, 21 May 2024 15:08:37 -0400 Subject: [PATCH 15/20] Refactored processPegoutsInBatch partially to fix sonar issue --- .../main/java/co/rsk/peg/BridgeSupport.java | 112 ++++++++++-------- .../rsk/peg/utils/BridgeEventLoggerTest.java | 4 +- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java index 065405816c3..cd6275d2c0c 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -1197,70 +1197,78 @@ private void processPegoutsInBatch( long currentBlockNumber = rskExecutionBlock.getNumber(); long nextPegoutCreationBlockNumber = getNextPegoutCreationBlockNumber(); - if (currentBlockNumber >= nextPegoutCreationBlockNumber) { - List pegoutEntries = pegoutRequests.getEntries(); - Coin totalPegoutValue = pegoutEntries - .stream() - .map(ReleaseRequestQueue.Entry::getAmount) - .reduce(Coin.ZERO, Coin::add); - - if (wallet.getBalance().isLessThan(totalPegoutValue)) { - logger.warn("[processPegoutsInBatch] wallet balance {} is less than the totalPegoutValue {}", wallet.getBalance(), totalPegoutValue); - return; - } - - if (!pegoutEntries.isEmpty()) { - logger.info("[processPegoutsInBatch] going to create a batched pegout transaction for {} requests, total amount {}", pegoutEntries.size(), totalPegoutValue); - ReleaseTransactionBuilder.BuildResult result = txBuilder.buildBatchedPegouts(pegoutEntries); + if (isCurrentBlockBelowNextPegoutCreationBlockNumber(currentBlockNumber, nextPegoutCreationBlockNumber)) { + return; + } + + List pegoutEntries = pegoutRequests.getEntries(); + Coin totalPegoutValue = pegoutEntries + .stream() + .map(ReleaseRequestQueue.Entry::getAmount) + .reduce(Coin.ZERO, Coin::add); - while (pegoutEntries.size() > 1 && result.getResponseCode() == ReleaseTransactionBuilder.Response.EXCEED_MAX_TRANSACTION_SIZE) { - logger.info("[processPegoutsInBatch] Max size exceeded, going to divide {} requests in half", pegoutEntries.size()); - int firstHalfSize = pegoutEntries.size() / 2; - pegoutEntries = pegoutEntries.subList(0, firstHalfSize); - result = txBuilder.buildBatchedPegouts(pegoutEntries); - } + if (wallet.getBalance().isLessThan(totalPegoutValue)) { + logger.warn("[processPegoutsInBatch] wallet balance {} is less than the totalPegoutValue {}", wallet.getBalance(), totalPegoutValue); + return; + } - if (result.getResponseCode() != ReleaseTransactionBuilder.Response.SUCCESS) { - logger.warn( - "Couldn't build a pegout BTC tx for {} pending requests (total amount: {}), Reason: {}", - pegoutRequests.getEntries().size(), - totalPegoutValue, - result.getResponseCode()); - return; - } + if (pegoutEntries.isEmpty()) { + logger.warn("[processPegoutsInBatch] No pegout requests to process"); + } else { + logger.info("[processPegoutsInBatch] going to create a batched pegout transaction for {} requests, total amount {}", pegoutEntries.size(), totalPegoutValue); + ReleaseTransactionBuilder.BuildResult result = txBuilder.buildBatchedPegouts(pegoutEntries); + + while (pegoutEntries.size() > 1 && result.getResponseCode() == ReleaseTransactionBuilder.Response.EXCEED_MAX_TRANSACTION_SIZE) { + logger.info("[processPegoutsInBatch] Max size exceeded, going to divide {} requests in half", pegoutEntries.size()); + int firstHalfSize = pegoutEntries.size() / 2; + pegoutEntries = pegoutEntries.subList(0, firstHalfSize); + result = txBuilder.buildBatchedPegouts(pegoutEntries); + } - logger.info("[processPegoutsInBatch] pegouts processed with btcTx hash {} and response code {}", result.getBtcTx().getHash(), result.getResponseCode()); + if (result.getResponseCode() != ReleaseTransactionBuilder.Response.SUCCESS) { + logger.warn( + "Couldn't build a pegout BTC tx for {} pending requests (total amount: {}), Reason: {}", + pegoutRequests.getEntries().size(), + totalPegoutValue, + result.getResponseCode()); + return; + } - BtcTransaction batchPegoutTransaction = result.getBtcTx(); - addToPegoutsWaitingForConfirmations(batchPegoutTransaction, pegoutsWaitingForConfirmations, rskTx.getHash(), totalPegoutValue); - savePegoutTxSigHash(batchPegoutTransaction); + logger.info("[processPegoutsInBatch] pegouts processed with btcTx hash {} and response code {}", result.getBtcTx().getHash(), result.getResponseCode()); - // Remove batched requests from the queue after successfully batching pegouts - pegoutRequests.removeEntries(pegoutEntries); + BtcTransaction batchPegoutTransaction = result.getBtcTx(); + addToPegoutsWaitingForConfirmations(batchPegoutTransaction, pegoutsWaitingForConfirmations, rskTx.getHash(), totalPegoutValue); + savePegoutTxSigHash(batchPegoutTransaction); - // Mark UTXOs as spent - List selectedUTXOs = result.getSelectedUTXOs(); - logger.debug("[processPegoutsInBatch] used {} UTXOs for this pegout", selectedUTXOs.size()); - availableUTXOs.removeAll(selectedUTXOs); + // Remove batched requests from the queue after successfully batching pegouts + pegoutRequests.removeEntries(pegoutEntries); - eventLogger.logBatchPegoutCreated(batchPegoutTransaction.getHash(), - pegoutEntries.stream().map(ReleaseRequestQueue.Entry::getRskTxHash).collect(Collectors.toList())); + // Mark UTXOs as spent + List selectedUTXOs = result.getSelectedUTXOs(); + logger.debug("[processPegoutsInBatch] used {} UTXOs for this pegout", selectedUTXOs.size()); + availableUTXOs.removeAll(selectedUTXOs); - if (activations.isActive(RSKIP428)) { - List outpointValues = extractOutpointValues(batchPegoutTransaction); - eventLogger.logPegoutTransactionCreated(batchPegoutTransaction.getHash(), outpointValues); - } + eventLogger.logBatchPegoutCreated(batchPegoutTransaction.getHash(), + pegoutEntries.stream().map(ReleaseRequestQueue.Entry::getRskTxHash).collect(Collectors.toList())); - adjustBalancesIfChangeOutputWasDust(batchPegoutTransaction, totalPegoutValue, wallet); + if (activations.isActive(RSKIP428)) { + List outpointValues = extractOutpointValues(batchPegoutTransaction); + eventLogger.logPegoutTransactionCreated(batchPegoutTransaction.getHash(), outpointValues); } - // update next Pegout height even if there were no request in queue - if (pegoutRequests.getEntries().isEmpty()) { - long nextPegoutHeight = currentBlockNumber + bridgeConstants.getNumberOfBlocksBetweenPegouts(); - provider.setNextPegoutHeight(nextPegoutHeight); - logger.info("[processPegoutsInBatch] Next Pegout Height updated from {} to {}", currentBlockNumber, nextPegoutHeight); - } + adjustBalancesIfChangeOutputWasDust(batchPegoutTransaction, totalPegoutValue, wallet); } + + // update next Pegout height even if there were no request in queue + if (pegoutRequests.getEntries().isEmpty()) { + long nextPegoutHeight = currentBlockNumber + bridgeConstants.getNumberOfBlocksBetweenPegouts(); + provider.setNextPegoutHeight(nextPegoutHeight); + logger.info("[processPegoutsInBatch] Next Pegout Height updated from {} to {}", currentBlockNumber, nextPegoutHeight); + } + } + + private static boolean isCurrentBlockBelowNextPegoutCreationBlockNumber(long currentBlockNumber, long nextPegoutCreationBlockNumber) { + return currentBlockNumber < nextPegoutCreationBlockNumber; } private void savePegoutTxSigHash(BtcTransaction pegoutTx) { diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java index fdcebfe6771..4fdc3281252 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerTest.java @@ -111,8 +111,8 @@ void logPegoutConfirmed() { @Test void logPegoutTransactionCreated() { Sha256Hash btcTxHash = btcTxMock.getHash(); + List outpointValues = Arrays.asList(Coin.COIN, Coin.SATOSHI, Coin.FIFTY_COINS); assertThrows(UnsupportedOperationException.class, - () -> eventLogger.logPegoutTransactionCreated(btcTxHash, - Arrays.asList(Coin.COIN, Coin.SATOSHI, Coin.FIFTY_COINS))); + () -> eventLogger.logPegoutTransactionCreated(btcTxHash, outpointValues)); } } From c18fec359d83162b679c7c2c5e474f1404c09b6b Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Wed, 22 May 2024 15:07:19 -0400 Subject: [PATCH 16/20] - Change test method names to use new standard - Set variables for parameter values --- ...geSupportPegoutTransactionCreatedEventTest.java | 6 +++--- .../BridgeSupportRegisterBtcTransactionTest.java | 8 +++----- .../test/java/co/rsk/peg/PowpegMigrationTest.java | 4 +++- .../java/co/rsk/peg/bitcoin/BitcoinTestUtils.java | 14 +++++++++----- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java index ab5fbf7639c..9c3116991d2 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java @@ -79,7 +79,7 @@ private static Stream pegoutTransactionCreatedEventArgsProvider() { @ParameterizedTest @MethodSource("pegoutTransactionCreatedEventArgsProvider") - void test_pegoutTransactionCreatedEvent_when_pegout_batch_is_created(ActivationConfig.ForBlock activations) throws IOException { + void updateCollections_whenPegoutBatchIsCreated_shouldLogPegoutTransactionCreatedEvent(ActivationConfig.ForBlock activations) throws IOException { // Arrange List fedUTXOs = PegTestUtils.createUTXOs( 10, @@ -138,7 +138,7 @@ void test_pegoutTransactionCreatedEvent_when_pegout_batch_is_created(ActivationC @ParameterizedTest @MethodSource("pegoutTransactionCreatedEventArgsProvider") - void test_pegoutTransactionCreatedEvent_when_migration_tx_is_created(ActivationConfig.ForBlock activations) throws IOException { + void updateCollections_whenPegoutMigrationIsCreated_shouldLogPegoutTransactionCreatedEvent(ActivationConfig.ForBlock activations) throws IOException { // Arrange when(provider.getReleaseRequestQueue()) .thenReturn(new ReleaseRequestQueue(Collections.emptyList())); @@ -212,7 +212,7 @@ void test_pegoutTransactionCreatedEvent_when_migration_tx_is_created(ActivationC @ParameterizedTest @MethodSource("pegoutTransactionCreatedEventArgsProvider") - void test_pegoutTransactionCreatedEvent_when_migration_and_pegout_batch_tx_are_created(ActivationConfig.ForBlock activations) throws IOException { + void updateCollections_whenPegoutMigrationAndBatchAreCreated_shouldLogPegoutTransactionCreatedEvent(ActivationConfig.ForBlock activations) throws IOException { // Arrange PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations = provider.getPegoutsWaitingForConfirmations(); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java index 130ba0f803b..babdba56569 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java @@ -199,16 +199,14 @@ private void assertPeginIsRejectedAndRefunded(ActivationConfig.ForBlock activati verify(bridgeEventLogger, times(1)).logRejectedPegin(btcTransaction, expectedRejectedPeginReason); verify(bridgeEventLogger, times(1)).logReleaseBtcRequested(rskTx.getHash().getBytes(), refundPegout, sentAmount); + verify(provider, times(1)).setHeightBtcTxhashAlreadyProcessed(btcTransaction.getHash(false), rskExecutionBlock.getNumber()); + verify(provider, never()).setPegoutTxSigHash(any()); + if(activations == lovell700Activations) { verify(bridgeEventLogger, times(1)).logPegoutTransactionCreated(refundPegoutHash, refundPegoutOutpointValues); } else { verify(bridgeEventLogger, never()).logPegoutTransactionCreated(any(), any()); } - - verify(provider, times(1)).setHeightBtcTxhashAlreadyProcessed(btcTransaction.getHash(false), rskExecutionBlock.getNumber()); - verify(provider, never()).setPegoutTxSigHash(any()); - - } // Before arrowhead600Activations is activated diff --git a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java index c126763eed6..24b5be1d979 100644 --- a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java @@ -620,7 +620,9 @@ private void testChangePowpeg( } private void verifyPegoutTransactionCreatedEvent(BridgeEventLogger bridgeEventLogger, BtcTransaction pegoutTx) { - verify(bridgeEventLogger, times(1)).logPegoutTransactionCreated(pegoutTx.getHash(), BitcoinTestUtils.extractOutpointValues(pegoutTx)); + Sha256Hash pegoutTxHash = pegoutTx.getHash(); + List outpointValues = BitcoinTestUtils.extractOutpointValues(pegoutTx); + verify(bridgeEventLogger, times(1)).logPegoutTransactionCreated(pegoutTxHash, outpointValues); } private void verifyPegoutTxSigHashIndex(ActivationConfig.ForBlock activations, BridgeStorageProvider bridgeStorageProvider, BtcTransaction pegoutTx) { diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java index 6b166260385..a3d86744e4d 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java @@ -26,7 +26,8 @@ public class BitcoinTestUtils { public static List getBtcEcKeysFromSeeds(String[] seeds, boolean sorted) { List keys = Arrays .stream(seeds) - .map(seed -> BtcECKey.fromPrivate(HashUtil.keccak256(seed.getBytes(StandardCharsets.UTF_8)))) + .map(seed -> BtcECKey.fromPrivate( + HashUtil.keccak256(seed.getBytes(StandardCharsets.UTF_8)))) .collect(Collectors.toList()); if (sorted) { @@ -37,11 +38,13 @@ public static List getBtcEcKeysFromSeeds(String[] seeds, boolean sorte } public static Address createP2PKHAddress(NetworkParameters networkParameters, String seed) { - BtcECKey key = BtcECKey.fromPrivate(HashUtil.keccak256(seed.getBytes(StandardCharsets.UTF_8))); + BtcECKey key = BtcECKey.fromPrivate( + HashUtil.keccak256(seed.getBytes(StandardCharsets.UTF_8))); return key.toAddress(networkParameters); } - public static Address createP2SHMultisigAddress(NetworkParameters networkParameters, List keys) { + public static Address createP2SHMultisigAddress(NetworkParameters networkParameters, + List keys) { Script redeemScript = ScriptBuilder.createRedeemScript((keys.size() / 2) + 1, keys); Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); @@ -58,7 +61,8 @@ public static Sha256Hash createHash(int nHash) { return Sha256Hash.wrap(bytes); } - public static List extractSignaturesFromTxInput(TransactionInput txInput) { + public static List extractSignaturesFromTxInput( + TransactionInput txInput) { Script scriptSig = txInput.getScriptSig(); List chunks = scriptSig.getChunks(); Script redeemScript = new Script(chunks.get(chunks.size() - 1).data); @@ -74,7 +78,7 @@ public static List extractSignaturesFromTxInput(Transac return signatures; } - public static List coinListOf(long ... values) { + public static List coinListOf(long... values) { return Arrays.stream(values) .mapToObj(Coin::valueOf) .collect(Collectors.toList()); From 4cf6024b1b76576c42852014880d943761530684 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Thu, 23 May 2024 14:27:08 -0400 Subject: [PATCH 17/20] Rebased onto master --- ...idgeSupportPegoutTransactionCreatedEventTest.java | 12 +++++++----- .../peg/BridgeSupportRegisterBtcTransactionTest.java | 3 +++ .../test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java index 9c3116991d2..3dd96b161a1 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java @@ -19,10 +19,10 @@ import co.rsk.bitcoinj.core.UTXO; import co.rsk.bitcoinj.wallet.Wallet; import co.rsk.blockchain.utils.BlockGenerator; -import co.rsk.config.BridgeConstants; -import co.rsk.config.BridgeMainNetConstants; import co.rsk.crypto.Keccak256; import co.rsk.peg.ReleaseRequestQueue.Entry; +import co.rsk.peg.constants.BridgeConstants; +import co.rsk.peg.constants.BridgeMainNetConstants; import co.rsk.peg.federation.Federation; import co.rsk.peg.federation.FederationArgs; import co.rsk.peg.federation.FederationFactory; @@ -50,6 +50,8 @@ class BridgeSupportPegoutTransactionCreatedEventTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); + public static final Federation GENESIS_FEDERATION = FederationTestUtils.getGenesisFederation( + bridgeMainnetConstants); private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); private BridgeStorageProvider provider; @@ -83,7 +85,7 @@ void updateCollections_whenPegoutBatchIsCreated_shouldLogPegoutTransactionCreate // Arrange List fedUTXOs = PegTestUtils.createUTXOs( 10, - bridgeMainnetConstants.getGenesisFederation().getAddress() + GENESIS_FEDERATION.getAddress() ); when(provider.getNewFederationBtcUTXOs()) .thenReturn(fedUTXOs); @@ -145,7 +147,7 @@ void updateCollections_whenPegoutMigrationIsCreated_shouldLogPegoutTransactionCr PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations = provider.getPegoutsWaitingForConfirmations(); - Federation oldFederation = bridgeMainnetConstants.getGenesisFederation(); + Federation oldFederation = GENESIS_FEDERATION; long newFedCreationBlockNumber = 5L; FederationArgs newFederationArgs = new FederationArgs(FederationTestUtils.getFederationMembers(1), @@ -220,7 +222,7 @@ void updateCollections_whenPegoutMigrationAndBatchAreCreated_shouldLogPegoutTran when(provider.getReleaseRequestQueue()) .thenReturn(new ReleaseRequestQueue(pegoutRequests)); - Federation oldFederation = bridgeMainnetConstants.getGenesisFederation(); + Federation oldFederation = GENESIS_FEDERATION; long newFedCreationBlockNumber = 5L; FederationArgs newFederationArgs = new FederationArgs(FederationTestUtils.getFederationMembers(1), diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java index babdba56569..2ccdf0247d8 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java @@ -38,6 +38,9 @@ import co.rsk.peg.bitcoin.BitcoinTestUtils; import co.rsk.peg.bitcoin.CoinbaseInformation; import co.rsk.peg.btcLockSender.BtcLockSenderProvider; +import co.rsk.peg.constants.BridgeConstants; +import co.rsk.peg.constants.BridgeMainNetConstants; +import co.rsk.peg.constants.BridgeRegTestConstants; import co.rsk.peg.federation.Federation; import co.rsk.peg.federation.FederationArgs; import co.rsk.peg.federation.FederationFactory; diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java index 96b4bd70f28..2abe688316a 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import co.rsk.bitcoinj.core.Coin; -import co.rsk.config.BridgeMainNetConstants; +import co.rsk.peg.constants.BridgeMainNetConstants; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; From cacb07a6ab0f8be1ad237e54e0bc87a517b96aa8 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Thu, 23 May 2024 14:37:15 -0400 Subject: [PATCH 18/20] - Improve wording of comment for set next pegout creation - Refactored way of checking if RSKIP428 is active and reorganize code accordingly - Format long lines - Get rid of isCurrentBlockBelowNextPegoutCreationBlockNumber method - Replace peek for forEach --- .../main/java/co/rsk/peg/BridgeSupport.java | 73 ++++++++++--------- ...idgeSupportRegisterBtcTransactionTest.java | 12 --- .../java/co/rsk/peg/PowpegMigrationTest.java | 7 +- 3 files changed, 41 insertions(+), 51 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java index cd6275d2c0c..fdaf9559f65 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -1067,11 +1067,6 @@ private void migrateFunds( pegoutsWaitingForConfirmations.add(migrationTransaction, rskExecutionBlock.getNumber()); } - if (activations.isActive(RSKIP428)) { - List outpointValues = extractOutpointValues(migrationTransaction); - eventLogger.logPegoutTransactionCreated(migrationTransaction.getHash(), outpointValues); - } - // Store pegoutTxSigHash to be able to identify the tx type savePegoutTxSigHash(migrationTransaction); @@ -1079,6 +1074,13 @@ private void migrateFunds( availableUTXOs.removeIf(utxo -> selectedUTXOs.stream().anyMatch(selectedUtxo -> utxo.getHash().equals(selectedUtxo.getHash()) && utxo.getIndex() == selectedUtxo.getIndex() )); + + if (!activations.isActive(RSKIP428)) { + return; + } + + List outpointValues = extractOutpointValues(migrationTransaction); + eventLogger.logPegoutTransactionCreated(migrationTransaction.getHash(), outpointValues); } private List extractOutpointValues(BtcTransaction generatedTransaction) { @@ -1197,7 +1199,7 @@ private void processPegoutsInBatch( long currentBlockNumber = rskExecutionBlock.getNumber(); long nextPegoutCreationBlockNumber = getNextPegoutCreationBlockNumber(); - if (isCurrentBlockBelowNextPegoutCreationBlockNumber(currentBlockNumber, nextPegoutCreationBlockNumber)) { + if (currentBlockNumber < nextPegoutCreationBlockNumber) { return; } @@ -1212,9 +1214,7 @@ private void processPegoutsInBatch( return; } - if (pegoutEntries.isEmpty()) { - logger.warn("[processPegoutsInBatch] No pegout requests to process"); - } else { + if (!pegoutEntries.isEmpty()) { logger.info("[processPegoutsInBatch] going to create a batched pegout transaction for {} requests, total amount {}", pegoutEntries.size(), totalPegoutValue); ReleaseTransactionBuilder.BuildResult result = txBuilder.buildBatchedPegouts(pegoutEntries); @@ -1234,10 +1234,13 @@ private void processPegoutsInBatch( return; } - logger.info("[processPegoutsInBatch] pegouts processed with btcTx hash {} and response code {}", result.getBtcTx().getHash(), result.getResponseCode()); + logger.info( + "[processPegoutsInBatch] pegouts processed with btcTx hash {} and response code {}", + result.getBtcTx().getHash(), result.getResponseCode()); BtcTransaction batchPegoutTransaction = result.getBtcTx(); - addToPegoutsWaitingForConfirmations(batchPegoutTransaction, pegoutsWaitingForConfirmations, rskTx.getHash(), totalPegoutValue); + addToPegoutsWaitingForConfirmations(batchPegoutTransaction, + pegoutsWaitingForConfirmations, rskTx.getHash(), totalPegoutValue); savePegoutTxSigHash(batchPegoutTransaction); // Remove batched requests from the queue after successfully batching pegouts @@ -1259,7 +1262,7 @@ private void processPegoutsInBatch( adjustBalancesIfChangeOutputWasDust(batchPegoutTransaction, totalPegoutValue, wallet); } - // update next Pegout height even if there were no request in queue + // set the next pegout creation block number when there are no pending pegout requests to be processed or they have been already processed if (pegoutRequests.getEntries().isEmpty()) { long nextPegoutHeight = currentBlockNumber + bridgeConstants.getNumberOfBlocksBetweenPegouts(); provider.setNextPegoutHeight(nextPegoutHeight); @@ -1267,10 +1270,6 @@ private void processPegoutsInBatch( } } - private static boolean isCurrentBlockBelowNextPegoutCreationBlockNumber(long currentBlockNumber, long nextPegoutCreationBlockNumber) { - return currentBlockNumber < nextPegoutCreationBlockNumber; - } - private void savePegoutTxSigHash(BtcTransaction pegoutTx) { if (!activations.isActive(ConsensusRule.RSKIP379)){ return; @@ -3242,26 +3241,7 @@ private void generateRejectionRelease( ); ReleaseTransactionBuilder.BuildResult buildReturnResult = txBuilder.buildEmptyWalletTo(btcRefundAddress); - if (buildReturnResult.getResponseCode() == ReleaseTransactionBuilder.Response.SUCCESS) { - BtcTransaction refundPegoutTransaction = buildReturnResult.getBtcTx(); - if (activations.isActive(ConsensusRule.RSKIP146)) { - provider.getPegoutsWaitingForConfirmations().add(refundPegoutTransaction, rskExecutionBlock.getNumber(), rskTxHash); - eventLogger.logReleaseBtcRequested(rskTxHash.getBytes(), refundPegoutTransaction, totalAmount); - - if (activations.isActive(RSKIP428)) { - List outpointValues = extractOutpointValues(refundPegoutTransaction); - eventLogger.logPegoutTransactionCreated(refundPegoutTransaction.getHash(), outpointValues); - } - } else { - provider.getPegoutsWaitingForConfirmations().add(refundPegoutTransaction, rskExecutionBlock.getNumber()); - } - logger.info( - "[generateRejectionRelease] Rejecting peg-in tx built successfully: Refund to address: {}. RskTxHash: {}. Value {}.", - btcRefundAddress, - rskTxHash, - totalAmount - ); - } else { + if (buildReturnResult.getResponseCode() != ReleaseTransactionBuilder.Response.SUCCESS) { logger.warn( "[generateRejectionRelease] Rejecting peg-in tx could not be built due to {}: Btc peg-in txHash {}. Refund to address: {}. RskTxHash: {}. Value: {}", buildReturnResult.getResponseCode(), @@ -3271,7 +3251,28 @@ private void generateRejectionRelease( totalAmount ); panicProcessor.panic("peg-in-refund", String.format("peg-in money return tx build for btc tx %s error. Return was to %s. Tx %s. Value %s. Reason %s", btcTx.getHash(), btcRefundAddress, rskTxHash, totalAmount, buildReturnResult.getResponseCode())); + return; } + + BtcTransaction refundPegoutTransaction = buildReturnResult.getBtcTx(); + if (activations.isActive(ConsensusRule.RSKIP146)) { + provider.getPegoutsWaitingForConfirmations().add(refundPegoutTransaction, rskExecutionBlock.getNumber(), rskTxHash); + eventLogger.logReleaseBtcRequested(rskTxHash.getBytes(), refundPegoutTransaction, totalAmount); + } else { + provider.getPegoutsWaitingForConfirmations().add(refundPegoutTransaction, rskExecutionBlock.getNumber()); + } + + if (activations.isActive(RSKIP428)) { + List outpointValues = extractOutpointValues(refundPegoutTransaction); + eventLogger.logPegoutTransactionCreated(refundPegoutTransaction.getHash(), outpointValues); + } + + logger.info( + "[generateRejectionRelease] Rejecting peg-in tx built successfully: Refund to address: {}. RskTxHash: {}. Value {}.", + btcRefundAddress, + rskTxHash, + totalAmount + ); } private void generateRejectionRelease( diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java index 2ccdf0247d8..8903eb0df75 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java @@ -81,19 +81,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.io.IOException; -import java.math.BigInteger; -import java.time.Instant; -import java.util.*; -import java.util.stream.Stream; - -import static co.rsk.peg.BridgeSupportTestUtil.mockChainOfStoredBlocks; -import static co.rsk.peg.PegTestUtils.*; import static co.rsk.peg.bitcoin.BitcoinTestUtils.extractOutpointValues; -import static co.rsk.peg.pegin.RejectedPeginReason.*; -import static co.rsk.peg.utils.UnrefundablePeginReason.LEGACY_PEGIN_UNDETERMINED_SENDER; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; class BridgeSupportRegisterBtcTransactionTest { diff --git a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java index 24b5be1d979..0442b38f6e7 100644 --- a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java @@ -475,16 +475,17 @@ private void testChangePowpeg( // Assert sigHashes were added for each new migration tx created bridgeSupport.save(); + List pegoutsTxs = bridgeStorageProvider.getPegoutsWaitingForConfirmations() .getEntries().stream() .map(Entry::getBtcTransaction).collect(Collectors.toList()); - pegoutsTxs.stream().peek( + pegoutsTxs.forEach( pegoutTx -> verifyPegoutTxSigHashIndex(activations, bridgeStorageProvider, pegoutTx)); if (activations.isActive(ConsensusRule.RSKIP428)) { - pegoutsTxs.stream() - .peek(pegoutTx -> verifyPegoutTransactionCreatedEvent(bridgeEventLogger, pegoutTx)); + pegoutsTxs + .forEach(pegoutTx -> verifyPegoutTransactionCreatedEvent(bridgeEventLogger, pegoutTx)); } else { verify(bridgeEventLogger, never()).logPegoutTransactionCreated(any(), any()); } From 42ac32d66e8a887f070f2a81b07fff80e3491592 Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Mon, 27 May 2024 03:32:56 -0400 Subject: [PATCH 19/20] - Refactored pegout transaction created event tests - Moved extractOutpointValues method to UtxoUtils class - Created test util method to get an ERP Federation for testing --- .../main/java/co/rsk/peg/BridgeSupport.java | 5 +- .../java/co/rsk/peg/bitcoin/UtxoUtils.java | 12 +++ ...portPegoutTransactionCreatedEventTest.java | 101 +++++++++--------- ...idgeSupportRegisterBtcTransactionTest.java | 3 +- .../test/java/co/rsk/peg/PegTestUtils.java | 53 +++++++-- .../java/co/rsk/peg/PowpegMigrationTest.java | 4 +- .../co/rsk/peg/bitcoin/BitcoinTestUtils.java | 29 +++-- .../co/rsk/peg/bitcoin/UtxoUtilsTest.java | 87 ++++++++++++++- .../peg/federation/FederationTestUtils.java | 32 ++++++ 9 files changed, 250 insertions(+), 76 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java index fdaf9559f65..b291258a0e3 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -19,6 +19,7 @@ import static co.rsk.peg.BridgeUtils.getRegularPegoutTxSize; import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2; +import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static co.rsk.peg.pegin.RejectedPeginReason.INVALID_AMOUNT; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.*; @@ -1083,10 +1084,6 @@ private void migrateFunds( eventLogger.logPegoutTransactionCreated(migrationTransaction.getHash(), outpointValues); } - private List extractOutpointValues(BtcTransaction generatedTransaction) { - return generatedTransaction.getInputs().stream().map(TransactionInput::getValue).collect(Collectors.toList()); - } - /** * Processes the current btc release request queue * and tries to build btc transactions using (and marking as spent) diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java index 95acbd69905..0704bd24f0c 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/UtxoUtils.java @@ -1,6 +1,8 @@ package co.rsk.peg.bitcoin; +import co.rsk.bitcoinj.core.BtcTransaction; import co.rsk.bitcoinj.core.Coin; +import co.rsk.bitcoinj.core.TransactionInput; import co.rsk.bitcoinj.core.VarInt; import java.io.ByteArrayOutputStream; @@ -8,6 +10,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.spongycastle.util.encoders.Hex; public final class UtxoUtils { @@ -91,4 +94,13 @@ private static void validateOutpointValue(Coin outpointValue) { outpointValue)); } } + + public static List extractOutpointValues(BtcTransaction generatedTransaction) { + if (generatedTransaction == null) { + return Collections.emptyList(); + } + + return generatedTransaction.getInputs().stream().map(TransactionInput::getValue).collect( + Collectors.toList()); + } } diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java index 3dd96b161a1..c8d62d9fe59 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java @@ -1,6 +1,7 @@ package co.rsk.peg; -import static co.rsk.peg.bitcoin.BitcoinTestUtils.extractOutpointValues; +import static co.rsk.peg.bitcoin.BitcoinTestUtils.createUTXOs; +import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -23,6 +24,7 @@ import co.rsk.peg.ReleaseRequestQueue.Entry; import co.rsk.peg.constants.BridgeConstants; import co.rsk.peg.constants.BridgeMainNetConstants; +import co.rsk.peg.federation.ErpFederation; import co.rsk.peg.federation.Federation; import co.rsk.peg.federation.FederationArgs; import co.rsk.peg.federation.FederationFactory; @@ -41,6 +43,7 @@ import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.config.blockchain.upgrades.ConsensusRule; +import org.ethereum.core.Block; import org.ethereum.core.Transaction; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; @@ -50,12 +53,14 @@ class BridgeSupportPegoutTransactionCreatedEventTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); - public static final Federation GENESIS_FEDERATION = FederationTestUtils.getGenesisFederation( - bridgeMainnetConstants); private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); + private static final ErpFederation TEST_ERP_FEDERATION = FederationTestUtils.getTestGenesisErpFederation(btcMainnetParams); private BridgeStorageProvider provider; private BridgeEventLogger eventLogger; + private Block rskCurrentBlock; + private Keccak256 pegoutCreationRskTxHash; + private Transaction executionRskTx; @BeforeEach void init() throws IOException { @@ -70,9 +75,25 @@ void init() throws IOException { when(provider.getPegoutsWaitingForConfirmations()) .thenReturn(new PegoutsWaitingForConfirmations(new HashSet<>())); + + List fedUTXOs = createUTXOs( + 10, + TEST_ERP_FEDERATION.getAddress() + ); + when(provider.getNewFederationBtcUTXOs()) + .thenReturn(fedUTXOs); + when(provider.getNewFederation()) + .thenReturn(TEST_ERP_FEDERATION); + + BlockGenerator blockGenerator = new BlockGenerator(); + rskCurrentBlock = blockGenerator.createBlock(TEST_ERP_FEDERATION.getCreationBlockNumber(), 1); + + pegoutCreationRskTxHash = PegTestUtils.createHash3(1); + executionRskTx = mock(Transaction.class); + when(executionRskTx.getHash()).thenReturn(pegoutCreationRskTxHash); } - private static Stream pegoutTransactionCreatedEventArgsProvider() { + private static Stream activationsProvider() { return Stream.of( Arguments.of(ActivationConfigsForTest.arrowhead600().forBlock(0)), Arguments.of(ActivationConfigsForTest.lovell700().forBlock(0)) @@ -80,16 +101,9 @@ private static Stream pegoutTransactionCreatedEventArgsProvider() { } @ParameterizedTest - @MethodSource("pegoutTransactionCreatedEventArgsProvider") + @MethodSource("activationsProvider") void updateCollections_whenPegoutBatchIsCreated_shouldLogPegoutTransactionCreatedEvent(ActivationConfig.ForBlock activations) throws IOException { // Arrange - List fedUTXOs = PegTestUtils.createUTXOs( - 10, - GENESIS_FEDERATION.getAddress() - ); - when(provider.getNewFederationBtcUTXOs()) - .thenReturn(fedUTXOs); - List pegoutRequests = PegTestUtils.createReleaseRequestQueueEntries(3); when(provider.getReleaseRequestQueue()) .thenReturn(new ReleaseRequestQueue(pegoutRequests)); @@ -100,46 +114,37 @@ void updateCollections_whenPegoutBatchIsCreated_shouldLogPegoutTransactionCreate .withBridgeConstants(bridgeMainnetConstants) .withProvider(provider) .withEventLogger(eventLogger) + .withExecutionBlock(rskCurrentBlock) .withActivations(activations) .build(); - Keccak256 pegoutCreationRskTxHash = PegTestUtils.createHash3(1); - - Transaction executionRskTx = mock(Transaction.class); - when(executionRskTx.getHash()).thenReturn(pegoutCreationRskTxHash); - // Act bridgeSupport.updateCollections(executionRskTx); // Assertions - // Assert one pegout tx was added to pegoutsWaitingForConfirmations from the creation of a pegout batch assertEquals(1, pegoutsWaitingForConfirmations.getEntries().size()); PegoutsWaitingForConfirmations.Entry pegoutEntry = pegoutsWaitingForConfirmations.getEntries().stream().findFirst().get(); - BtcTransaction pegoutBatchTransaction = pegoutEntry.getBtcTransaction(); - - Sha256Hash btcTxHash = pegoutBatchTransaction.getHash(); - + Sha256Hash pegoutTxHash = pegoutBatchTransaction.getHash(); List outpointValues = extractOutpointValues(pegoutBatchTransaction); - List pegoutRequestRskTxHashes = pegoutRequests.stream().map(Entry::getRskTxHash).collect( Collectors.toList()); Coin totalTransactionAmount = pegoutRequests.stream().map(Entry::getAmount).reduce(Coin.ZERO, Coin::add); - verify(eventLogger, times(1)).logBatchPegoutCreated(btcTxHash, pegoutRequestRskTxHashes); + verify(eventLogger, times(1)).logBatchPegoutCreated(pegoutTxHash, pegoutRequestRskTxHashes); verify(eventLogger, times(1)).logReleaseBtcRequested(pegoutCreationRskTxHash.getBytes(), pegoutBatchTransaction, totalTransactionAmount); if (activations.isActive(ConsensusRule.RSKIP428)){ - verify(eventLogger, times(1)).logPegoutTransactionCreated(btcTxHash, outpointValues); + verify(eventLogger, times(1)).logPegoutTransactionCreated(pegoutTxHash, outpointValues); } else { verify(eventLogger, never()).logPegoutTransactionCreated(any(), any()); } } @ParameterizedTest - @MethodSource("pegoutTransactionCreatedEventArgsProvider") + @MethodSource("activationsProvider") void updateCollections_whenPegoutMigrationIsCreated_shouldLogPegoutTransactionCreatedEvent(ActivationConfig.ForBlock activations) throws IOException { // Arrange when(provider.getReleaseRequestQueue()) @@ -147,29 +152,32 @@ void updateCollections_whenPegoutMigrationIsCreated_shouldLogPegoutTransactionCr PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations = provider.getPegoutsWaitingForConfirmations(); - Federation oldFederation = GENESIS_FEDERATION; - long newFedCreationBlockNumber = 5L; + Federation oldFederation = TEST_ERP_FEDERATION; + when(provider.getOldFederation()) + .thenReturn(oldFederation); + + long newFedCreationBlockNumber = 5L; FederationArgs newFederationArgs = new FederationArgs(FederationTestUtils.getFederationMembers(1), Instant.EPOCH, newFedCreationBlockNumber, btcMainnetParams); Federation newFederation = FederationFactory.buildStandardMultiSigFederation(newFederationArgs); - when(provider.getOldFederation()) - .thenReturn(oldFederation); + when(provider.getNewFederation()) .thenReturn(newFederation); // Utxos to migrate - List utxos = PegTestUtils.createUTXOs(10, oldFederation.getAddress()); - Coin totalTransactionInputAmount = utxos.stream().map(UTXO::getValue).reduce(Coin.ZERO, Coin::add); + List utxosToMigrate = createUTXOs(10, oldFederation.getAddress()); + Coin totalTransactionInputAmount = utxosToMigrate.stream().map(UTXO::getValue).reduce(Coin.ZERO, Coin::add); when(provider.getOldFederationBtcUTXOs()) - .thenReturn(utxos); + .thenReturn(utxosToMigrate); // Advance blockchain to migration phase. Migration phase starts 1 block after migration age is reached. long migrationAge = bridgeMainnetConstants.getFederationActivationAge(activations) + bridgeMainnetConstants.getFundsMigrationAgeSinceActivationBegin() + newFedCreationBlockNumber + 1; + BlockGenerator blockGenerator = new BlockGenerator(); - org.ethereum.core.Block rskCurrentBlock = blockGenerator.createBlock(migrationAge, 1); + rskCurrentBlock = blockGenerator.createBlock(migrationAge, 1); BridgeSupport bridgeSupport = new BridgeSupportBuilder() .withBridgeConstants(bridgeMainnetConstants) @@ -179,10 +187,6 @@ void updateCollections_whenPegoutMigrationIsCreated_shouldLogPegoutTransactionCr .withActivations(activations) .build(); - Keccak256 pegoutCreationRskTxHash = PegTestUtils.createHash3(1); - Transaction executionRskTx = mock(Transaction.class); - when(executionRskTx.getHash()).thenReturn(pegoutCreationRskTxHash); - // Act bridgeSupport.updateCollections(executionRskTx); @@ -213,7 +217,7 @@ void updateCollections_whenPegoutMigrationIsCreated_shouldLogPegoutTransactionCr } @ParameterizedTest - @MethodSource("pegoutTransactionCreatedEventArgsProvider") + @MethodSource("activationsProvider") void updateCollections_whenPegoutMigrationAndBatchAreCreated_shouldLogPegoutTransactionCreatedEvent(ActivationConfig.ForBlock activations) throws IOException { // Arrange PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations = provider.getPegoutsWaitingForConfirmations(); @@ -222,27 +226,28 @@ void updateCollections_whenPegoutMigrationAndBatchAreCreated_shouldLogPegoutTran when(provider.getReleaseRequestQueue()) .thenReturn(new ReleaseRequestQueue(pegoutRequests)); - Federation oldFederation = GENESIS_FEDERATION; + Federation oldFederation = TEST_ERP_FEDERATION; + when(provider.getOldFederation()) + .thenReturn(oldFederation); long newFedCreationBlockNumber = 5L; FederationArgs newFederationArgs = new FederationArgs(FederationTestUtils.getFederationMembers(1), Instant.EPOCH, newFedCreationBlockNumber, btcMainnetParams); Federation newFederation = FederationFactory.buildStandardMultiSigFederation( newFederationArgs); - when(provider.getOldFederation()) - .thenReturn(oldFederation); + when(provider.getNewFederation()) .thenReturn(newFederation); // Utxos to migrate - List utxos = PegTestUtils.createUTXOs(10, oldFederation.getAddress()); + List utxosToMigrate = createUTXOs(10, oldFederation.getAddress()); when(provider.getOldFederationBtcUTXOs()) - .thenReturn(utxos); - Coin migrationTotalAmount = utxos.stream().map(UTXO::getValue).reduce(Coin.ZERO, Coin::add); + .thenReturn(utxosToMigrate); + Coin migrationTotalAmount = utxosToMigrate.stream().map(UTXO::getValue).reduce(Coin.ZERO, Coin::add); - List utxosNew = PegTestUtils.createUTXOs(10, newFederation.getAddress()); + List utxosNewFederation = createUTXOs(10, newFederation.getAddress()); when(provider.getNewFederationBtcUTXOs()) - .thenReturn(utxosNew); + .thenReturn(utxosNewFederation); // Advance blockchain to migration phase. Migration phase starts 1 block after migration age is reached. long migrationAge = bridgeMainnetConstants.getFederationActivationAge(activations) + @@ -250,7 +255,7 @@ void updateCollections_whenPegoutMigrationAndBatchAreCreated_shouldLogPegoutTran newFedCreationBlockNumber + 1; BlockGenerator blockGenerator = new BlockGenerator(); - org.ethereum.core.Block rskCurrentBlock = blockGenerator.createBlock(migrationAge, 1); + rskCurrentBlock = blockGenerator.createBlock(migrationAge, 1); BridgeSupport bridgeSupport = new BridgeSupportBuilder() .withBridgeConstants(bridgeMainnetConstants) diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java index 8903eb0df75..92acfaec9ba 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportRegisterBtcTransactionTest.java @@ -4,6 +4,7 @@ import static co.rsk.peg.PegTestUtils.createBaseInputScriptThatSpendsFromTheFederation; import static co.rsk.peg.PegTestUtils.createBech32Output; import static co.rsk.peg.PegTestUtils.createFederation; +import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static co.rsk.peg.pegin.RejectedPeginReason.INVALID_AMOUNT; import static co.rsk.peg.pegin.RejectedPeginReason.LEGACY_PEGIN_MULTISIG_SENDER; import static co.rsk.peg.pegin.RejectedPeginReason.PEGIN_V1_INVALID_PAYLOAD; @@ -81,8 +82,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import static co.rsk.peg.bitcoin.BitcoinTestUtils.extractOutpointValues; - class BridgeSupportRegisterBtcTransactionTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); diff --git a/rskj-core/src/test/java/co/rsk/peg/PegTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/PegTestUtils.java index fe605d75ebe..57f24bab1d6 100644 --- a/rskj-core/src/test/java/co/rsk/peg/PegTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/PegTestUtils.java @@ -232,10 +232,26 @@ public static RskAddress createRandomRskAddress() { return new RskAddress(key.getAddress()); } + /** + * + * @deprecated Use {@link co.rsk.peg.bitcoin.BitcoinTestUtils#createUTXO(int, long, Coin, Address)} instead. + * @param nHash + * @param index + * @param value + * @return + */ public static UTXO createUTXO(int nHash, long index, Coin value) { return createUTXO(nHash, index, value, createRandomP2PKHBtcAddress(RegTestParams.get())); } + /** + * @deprecated Use {@link co.rsk.peg.bitcoin.BitcoinTestUtils#createUTXO(int, long, Coin, Address)} instead. + * @param nHash + * @param index + * @param value + * @param address + * @return + */ public static UTXO createUTXO(int nHash, long index, Coin value, Address address) { return new UTXO( createHash(nHash), @@ -246,6 +262,13 @@ public static UTXO createUTXO(int nHash, long index, Coin value, Address address ScriptBuilder.createOutputScript(address)); } + /** + * + * @deprecated Use {@link co.rsk.peg.bitcoin.BitcoinTestUtils#createUTXOs(int, Address)} instead. + * @param amount + * @param address + * @return + */ public static List createUTXOs(int amount, Address address) { List utxos = new ArrayList<>(); for (int i = 0; i < amount; i++) { @@ -255,6 +278,25 @@ public static List createUTXOs(int amount, Address address) { return utxos; } + /** + * + * @deprecated Use {@link co.rsk.peg.bitcoin.BitcoinTestUtils#createUTXO(int, long, Coin, Address)} instead. + * @param btcHash + * @param index + * @param value + * @return + */ + public static UTXO createUTXO(Sha256Hash btcHash, long index, Coin value) { + return new UTXO( + btcHash, + index, + value, + 10, + false, + ScriptBuilder.createOutputScript(new BtcECKey()) + ); + } + public static List createReleaseRequestQueueEntries(int amount) { List entries = new ArrayList<>(); for (int i = 0; i < amount; i++) { @@ -269,17 +311,6 @@ public static List createReleaseRequestQueueEntries(i return entries; } - public static UTXO createUTXO(Sha256Hash btcHash, long index, Coin value) { - return new UTXO( - btcHash, - index, - value, - 10, - false, - ScriptBuilder.createOutputScript(new BtcECKey()) - ); - } - public static Address getFlyoverAddressFromRedeemScript(BridgeConstants bridgeConstants, Script redeemScript, Sha256Hash derivationArgumentHash) { Script flyoverRedeemScript = FastBridgeRedeemScriptParser.createMultiSigFastBridgeRedeemScript( redeemScript, diff --git a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java index 0442b38f6e7..fe89cce85e9 100644 --- a/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/PowpegMigrationTest.java @@ -11,7 +11,6 @@ import co.rsk.db.MutableTrieCache; import co.rsk.db.MutableTrieImpl; import co.rsk.peg.PegoutsWaitingForConfirmations.Entry; -import co.rsk.peg.bitcoin.BitcoinTestUtils; import co.rsk.peg.vote.ABICallSpec; import co.rsk.peg.bitcoin.BitcoinUtils; import co.rsk.peg.bitcoin.NonStandardErpRedeemScriptBuilder; @@ -53,6 +52,7 @@ import static co.rsk.peg.PegTestUtils.BTC_TX_LEGACY_VERSION; import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2; +import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -622,7 +622,7 @@ private void testChangePowpeg( private void verifyPegoutTransactionCreatedEvent(BridgeEventLogger bridgeEventLogger, BtcTransaction pegoutTx) { Sha256Hash pegoutTxHash = pegoutTx.getHash(); - List outpointValues = BitcoinTestUtils.extractOutpointValues(pegoutTx); + List outpointValues = extractOutpointValues(pegoutTx); verify(bridgeEventLogger, times(1)).logPegoutTransactionCreated(pegoutTxHash, outpointValues); } diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java index a3d86744e4d..b76f329969d 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java @@ -2,11 +2,12 @@ import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; -import co.rsk.bitcoinj.core.BtcTransaction; + import co.rsk.bitcoinj.core.Coin; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.bitcoinj.core.Sha256Hash; import co.rsk.bitcoinj.core.TransactionInput; +import co.rsk.bitcoinj.core.UTXO; import co.rsk.bitcoinj.crypto.TransactionSignature; import co.rsk.bitcoinj.script.RedeemScriptParser; import co.rsk.bitcoinj.script.RedeemScriptParserFactory; @@ -43,8 +44,7 @@ public static Address createP2PKHAddress(NetworkParameters networkParameters, St return key.toAddress(networkParameters); } - public static Address createP2SHMultisigAddress(NetworkParameters networkParameters, - List keys) { + public static Address createP2SHMultisigAddress(NetworkParameters networkParameters, List keys) { Script redeemScript = ScriptBuilder.createRedeemScript((keys.size() / 2) + 1, keys); Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); @@ -61,8 +61,7 @@ public static Sha256Hash createHash(int nHash) { return Sha256Hash.wrap(bytes); } - public static List extractSignaturesFromTxInput( - TransactionInput txInput) { + public static List extractSignaturesFromTxInput(TransactionInput txInput) { Script scriptSig = txInput.getScriptSig(); List chunks = scriptSig.getChunks(); Script redeemScript = new Script(chunks.get(chunks.size() - 1).data); @@ -84,8 +83,22 @@ public static List coinListOf(long... values) { .collect(Collectors.toList()); } - public static List extractOutpointValues(BtcTransaction btcTransaction) { - return btcTransaction.getInputs().stream().map(TransactionInput::getValue).collect( - Collectors.toList()); + public static UTXO createUTXO(int nHash, long index, Coin value, Address address) { + return new UTXO( + createHash(nHash), + index, + value, + 10, + false, + ScriptBuilder.createOutputScript(address)); + } + + public static List createUTXOs(int amount, Address address) { + List utxos = new ArrayList<>(); + for (int i = 0; i < amount; i++) { + utxos.add(createUTXO(i + 1, 0, Coin.COIN, address)); + } + + return utxos; } } diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java index 2abe688316a..581ff8c40ad 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -5,8 +5,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import co.rsk.bitcoinj.core.Address; +import co.rsk.bitcoinj.core.BtcTransaction; import co.rsk.bitcoinj.core.Coin; +import co.rsk.bitcoinj.core.NetworkParameters; +import co.rsk.bitcoinj.script.Script; +import co.rsk.peg.constants.BridgeConstants; import co.rsk.peg.constants.BridgeMainNetConstants; +import co.rsk.peg.federation.ErpFederation; +import co.rsk.peg.federation.FederationTestUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -15,6 +22,8 @@ import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.ethereum.config.blockchain.upgrades.ActivationConfig.ForBlock; +import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -24,6 +33,11 @@ class UtxoUtilsTest { private final static Coin MAX_BTC = BridgeMainNetConstants.getInstance().getMaxRbtc(); + private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); + private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); + private static final ErpFederation TEST_ERP_FEDERATION = FederationTestUtils.getTestGenesisErpFederation(btcMainnetParams); + private static final int FIRST_OUTPUT_INDEX = 0; + private static final Address testAddress = BitcoinTestUtils.createP2PKHAddress(btcMainnetParams, "test"); private static Stream validOutpointValues() { List arguments = new ArrayList<>(); @@ -89,7 +103,7 @@ void decodeOutpointValues_null_shouldReturnEmptyList() { List decodeOutpointValues = UtxoUtils.decodeOutpointValues(null); // assert - List expectedDecodedValues = Collections.EMPTY_LIST; + List expectedDecodedValues = Collections.emptyList(); assertArrayEquals(expectedDecodedValues.toArray(), decodeOutpointValues.toArray()); } @@ -168,4 +182,75 @@ private static Stream invalidEncodedOutpointValues() { return arguments.stream(); } + + @Test + void extractOutpointValues_validBtcTransaction_shouldReturnOutpointValues() { + Coin amountToSend = Coin.COIN; + + BtcTransaction fundingTransaction = new BtcTransaction(btcMainnetParams); + fundingTransaction.addInput( + BitcoinTestUtils.createHash(1), + FIRST_OUTPUT_INDEX, + new Script(new byte[]{}) + ); + fundingTransaction.addOutput(amountToSend, TEST_ERP_FEDERATION.getAddress()); + + BtcTransaction pegout = new BtcTransaction(btcMainnetParams); + pegout.addInput(fundingTransaction.getOutput(FIRST_OUTPUT_INDEX)); + pegout.addOutput(amountToSend, testAddress); + + List actualOutpointValues = UtxoUtils.extractOutpointValues(pegout); + + // assert + List expectedOutpointValues = Collections.singletonList(Coin.COIN); + assertArrayEquals(expectedOutpointValues.toArray(), actualOutpointValues.toArray()); + } + + @Test + void extractOutpoint_noInputs_shouldReturnEmptyList() { + BtcTransaction pegout = new BtcTransaction(btcMainnetParams); + pegout.addOutput(Coin.COIN, testAddress); + + List actualOutpointValues = UtxoUtils.extractOutpointValues(pegout); + + // assert + List expectedOutpointValues = Collections.emptyList(); + assertArrayEquals(expectedOutpointValues.toArray(), actualOutpointValues.toArray()); + } + + @Test + void extractOutpoint_null_shouldReturnEmptyList() { + List actualOutpointValues = UtxoUtils.extractOutpointValues(null); + + // assert + List expectedOutpointValues = Collections.emptyList(); + assertArrayEquals(expectedOutpointValues.toArray(), actualOutpointValues.toArray()); + } + + @Test + void extractOutpoint_txWithManyInputs_shouldReturnListOfManyOutpointValues() { + ForBlock activations = ActivationConfigsForTest.lovell700().forBlock(0); + Coin amountToSend = bridgeMainnetConstants.getMinimumPeginTxValue(activations); + + BtcTransaction pegout = new BtcTransaction(btcMainnetParams); + pegout.addOutput(amountToSend, testAddress); + + + for (int i = 0; i < 1000; i++) { + BtcTransaction fundingTransaction = new BtcTransaction(btcMainnetParams); + fundingTransaction.addInput( + BitcoinTestUtils.createHash(i), + FIRST_OUTPUT_INDEX, + new Script(new byte[]{}) + ); + fundingTransaction.addOutput(amountToSend, TEST_ERP_FEDERATION.getAddress()); + pegout.addInput(fundingTransaction.getOutput(FIRST_OUTPUT_INDEX)); + } + List actualOutpointValues = UtxoUtils.extractOutpointValues(pegout); + + // assert + List expectedOutpointValues = Stream.generate(() -> amountToSend).limit(1000) + .collect(Collectors.toList()); + assertArrayEquals(expectedOutpointValues.toArray(), actualOutpointValues.toArray()); + } } diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/FederationTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/federation/FederationTestUtils.java index 1a84a6510f1..ca36c6dce5d 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/FederationTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/FederationTestUtils.java @@ -30,7 +30,9 @@ import co.rsk.bitcoinj.crypto.TransactionSignature; import co.rsk.bitcoinj.script.Script; import co.rsk.bitcoinj.script.ScriptBuilder; +import co.rsk.peg.bitcoin.BitcoinTestUtils; import java.math.BigInteger; +import java.time.Duration; import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -46,6 +48,36 @@ public final class FederationTestUtils { private FederationTestUtils() { } + public static final List TEST_GENESIS_FED_SIGNERS = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{"fa01", "fa02", "fa03", "fa04", "fa05", "fa06", "fa07", "fa08", "fa09"}, true + ); + + public static List TEST_GENESIS_ERP_SIGNERS = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{"fb01", "fb02", "fb03"}, true + ); + + public static ErpFederation getTestGenesisErpFederation(NetworkParameters networkParameters) { + List fedMember = FederationTestUtils.getFederationMembersWithBtcKeys( + TEST_GENESIS_FED_SIGNERS); + + Instant fedCreationTime30daysAgo = Instant.now().minus(Duration.ofDays(30)); + + FederationArgs federationArgs = new FederationArgs( + fedMember, + fedCreationTime30daysAgo, + 0, + networkParameters + ); + + long erpFedActivationDelay = 100L; + + return FederationFactory.buildP2shErpFederation( + federationArgs, + TEST_GENESIS_ERP_SIGNERS, + erpFedActivationDelay + ); + } + public static Federation getFederation(Integer... federationMemberPks) { FederationArgs federationArgs = new FederationArgs( getFederationMembersFromPks(federationMemberPks), From 2759629b8473497ac244a34f2cf1719c157324ac Mon Sep 17 00:00:00 2001 From: nathanieliov Date: Mon, 27 May 2024 14:12:27 -0400 Subject: [PATCH 20/20] - Renamed testing erp federation - Set a constant value as creation time for the erp federation --- ...portPegoutTransactionCreatedEventTest.java | 12 +++++----- .../co/rsk/peg/bitcoin/UtxoUtilsTest.java | 2 +- .../peg/federation/FederationTestUtils.java | 24 ++++++++----------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java index c8d62d9fe59..4c4cf1a9491 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportPegoutTransactionCreatedEventTest.java @@ -54,7 +54,7 @@ class BridgeSupportPegoutTransactionCreatedEventTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); - private static final ErpFederation TEST_ERP_FEDERATION = FederationTestUtils.getTestGenesisErpFederation(btcMainnetParams); + private static final ErpFederation ERP_FEDERATION = FederationTestUtils.getErpFederation(btcMainnetParams); private BridgeStorageProvider provider; private BridgeEventLogger eventLogger; @@ -78,15 +78,15 @@ void init() throws IOException { List fedUTXOs = createUTXOs( 10, - TEST_ERP_FEDERATION.getAddress() + ERP_FEDERATION.getAddress() ); when(provider.getNewFederationBtcUTXOs()) .thenReturn(fedUTXOs); when(provider.getNewFederation()) - .thenReturn(TEST_ERP_FEDERATION); + .thenReturn(ERP_FEDERATION); BlockGenerator blockGenerator = new BlockGenerator(); - rskCurrentBlock = blockGenerator.createBlock(TEST_ERP_FEDERATION.getCreationBlockNumber(), 1); + rskCurrentBlock = blockGenerator.createBlock(ERP_FEDERATION.getCreationBlockNumber(), 1); pegoutCreationRskTxHash = PegTestUtils.createHash3(1); executionRskTx = mock(Transaction.class); @@ -152,7 +152,7 @@ void updateCollections_whenPegoutMigrationIsCreated_shouldLogPegoutTransactionCr PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations = provider.getPegoutsWaitingForConfirmations(); - Federation oldFederation = TEST_ERP_FEDERATION; + Federation oldFederation = ERP_FEDERATION; when(provider.getOldFederation()) .thenReturn(oldFederation); @@ -226,7 +226,7 @@ void updateCollections_whenPegoutMigrationAndBatchAreCreated_shouldLogPegoutTran when(provider.getReleaseRequestQueue()) .thenReturn(new ReleaseRequestQueue(pegoutRequests)); - Federation oldFederation = TEST_ERP_FEDERATION; + Federation oldFederation = ERP_FEDERATION; when(provider.getOldFederation()) .thenReturn(oldFederation); diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java index 581ff8c40ad..8dc71e89781 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/UtxoUtilsTest.java @@ -35,7 +35,7 @@ class UtxoUtilsTest { private final static Coin MAX_BTC = BridgeMainNetConstants.getInstance().getMaxRbtc(); private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); - private static final ErpFederation TEST_ERP_FEDERATION = FederationTestUtils.getTestGenesisErpFederation(btcMainnetParams); + private static final ErpFederation TEST_ERP_FEDERATION = FederationTestUtils.getErpFederation(btcMainnetParams); private static final int FIRST_OUTPUT_INDEX = 0; private static final Address testAddress = BitcoinTestUtils.createP2PKHAddress(btcMainnetParams, "test"); diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/FederationTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/federation/FederationTestUtils.java index ca36c6dce5d..0f7fe387537 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/FederationTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/FederationTestUtils.java @@ -32,7 +32,6 @@ import co.rsk.bitcoinj.script.ScriptBuilder; import co.rsk.peg.bitcoin.BitcoinTestUtils; import java.math.BigInteger; -import java.time.Duration; import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -48,23 +47,20 @@ public final class FederationTestUtils { private FederationTestUtils() { } - public static final List TEST_GENESIS_FED_SIGNERS = BitcoinTestUtils.getBtcEcKeysFromSeeds( - new String[]{"fa01", "fa02", "fa03", "fa04", "fa05", "fa06", "fa07", "fa08", "fa09"}, true - ); - - public static List TEST_GENESIS_ERP_SIGNERS = BitcoinTestUtils.getBtcEcKeysFromSeeds( - new String[]{"fb01", "fb02", "fb03"}, true - ); + public static ErpFederation getErpFederation(NetworkParameters networkParameters) { + final List fedSigners = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{"fa01", "fa02", "fa03", "fa04", "fa05", "fa06", "fa07", "fa08", "fa09"}, true + ); + final List erpSigners = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{"fb01", "fb02", "fb03"}, true + ); - public static ErpFederation getTestGenesisErpFederation(NetworkParameters networkParameters) { List fedMember = FederationTestUtils.getFederationMembersWithBtcKeys( - TEST_GENESIS_FED_SIGNERS); - - Instant fedCreationTime30daysAgo = Instant.now().minus(Duration.ofDays(30)); + fedSigners); FederationArgs federationArgs = new FederationArgs( fedMember, - fedCreationTime30daysAgo, + ZonedDateTime.parse("1970-01-18T12:49:08.400Z").toInstant(), 0, networkParameters ); @@ -73,7 +69,7 @@ public static ErpFederation getTestGenesisErpFederation(NetworkParameters networ return FederationFactory.buildP2shErpFederation( federationArgs, - TEST_GENESIS_ERP_SIGNERS, + erpSigners, erpFedActivationDelay ); }