diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSerializationUtils.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSerializationUtils.java index 229829fe96..b3f5c252eb 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSerializationUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSerializationUtils.java @@ -120,6 +120,10 @@ private static BtcTransaction deserializeBtcTransactionFromRawTx( public static byte[] serializeRskTxWaitingForSignatures( Map.Entry rskTxWaitingForSignaturesEntry) { + if (rskTxWaitingForSignaturesEntry == null) { + return RLP.encodedEmptyList(); + } + byte[][] serializedRskTxWaitingForSignaturesEntry = serializeRskTxWaitingForSignaturesEntry(rskTxWaitingForSignaturesEntry); return RLP.encodeList(serializedRskTxWaitingForSignaturesEntry); @@ -154,7 +158,6 @@ private static byte[][] serializeRskTxWaitingForSignaturesEntry( public static Map.Entry deserializeRskTxWaitingForSignatures( byte[] data, NetworkParameters networkParameters) { - if (data == null || data.length == 0) { return null; } @@ -187,6 +190,9 @@ public static SortedMap deserializeRskTxsWaitingForSi private static Map.Entry deserializeRskTxWaitingForSignaturesEntry( RLPList rlpList, int index, NetworkParameters networkParameters) { + if (rlpList.size() == 0) { + return null; + } RLPElement rskTxHashRLPElement = rlpList.get(index * 2); byte[] rskTxHashData = rskTxHashRLPElement.getRLPData(); 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 9660b59b41..965e14f5c1 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -1939,19 +1939,20 @@ public byte[] getStateForBtcReleaseClient() throws IOException { /** * Retrieves the current SVP spend transaction state for the SVP client. + * *

* This method checks if there is an SVP spend transaction waiting for signatures, and if so, it serializes * the state into RLP format. If no transaction is waiting, it returns an encoded empty RLP list. *

* * @return A byte array representing the RLP-encoded state of the SVP spend transaction. If no transaction - * is waiting, returns an RLP-encoded empty list. + * is waiting, returns a double RLP-encoded empty list. */ public byte[] getStateForSvpClient() { return provider.getSvpSpendTxWaitingForSignatures() .map(StateForProposedFederator::new) .map(StateForProposedFederator::encodeToRlp) - .orElse(RLP.encodedEmptyList()); + .orElse(RLP.encodeList(RLP.encodedEmptyList())); } /** diff --git a/rskj-core/src/main/java/co/rsk/peg/StateForFederator.java b/rskj-core/src/main/java/co/rsk/peg/StateForFederator.java index 02df23f235..4bc993b246 100644 --- a/rskj-core/src/main/java/co/rsk/peg/StateForFederator.java +++ b/rskj-core/src/main/java/co/rsk/peg/StateForFederator.java @@ -43,6 +43,18 @@ public StateForFederator(byte[] rlpData, NetworkParameters networkParameters) { decodeRlpToMap(rlpData), networkParameters)); } + /** + * Retrieves a sorted map of RSK transactions that are waiting for signatures. + * + *

+ * The returned {@code SortedMap} contains entries where the key + * is the hash of the RSK transaction, and the value is the {@code BtcTransaction} object. + * This method guarantees a non-null result, returning an empty map if no transactions are pending. + *

+ * + * @return a non-null {@code SortedMap} of transactions waiting for signatures; + * if no transactions are pending, an empty map is returned. + */ public SortedMap getRskTxsWaitingForSignatures() { return rskTxsWaitingForSignatures; } diff --git a/rskj-core/src/main/java/co/rsk/peg/StateForProposedFederator.java b/rskj-core/src/main/java/co/rsk/peg/StateForProposedFederator.java index 841adcb7e7..b20f018d73 100644 --- a/rskj-core/src/main/java/co/rsk/peg/StateForProposedFederator.java +++ b/rskj-core/src/main/java/co/rsk/peg/StateForProposedFederator.java @@ -21,7 +21,6 @@ import co.rsk.bitcoinj.core.BtcTransaction; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.crypto.Keccak256; -import java.util.AbstractMap; import java.util.Map; import java.util.Objects; import org.ethereum.util.RLP; @@ -32,10 +31,7 @@ public class StateForProposedFederator { private final Map.Entry svpSpendTxWaitingForSignatures; public StateForProposedFederator(Map.Entry svpSpendTxWaitingForSignatures) { - Objects.requireNonNull(svpSpendTxWaitingForSignatures); - - this.svpSpendTxWaitingForSignatures = - new AbstractMap.SimpleImmutableEntry<>(svpSpendTxWaitingForSignatures); + this.svpSpendTxWaitingForSignatures = svpSpendTxWaitingForSignatures; } public StateForProposedFederator(byte[] rlpData, NetworkParameters networkParameters) { @@ -44,6 +40,18 @@ public StateForProposedFederator(byte[] rlpData, NetworkParameters networkParame decodeRlpToEntry(rlpData), networkParameters)); } + /** + * Retrieves the SVP spend transaction waiting for signatures. + * + *

+ * This method returns a {@code Map.Entry} representing + * the transaction waiting for signatures, with the hash of the transaction as the key + * and the {@code BtcTransaction} object as the value. + *

+ * + * @return a {@code Map.Entry} if a transaction is pending + * for signatures, or {@code null} if no such transaction exists. + */ public Map.Entry getSvpSpendTxWaitingForSignatures() { return svpSpendTxWaitingForSignatures; } diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSerializationUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSerializationUtilsTest.java index 53b5d2d37d..1fa0f4c59c 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSerializationUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSerializationUtilsTest.java @@ -73,11 +73,9 @@ import org.junit.jupiter.api.Assertions; 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.EmptySource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullSource; - import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.time.Instant; @@ -213,9 +211,18 @@ void serializeAndDeserializeRskTxWaitingForSignatures_whenValidData_shouldReturn assertArrayEquals(pegoutTx.bitcoinSerialize(), deserializedEntry.getValue().bitcoinSerialize()); } + @Test + void serializeRskTxWaitingForSignatures_whenNullValuePassed_shouldReturnEmptyResult() { + // Act + byte[] result = + BridgeSerializationUtils.serializeRskTxWaitingForSignatures(null); + + // Assert + assertArrayEquals(RLP.encodedEmptyList(), result); + } + @ParameterizedTest - @NullSource - @EmptySource + @MethodSource("provideInvalidData") void deserializeRskTxWaitingForSignatures_whenInvalidData_shouldReturnEmptyResult(byte[] data) { // Act Map.Entry result = @@ -224,6 +231,13 @@ void deserializeRskTxWaitingForSignatures_whenInvalidData_shouldReturnEmptyResul // Assert assertNull(result); } + + private static Stream provideInvalidData() { + return Stream.of( + null, + new byte[]{}, + RLP.encodedEmptyList()); + } @Test void serializeAndDeserializeRskTxsWaitingForSignatures_whenValidEntries_shouldReturnEqualsResults() { diff --git a/rskj-core/src/test/java/co/rsk/peg/StateForProposedFederatorTest.java b/rskj-core/src/test/java/co/rsk/peg/StateForProposedFederatorTest.java index d7b55d2742..61880cf58b 100644 --- a/rskj-core/src/test/java/co/rsk/peg/StateForProposedFederatorTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/StateForProposedFederatorTest.java @@ -15,19 +15,20 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - package co.rsk.peg; import static co.rsk.RskTestUtils.createHash; import static org.ethereum.TestUtils.assertThrows; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import co.rsk.bitcoinj.core.BtcTransaction; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.crypto.Keccak256; import java.util.AbstractMap; import java.util.Map; +import org.ethereum.util.RLP; import org.junit.jupiter.api.Test; class StateForProposedFederatorTest { @@ -53,9 +54,15 @@ void stateForProposedFederator_whenTxWaitingForSignaturesAndSerializeAndDeserial } @Test - void stateForProposedFederator_whenNullValueAndSerializeAndDeserialize_shouldThrowNullPointerException() { + void stateForProposedFederator_whenEmptyDoubleRLPEncodedList_shouldReturnEmptySvpSpendTxWaitingForSignatures() { + // Arrange + var doubleEncodedEmptyList = RLP.encodeList(RLP.encodedEmptyList()); + + // Act + StateForProposedFederator deserializedStateForProposedFederator = + new StateForProposedFederator(doubleEncodedEmptyList, NETWORK_PARAMETERS); + // Assert - assertThrows(NullPointerException.class, () -> new StateForProposedFederator(null)); - assertThrows(NullPointerException.class, () -> new StateForProposedFederator(null, NETWORK_PARAMETERS)); + assertNull(deserializedStateForProposedFederator.getSvpSpendTxWaitingForSignatures()); } }