diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/Bytes.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/Bytes.java index ce988888068..cb4c16d17d3 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/Bytes.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/Bytes.java @@ -23,7 +23,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Arrays; import java.util.Objects; /** @@ -140,8 +139,8 @@ public byte byteAt(int index) { } @Override - public byte[] copyArrayOfRange(int from, int to) { - return Arrays.copyOfRange(byteArray, from, to); + public void arraycopy(int srcPos, byte[] dest, int destPos, int length) { + System.arraycopy(byteArray, srcPos, dest, destPos, length); } @Override diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java index 5210ff8eea0..ac3616fd8f0 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java @@ -18,11 +18,62 @@ package co.rsk.core.types.bytes; +import java.util.Arrays; + /** * A {@link BytesSlice} is a subsequence of bytes backed by another broader byte sequence. */ public interface BytesSlice extends HexPrintableBytes { + /** + * Copies an array from the {@link BytesSlice} source, beginning at the + * specified position, to the specified position of the destination array. + * A subsequence of array components are copied from this instance to the + * destination array referenced by {@code dest}. The number of components + * copied is equal to the {@code length} argument. The components at + * positions {@code srcPos} through {@code srcPos+length-1} in the source + * array are copied into positions {@code destPos} through + * {@code destPos+length-1}, respectively, of the destination + * array. + *

+ * If the underlying byte array and {@code dest} argument refer to the + * same array object, then the copying is performed as if the + * components at positions {@code srcPos} through + * {@code srcPos+length-1} were first copied to a temporary + * array with {@code length} components and then the contents of + * the temporary array were copied into positions + * {@code destPos} through {@code destPos+length-1} of the + * destination array. + *

+ * If {@code dest} is {@code null}, then a + * {@code NullPointerException} is thrown. + *

+ * Otherwise, if any of the following is true, an + * {@code IndexOutOfBoundsException} is + * thrown and the destination is not modified: + *

+ * + *

+ * Note: this method mimics behaviour of {@link System#arraycopy(Object, int, Object, int, int)} + * + * @param srcPos starting position in the source array. + * @param dest the destination array. + * @param destPos starting position in the destination data. + * @param length the number of array elements to be copied. + * @throws IndexOutOfBoundsException if copying would cause + * access of data outside array bounds. + * @throws NullPointerException if {@code dest} is {@code null}. + */ + void arraycopy(int srcPos, byte[] dest, int destPos, int length); + /** * Copies the specified range of the specified array into a new array. * The initial index of the range (from) must lie between zero @@ -37,17 +88,30 @@ public interface BytesSlice extends HexPrintableBytes { * greater than or equal to original.length - from. The length * of the returned array will be to - from. * + *

+ * Note: this method mimics behaviour of {@link Arrays#copyOfRange(Object[], int, int)} + * * @param from the initial index of the range to be copied, inclusive * @param to the final index of the range to be copied, exclusive. * (This index may lie outside the array.) * @return a new array containing the specified range from the original array, * truncated or padded with zeros to obtain the required length - * @throws ArrayIndexOutOfBoundsException if {@code from < 0} + * @throws IndexOutOfBoundsException if {@code from < 0} * or {@code from > original.length} * @throws IllegalArgumentException if from > to - * @throws NullPointerException if original is null */ - byte[] copyArrayOfRange(int from, int to); + default byte[] copyArrayOfRange(int from, int to) { + if (from < 0 || from > length()) { + throw new IndexOutOfBoundsException("invalid 'from': " + from); + } + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + arraycopy(from, copy, 0, Math.min(length() - from, newLength)); + return copy; + } default byte[] copyArray() { return copyArrayOfRange(0, length()); @@ -104,11 +168,17 @@ public byte byteAt(int index) { } @Override - public byte[] copyArrayOfRange(int from, int to) { - if (from < 0 || from > to || to > length()) { - throw new IndexOutOfBoundsException("invalid 'from' and/or 'to': [" + from + ";" + to + ")"); + public void arraycopy(int srcPos, byte[] dest, int destPos, int length) { + if (length < 0) { + throw new IndexOutOfBoundsException("invalid 'length': " + length); + } + if (srcPos < 0 || srcPos + length > length()) { + throw new IndexOutOfBoundsException("invalid 'srcPos' and/or 'length': [" + srcPos + ";" + length + ")"); + } + if (destPos < 0 || destPos + length > dest.length) { + throw new IndexOutOfBoundsException("invalid 'destPos' and/or 'length': [" + destPos + ";" + length + ")"); } - return originBytes.copyArrayOfRange(this.from + from, this.from + to); + originBytes.arraycopy(this.from + srcPos, dest, destPos, length); } @Override diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java index d700c0cc6a6..3f91f918eda 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java @@ -77,7 +77,7 @@ public String toFormattedString(@Nonnull HexPrintableBytes printableBytes, int o } if (length > 32) { - return printableBytes.toHexString(off, 15) + ".." + printableBytes.toHexString(off + length - 15, 15); + return printableBytes.toHexString(off, 16) + ".." + printableBytes.toHexString(off + length - 15, 15); } return printableBytes.toHexString(off, length); } diff --git a/rskj-core/src/main/java/co/rsk/util/StringUtils.java b/rskj-core/src/main/java/co/rsk/util/StringUtils.java index a7c58a322b3..02ef4106555 100644 --- a/rskj-core/src/main/java/co/rsk/util/StringUtils.java +++ b/rskj-core/src/main/java/co/rsk/util/StringUtils.java @@ -24,6 +24,8 @@ public class StringUtils { private static final int DEFAULT_MAX_LEN = 64; + private StringUtils() { /* hidden */ } + public static String trim(@Nullable String src) { return trim(src, DEFAULT_MAX_LEN); } diff --git a/rskj-core/src/main/java/org/ethereum/core/CallTransaction.java b/rskj-core/src/main/java/org/ethereum/core/CallTransaction.java index 4c6cf7a8296..50750a55ba2 100644 --- a/rskj-core/src/main/java/org/ethereum/core/CallTransaction.java +++ b/rskj-core/src/main/java/org/ethereum/core/CallTransaction.java @@ -20,6 +20,8 @@ package org.ethereum.core; import co.rsk.core.RskAddress; +import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonInclude; @@ -36,7 +38,6 @@ import java.util.Arrays; import static java.lang.String.format; -import static org.apache.commons.lang3.ArrayUtils.subarray; import static org.apache.commons.lang3.StringUtils.stripEnd; import static org.ethereum.util.ByteUtil.longToBytesNoLeadZeroes; @@ -119,9 +120,9 @@ public static Type getType(String typeName) { */ public abstract byte[] encode(Object value); - public abstract Object decode(byte[] encoded, int offset); + public abstract Object decode(BytesSlice encoded, int offset); - public Object decode(byte[] encoded) { + public Object decode(BytesSlice encoded) { return decode(encoded, 0); } @@ -187,12 +188,12 @@ public byte[] encode(Object value) { } @Override - public Object decode(byte[] encoded, int offset) { + public Object decode(BytesSlice encoded, int offset) { return decodeInt(encoded, offset); } - public static BigInteger decodeInt(byte[] encoded, int offset) { - return new BigInteger(Arrays.copyOfRange(encoded, offset, offset + 32)); + public static BigInteger decodeInt(BytesSlice encoded, int offset) { + return new BigInteger(encoded.copyArrayOfRange(offset, offset + 32)); } public static byte[] encodeInt(int i) { @@ -222,7 +223,7 @@ public byte[] encode(Object value) { } @Override - public Object decode(byte[] encoded, int offset) { + public Object decode(BytesSlice encoded, int offset) { return Boolean.valueOf(((Number) super.decode(encoded, offset)).intValue() != 0); } } @@ -350,7 +351,7 @@ public Object[] decodeEventData(byte[] encodedData) { checkFunctionType(FunctionType.event); Param[] dataInputs = Arrays.stream(inputs).filter(i -> !i.indexed).toArray(Param[]::new); - return decode(encodedData, dataInputs); + return decode(Bytes.of(encodedData), dataInputs); } private void checkFunctionType(FunctionType expected) { @@ -405,7 +406,7 @@ public byte[] encodeOutputs(Object... args) { return encodeArguments(outputs, args); } - private Object[] decode(byte[] encoded, Param[] params) { + private Object[] decode(BytesSlice encoded, Param[] params) { Object[] ret = new Object[params.length]; int off = 0; @@ -421,11 +422,11 @@ private Object[] decode(byte[] encoded, Param[] params) { } public Object[] decode(byte[] encoded) { - return decode(subarray(encoded, 4, encoded.length), inputs); + return decode(Bytes.of(encoded).slice(4, encoded.length), inputs); } public Object[] decodeResult(byte[] encodedRet) { - return decode(encodedRet, outputs); + return decode(Bytes.of(encodedRet), outputs); } public String formatSignature() { diff --git a/rskj-core/src/main/java/org/ethereum/solidity/SolidityType.java b/rskj-core/src/main/java/org/ethereum/solidity/SolidityType.java index a4ef7aa34be..23d6197fc6c 100644 --- a/rskj-core/src/main/java/org/ethereum/solidity/SolidityType.java +++ b/rskj-core/src/main/java/org/ethereum/solidity/SolidityType.java @@ -20,6 +20,7 @@ package org.ethereum.solidity; import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import org.ethereum.util.ByteUtil; @@ -101,9 +102,9 @@ public static SolidityType getType(String typeName) { */ public abstract byte[] encode(Object value); - public abstract Object decode(byte[] encoded, int offset); + public abstract Object decode(BytesSlice encoded, int offset); - public Object decode(byte[] encoded) { + public Object decode(BytesSlice encoded) { return decode(encoded, 0); } @@ -196,7 +197,7 @@ public byte[] encodeList(List l) { } @Override - public Object[] decode(byte[] encoded, int offset) { + public Object[] decode(BytesSlice encoded, int offset) { Utils.validateArrayAllegedSize(encoded, offset, getFixedSize()); Object[] result = new Object[size]; for (int i = 0; i < size; i++) { @@ -247,8 +248,8 @@ public byte[] encodeList(List l) { } @Override - public Object decode(byte[] encoded, int origOffset) { - if (encoded.length == 0) { + public Object decode(BytesSlice encoded, int origOffset) { + if (encoded.length() == 0) { return new Object[0]; } int len = IntType.decodeInt(encoded, origOffset).intValue(); @@ -299,7 +300,7 @@ public byte[] encode(Object value) { } @Override - public Object decode(byte[] encoded, int offset) { + public Object decode(BytesSlice encoded, int offset) { int len = IntType.decodeInt(encoded, offset).intValue(); offset += IntType.INT_SIZE; return Utils.safeCopyOfRange(encoded, offset, len); @@ -325,7 +326,7 @@ public byte[] encode(Object value) { } @Override - public Object decode(byte[] encoded, int offset) { + public Object decode(BytesSlice encoded, int offset) { return new String((byte[]) super.decode(encoded, offset), StandardCharsets.UTF_8); } } @@ -357,7 +358,7 @@ public byte[] encode(Object value) { } @Override - public Object decode(byte[] encoded, int offset) { + public Object decode(BytesSlice encoded, int offset) { return Utils.safeCopyOfRange(encoded, offset, getFixedSize()); } } @@ -383,7 +384,7 @@ public byte[] encode(Object value) { } @Override - public Object decode(byte[] encoded, int offset) { + public Object decode(BytesSlice encoded, int offset) { BigInteger asBigInteger = (BigInteger) super.decode(encoded, offset); return DataWord.valueOf(asBigInteger.toByteArray()); } @@ -434,14 +435,14 @@ public byte[] encode(Object value) { } @Override - public Object decode(byte[] encoded, int offset) { + public Object decode(BytesSlice encoded, int offset) { return decodeInt(encoded, offset); } - public static BigInteger decodeInt(byte[] encoded, int offset) { + public static BigInteger decodeInt(BytesSlice encoded, int offset) { // This is here because getGasForData might send an empty payload which will produce an exception // But currently the bridge would return the cost of RELEASE_BTC in this situation - if (encoded.length == 0) { + if (encoded.length() == 0) { return BigInteger.ZERO; } return new BigInteger(Utils.safeCopyOfRange(encoded, offset, INT_SIZE)); @@ -474,7 +475,7 @@ public byte[] encode(Object value) { } @Override - public Object decode(byte[] encoded, int offset) { + public Object decode(BytesSlice encoded, int offset) { return Boolean.valueOf(((Number) super.decode(encoded, offset)).intValue() != 0); } } diff --git a/rskj-core/src/main/java/org/ethereum/util/RLP.java b/rskj-core/src/main/java/org/ethereum/util/RLP.java index 5caf38f071f..4a0e1728baa 100644 --- a/rskj-core/src/main/java/org/ethereum/util/RLP.java +++ b/rskj-core/src/main/java/org/ethereum/util/RLP.java @@ -22,7 +22,10 @@ import co.rsk.core.BlockDifficulty; import co.rsk.core.Coin; import co.rsk.core.RskAddress; +import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import co.rsk.util.RLPException; +import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.tuple.Pair; import org.bouncycastle.util.BigIntegers; import org.ethereum.db.ByteArrayWrapper; @@ -100,7 +103,8 @@ public class RLP { * byte with value 0x80 plus the length of the string followed by the * string. The range of the first byte is thus [0x80, 0xb7]. */ - private static final int OFFSET_SHORT_ITEM = 0x80; + @VisibleForTesting + static final int OFFSET_SHORT_ITEM = 0x80; /** * [0xb7] @@ -111,7 +115,8 @@ public class RLP { * \xb9\x04\x00 followed by the string. The range of the first byte is thus * [0xb8, 0xbf]. */ - private static final int OFFSET_LONG_ITEM = 0xb7; + @VisibleForTesting + static final int OFFSET_LONG_ITEM = 0xb7; /** * [0xc0] @@ -121,7 +126,8 @@ public class RLP { * of the RLP encodings of the items. The range of the first byte is thus * [0xc0, 0xf7]. */ - private static final int OFFSET_SHORT_LIST = 0xc0; + @VisibleForTesting + static final int OFFSET_SHORT_LIST = 0xc0; /** * [0xf7] @@ -131,29 +137,14 @@ public class RLP { * followed by the concatenation of the RLP encodings of the items. The * range of the first byte is thus [0xf8, 0xff]. */ - private static final int OFFSET_LONG_LIST = 0xf7; + @VisibleForTesting + static final int OFFSET_LONG_LIST = 0xf7; /* ****************************************************** * DECODING * * ******************************************************/ - private static byte decodeOneByteItem(byte[] data, int index) { - // null item - if ((data[index] & 0xFF) == OFFSET_SHORT_ITEM) { - return (byte) (data[index] - OFFSET_SHORT_ITEM); - } - // single byte item - if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) { - return data[index]; - } - // single byte item - if ((data[index] & 0xFF) == OFFSET_SHORT_ITEM + 1) { - return data[index + 1]; - } - return 0; - } - public static int decodeInt(byte[] data, int index) { // NOTE: there are two ways zero can be encoded - 0x00 and OFFSET_SHORT_ITEM @@ -239,108 +230,7 @@ public static int getNextElementIndex(byte[] payload, int pos) { return -1; } - /** - * Get exactly one message payload - */ - public static void fullTraverse(byte[] msgData, int level, int startPos, - int endPos, int levelToIndex, Queue index) { - - try { - - if (msgData == null || msgData.length == 0) { - return; - } - int pos = startPos; - - while (pos < endPos) { - - if (level == levelToIndex) { - index.add(pos); - } - - // It's a list with a payload more than 55 bytes - // data[0] - 0xF7 = how many next bytes allocated - // for the length of the list - if ((msgData[pos] & 0xFF) >= OFFSET_LONG_LIST) { - - byte lengthOfLength = (byte) (msgData[pos] - OFFSET_LONG_LIST); - int length = calcLength(lengthOfLength, msgData, pos); - - // now we can parse an item for data[1]..data[length] - System.out.println("-- level: [" + level - + "] Found big list length: " + length); - - fullTraverse(msgData, level + 1, pos + lengthOfLength + 1, - pos + lengthOfLength + length, levelToIndex, index); - - pos += lengthOfLength + length + 1; - continue; - } - // It's a list with a payload less than 55 bytes - if ((msgData[pos] & 0xFF) >= OFFSET_SHORT_LIST - && (msgData[pos] & 0xFF) < OFFSET_LONG_LIST) { - - byte length = (byte) ((msgData[pos] & 0xFF) - OFFSET_SHORT_LIST); - - System.out.println("-- level: [" + level - + "] Found small list length: " + length); - - fullTraverse(msgData, level + 1, pos + 1, pos + length + 1, - levelToIndex, index); - - pos += 1 + length; - continue; - } - // It's an item with a payload more than 55 bytes - // data[0] - 0xB7 = how much next bytes allocated for - // the length of the string - if ((msgData[pos] & 0xFF) >= OFFSET_LONG_ITEM - && (msgData[pos] & 0xFF) < OFFSET_SHORT_LIST) { - - byte lengthOfLength = (byte) (msgData[pos] - OFFSET_LONG_ITEM); - int length = calcLength(lengthOfLength, msgData, pos); - - // now we can parse an item for data[1]..data[length] - System.out.println("-- level: [" + level - + "] Found big item length: " + length); - pos += lengthOfLength + length + 1; - - continue; - } - // It's an item less than 55 bytes long, - // data[0] - 0x80 == length of the item - if ((msgData[pos] & 0xFF) > OFFSET_SHORT_ITEM - && (msgData[pos] & 0xFF) < OFFSET_LONG_ITEM) { - - byte length = (byte) ((msgData[pos] & 0xFF) - OFFSET_SHORT_ITEM); - - System.out.println("-- level: [" + level - + "] Found small item length: " + length); - pos += 1 + length; - continue; - } - // null item - if ((msgData[pos] & 0xFF) == OFFSET_SHORT_ITEM) { - System.out.println("-- level: [" + level - + "] Found null item: "); - pos += 1; - continue; - } - // single byte item - if ((msgData[pos] & 0xFF) < OFFSET_SHORT_ITEM) { - System.out.println("-- level: [" + level - + "] Found single item: "); - pos += 1; - continue; - } - } - } catch (Throwable th) { - throw new RuntimeException("RLP wrong encoding", - th.fillInStackTrace()); - } - } - - private static int calcLength(int lengthOfLength, byte[] msgData, int pos) { + static int calcLength(int lengthOfLength, byte[] msgData, int pos) { byte pow = (byte) (lengthOfLength - 1); int length = 0; for (int i = 1; i <= lengthOfLength; ++i) { @@ -350,6 +240,11 @@ private static int calcLength(int lengthOfLength, byte[] msgData, int pos) { return length; } + @Nonnull + public static ArrayList decode2(@CheckForNull byte[] msgData) { + return decode2(Bytes.of(msgData)); + } + /** * Parse wire byte[] message into RLP elements * @@ -358,14 +253,14 @@ private static int calcLength(int lengthOfLength, byte[] msgData, int pos) { * - outcome of recursive RLP structure */ @Nonnull - public static ArrayList decode2(@CheckForNull byte[] msgData) { + public static ArrayList decode2(@CheckForNull BytesSlice msgData) { ArrayList elements = new ArrayList<>(); if (msgData == null) { return elements; } - int tlength = msgData.length; + int tlength = msgData.length(); int position = 0; while (position < tlength) { @@ -382,11 +277,11 @@ public static RLPElement decodeFirstElement(@CheckForNull byte[] msgData, int po return null; } - return decodeElement(msgData, position).getKey(); + return decodeElement(Bytes.of(msgData), position).getKey(); } - private static Pair decodeElement(byte[] msgData, int position) { // NOSONAR - int b0 = msgData[position] & 0xff; + private static Pair decodeElement(BytesSlice msgData, int position) { + int b0 = msgData.byteAt(position) & 0xff; if (b0 >= 192) { int length; @@ -403,24 +298,23 @@ private static Pair decodeElement(byte[] msgData, int posit int endingIndex = safeAdd(length, position); - if (endingIndex > msgData.length) { + if (endingIndex > msgData.length()) { throw new RLPException("The RLP byte array doesn't have enough space to hold an element with the specified length"); } - byte[] bytes = Arrays.copyOfRange(msgData, position, endingIndex); - RLPList list = new RLPList(bytes, offset); + RLPList list = new RLPList(msgData.slice(position, endingIndex), offset); return Pair.of(list, endingIndex); } if (b0 == EMPTY_MARK) { - return Pair.of(new RLPItem(ByteUtil.EMPTY_BYTE_ARRAY), position + 1); + return Pair.of(new RLPItem(Bytes.of(ByteUtil.EMPTY_BYTE_ARRAY)), position + 1); } if (b0 < EMPTY_MARK) { byte[] data = new byte[1]; - data[0] = msgData[position]; - return Pair.of(new RLPItem(data), position + 1); + data[0] = msgData.byteAt(position); + return Pair.of(new RLPItem(Bytes.of(data)), position + 1); } int length; @@ -439,15 +333,13 @@ private static Pair decodeElement(byte[] msgData, int posit } int endingIndex = position + offset + length; - if ( endingIndex < 0 || endingIndex > msgData.length) { + if (endingIndex < 0 || endingIndex > msgData.length()) { throw new RLPException("The RLP byte array doesn't have enough space to hold an element with the specified length"); } - byte[] decoded = new byte[length]; - - System.arraycopy(msgData, position + offset, decoded, 0, length); - - return Pair.of(new RLPItem(decoded), position + offset + length); + int from = position + offset; + int to = from + length; + return Pair.of(new RLPItem(msgData.slice(from, to)), to); } private static int safeAdd(int a, int b) { @@ -458,8 +350,8 @@ private static int safeAdd(int a, int b) { } } - private static int bytesToLength(byte[] bytes, int position, int size) { - if (position + size > bytes.length) { + private static int bytesToLength(BytesSlice bytes, int position, int size) { + if (position + size > bytes.length()) { throw new RLPException("The length of the RLP item length can't possibly fit the data byte array"); } @@ -467,7 +359,7 @@ private static int bytesToLength(byte[] bytes, int position, int size) { for (int k = 0; k < size; k++) { length <<= 8; - length += bytes[position + k] & 0xff; + length += bytes.byteAt(position + k) & 0xff; } if (length < 0) { diff --git a/rskj-core/src/main/java/org/ethereum/util/RLPItem.java b/rskj-core/src/main/java/org/ethereum/util/RLPItem.java index 2ca8f7f81e3..49ce56965f1 100644 --- a/rskj-core/src/main/java/org/ethereum/util/RLPItem.java +++ b/rskj-core/src/main/java/org/ethereum/util/RLPItem.java @@ -19,29 +19,40 @@ package org.ethereum.util; +import co.rsk.core.types.bytes.BytesSlice; + /** * @author Roman Mandeleil * @since 21.04.14 */ public class RLPItem implements RLPElement { - private final byte[] rlpData; + private final BytesSlice rlpData; - public RLPItem(byte[] rlpData) { + public RLPItem(BytesSlice rlpData) { this.rlpData = rlpData; } + protected BytesSlice getRLPBytes() { + if (rlpData.length() == 0) { + return null; + } + + return rlpData; + } + @Override public byte[] getRLPData() { - if (rlpData.length == 0) { + BytesSlice rlpBytes = getRLPBytes(); + if (rlpBytes == null) { return null; } - return rlpData; + return rlpBytes.copyArray(); } @Override public byte[] getRLPRawData() { - return rlpData; + return rlpData.copyArray(); } } diff --git a/rskj-core/src/main/java/org/ethereum/util/RLPList.java b/rskj-core/src/main/java/org/ethereum/util/RLPList.java index c4a5f035f53..e9ec39a2dc0 100644 --- a/rskj-core/src/main/java/org/ethereum/util/RLPList.java +++ b/rskj-core/src/main/java/org/ethereum/util/RLPList.java @@ -19,7 +19,8 @@ package org.ethereum.util; -import java.util.Arrays; +import co.rsk.core.types.bytes.BytesSlice; + import java.util.List; /** @@ -30,7 +31,7 @@ public class RLPList extends RLPItem implements RLPElement { private List elements; private final int offset; - public RLPList(byte[] rlpData, int offset) { + public RLPList(BytesSlice rlpData, int offset) { super(rlpData); this.offset = offset; } @@ -52,8 +53,8 @@ private void checkElements() { return; } - byte[] bytes = this.getRLPData(); - byte[] content = Arrays.copyOfRange(bytes, offset, bytes.length); + BytesSlice bytes = getRLPBytes(); + BytesSlice content = bytes.slice(offset, bytes.length()); this.elements = RLP.decode2(content); } diff --git a/rskj-core/src/main/java/org/ethereum/util/Utils.java b/rskj-core/src/main/java/org/ethereum/util/Utils.java index a2d23853a51..1236b0caeea 100644 --- a/rskj-core/src/main/java/org/ethereum/util/Utils.java +++ b/rskj-core/src/main/java/org/ethereum/util/Utils.java @@ -19,6 +19,7 @@ package org.ethereum.util; +import co.rsk.core.types.bytes.BytesSlice; import org.bouncycastle.util.encoders.DecoderException; import org.bouncycastle.util.encoders.Hex; import java.lang.reflect.Array; @@ -206,15 +207,15 @@ public static boolean contains(List list, byte[] valueToFind) { return false; } - public static void validateArrayAllegedSize(byte[] data, int offset, int allegedSize) { - if (data.length < Math.addExact(allegedSize, offset)) { + public static void validateArrayAllegedSize(BytesSlice data, int offset, int allegedSize) { + if (data.length() < Math.addExact(allegedSize, offset)) { throw new IllegalArgumentException("The specified size exceeds the size of the payload"); } } - public static byte[] safeCopyOfRange(byte[] data, int from, int size) { + public static byte[] safeCopyOfRange(BytesSlice data, int from, int size) { validateArrayAllegedSize(data, from, size); - return Arrays.copyOfRange(data, from, from + size); + return data.copyArrayOfRange(from, from + size); } public static boolean isDecimalString(String s) { diff --git a/rskj-core/src/test/java/co/rsk/core/CallTransactionTest.java b/rskj-core/src/test/java/co/rsk/core/CallTransactionTest.java index e3320bf3380..4fe0c0c870b 100644 --- a/rskj-core/src/test/java/co/rsk/core/CallTransactionTest.java +++ b/rskj-core/src/test/java/co/rsk/core/CallTransactionTest.java @@ -18,6 +18,7 @@ package co.rsk.core; +import co.rsk.core.types.bytes.Bytes; import org.bouncycastle.util.encoders.Hex; import org.ethereum.core.CallTransaction; import org.ethereum.solidity.SolidityType; @@ -115,7 +116,7 @@ void decodeString() { // string 104, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - Assertions.assertEquals("hello", type.decode(toDecode)); + Assertions.assertEquals("hello", type.decode(Bytes.of(toDecode))); } @Test diff --git a/rskj-core/src/test/java/co/rsk/core/TransactionTest.java b/rskj-core/src/test/java/co/rsk/core/TransactionTest.java index 3985c64155d..6917aaf01e4 100644 --- a/rskj-core/src/test/java/co/rsk/core/TransactionTest.java +++ b/rskj-core/src/test/java/co/rsk/core/TransactionTest.java @@ -19,6 +19,7 @@ package co.rsk.core; import co.rsk.config.TestSystemProperties; +import co.rsk.core.types.bytes.Bytes; import co.rsk.peg.BridgeSupportFactory; import co.rsk.peg.RepositoryBtcBlockStoreWithCache; import org.bouncycastle.util.BigIntegers; @@ -283,7 +284,7 @@ protected ProgramResult executeTransaction(Transaction tx) { track.rollback(); - System.out.println("Return value: " + new CallTransaction.IntType("uint").decode(executor.getResult().getHReturn())); + System.out.println("Return value: " + new CallTransaction.IntType("uint").decode(Bytes.of(executor.getResult().getHReturn()))); } // now executing the JSON test transaction diff --git a/rskj-core/src/test/java/co/rsk/core/types/bytes/BytesSliceTest.java b/rskj-core/src/test/java/co/rsk/core/types/bytes/BytesSliceTest.java index 93707953604..001aee51f35 100644 --- a/rskj-core/src/test/java/co/rsk/core/types/bytes/BytesSliceTest.java +++ b/rskj-core/src/test/java/co/rsk/core/types/bytes/BytesSliceTest.java @@ -19,17 +19,56 @@ package co.rsk.core.types.bytes; +import co.rsk.util.Functions; import org.junit.jupiter.api.Test; +import java.util.Arrays; + import static org.junit.jupiter.api.Assertions.*; class BytesSliceTest { + @Test + void testBytesLength() { + assertEquals(0, Bytes.of(new byte[]{}).slice(0, 0).length()); + assertEquals(0, Bytes.of(new byte[]{1}).slice(0, 0).length()); + assertEquals(1, Bytes.of(new byte[]{1}).slice(0, 1).length()); + assertEquals(0, Bytes.of(new byte[]{1,2,3}).slice(1, 1).length()); + assertEquals(1, Bytes.of(new byte[]{1,2,3}).slice(1, 2).length()); + assertEquals(2, Bytes.of(new byte[]{1,2,3}).slice(0, 2).length()); + assertEquals(3, Bytes.of(new byte[]{1,2,3}).slice(0, 3).length()); + } + + @Test + void testBytesAt() { + assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{}).slice(0, 0).byteAt(0)); + assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1}).slice(0, 1).byteAt(1)); + assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1}).slice(0, 1).byteAt(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1,2,3}).slice(1, 2).byteAt(1)); + assertEquals(1, Bytes.of(new byte[]{1}).slice(0, 1).byteAt(0)); + assertEquals(2, Bytes.of(new byte[]{1,2}).slice(0, 2).byteAt(1)); + assertEquals(2, Bytes.of(new byte[]{1,2,3}).slice(1, 2).byteAt(0)); + assertEquals(4, Bytes.of(new byte[]{1,2,3,4}).slice(2, 4).byteAt(1)); + } + + @Test + void testBytesSliceArraycopy() { + checkArraycopy((src, srcPos, dest, destPos, length) -> Bytes.of((byte[]) src).slice(1, 4).arraycopy(srcPos, (byte[]) dest, destPos, length)); + } + + @Test + void testBytesSliceArraycopyMimicsSystemOne() { + checkArraycopy((src, srcPos, dest, destPos, length) -> System.arraycopy(Arrays.copyOfRange((byte[]) src, 1, 4), srcPos, dest, destPos, length)); + } + @Test void testCopyArrayOfRange() { - byte[] bArray = new byte[]{1, 2, 3, 4, 5, 6}; - byte[] expectedResult = new byte[]{3, 4, 5}; - assertArrayEquals(expectedResult, Bytes.of(bArray).slice(0, bArray.length).copyArrayOfRange(2, 5)); + checkCopyOfRange(BytesSlice::copyArrayOfRange, (origin, from, to) -> Bytes.of(origin).slice(from, to)); + } + + @Test + void testCopyArrayOfRangeMimicsSystemOne() { + checkCopyOfRange(Arrays::copyOfRange, Arrays::copyOfRange); } @Test @@ -74,4 +113,44 @@ void testEmptySlice() { assertEquals(0, actualResult.length()); assertArrayEquals(expectedResult, actualResult.copyArray()); } + + private static void checkArraycopy(Functions.Action5 fun) { + byte[] dest = new byte[3]; + byte[] origin = new byte[]{1,2,3,4,5}; + + assertThrows(NullPointerException.class, () -> fun.apply(origin, 0, null, 0, 3)); + + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, -1, dest, 0, 3)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, -1, 3)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 0, 4)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 1, dest, 0, 3)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 1, 3)); + + assertArrayEquals(new byte[3], dest); // yet unmodified + + fun.apply(origin, 0, dest, 0, 3); + assertArrayEquals(new byte[]{2,3,4}, dest); + + byte[] dest2 = new byte[3]; + fun.apply(origin, 1, dest2, 1, 1); + assertArrayEquals(new byte[]{0,3,0}, dest2); + } + + private static void checkCopyOfRange(Functions.Function3 fun, + Functions.Function3 slicer) { + byte[] bArray = new byte[]{1, 2, 3, 4, 5, 6}; + + assertEquals(bArray.length, fun.apply(slicer.apply(bArray, 0, 6), 0, 6).length); + assertNotSame(bArray, fun.apply(slicer.apply(bArray, 0, 6), 0, 6)); + + assertArrayEquals(new byte[]{3, 4, 5}, fun.apply(slicer.apply(bArray, 0, 6), 2, 5)); + assertArrayEquals(new byte[]{3, 4}, fun.apply(slicer.apply(bArray, 1, 5), 1, 3)); + assertArrayEquals(new byte[]{3, 4, 5, 0, 0, 0, 0}, fun.apply(slicer.apply(bArray, 1, 5), 1, 8)); + assertArrayEquals(new byte[]{}, fun.apply(slicer.apply(bArray, 1, 5), 4, 4)); + + assertThrows(IllegalArgumentException.class, () -> fun.apply(slicer.apply(bArray, 1, 5), 1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(slicer.apply(bArray, 1, 5), -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(slicer.apply(bArray, 1, 5), 5, 5)); + } } diff --git a/rskj-core/src/test/java/co/rsk/core/types/bytes/BytesTest.java b/rskj-core/src/test/java/co/rsk/core/types/bytes/BytesTest.java index b60ca431ddc..7cce5532f17 100644 --- a/rskj-core/src/test/java/co/rsk/core/types/bytes/BytesTest.java +++ b/rskj-core/src/test/java/co/rsk/core/types/bytes/BytesTest.java @@ -19,10 +19,13 @@ package co.rsk.core.types.bytes; +import co.rsk.util.Functions; import org.ethereum.TestUtils; import org.ethereum.util.ByteUtil; import org.junit.jupiter.api.Test; +import java.util.Arrays; + import static org.junit.jupiter.api.Assertions.*; class BytesTest { @@ -34,6 +37,41 @@ void testBytesOf() { assertNotNull(Bytes.of(new byte[]{1})); } + @Test + void testBytesLength() { + assertEquals(0, Bytes.of(new byte[]{}).length()); + assertEquals(1, Bytes.of(new byte[]{1}).length()); + } + + @Test + void testBytesAt() { + assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{}).byteAt(0)); + assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1}).byteAt(1)); + assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1}).byteAt(-1)); + assertEquals(1, Bytes.of(new byte[]{1}).byteAt(0)); + assertEquals(2, Bytes.of(new byte[]{1,2}).byteAt(1)); + } + + @Test + void testBytesArraycopy() { + checkArraycopy((src, srcPos, dest, destPos, length) -> Bytes.of((byte[]) src).arraycopy(srcPos, (byte[]) dest, destPos, length)); + } + + @Test + void testBytesArraycopyMimicsSystemOne() { + checkArraycopy(System::arraycopy); + } + + @Test + void testCopyArrayOfRange() { + checkCopyOfRange((original, from, to) -> Bytes.of(original).copyArrayOfRange(from, to)); + } + + @Test + void testCopyArrayOfRangeMimicsSystemOne() { + checkCopyOfRange(Arrays::copyOfRange); + } + @Test void testToPrintableString() { assertEquals("0a", Bytes.toPrintableString(new byte[]{10})); @@ -83,13 +121,17 @@ void testShortEnoughBytesToString() { @Test void testLongBytesToString() { - byte[] bArray1 = TestUtils.generateBytes("hash1",15); + byte[] bArray1 = TestUtils.generateBytes("hash1",16); byte[] bArray2 = TestUtils.generateBytes("hash2",15); byte[] finalArray = ByteUtil.merge(bArray1, new byte[]{1, 2, 3}, bArray2); - assertEquals(33, finalArray.length); + assertEquals(34, finalArray.length); + + Bytes bytes = Bytes.of(finalArray); + + assertEquals(64, String.format("%s", bytes).length()); - String actualMessage = String.format("Some '%s' hex", Bytes.of(finalArray)); + String actualMessage = String.format("Some '%s' hex", bytes); String expectedMessage = "Some '" + ByteUtil.toHexString(bArray1) + ".." @@ -116,4 +158,43 @@ void testEmptyBytesToString() { assertEquals(expectedMessage, actualMessage); } -} \ No newline at end of file + + private static void checkArraycopy(Functions.Action5 fun) { + byte[] dest = new byte[5]; + byte[] origin = new byte[]{1,2,3,4,5}; + + assertThrows(NullPointerException.class, () -> fun.apply(origin, 0, null, 0, 5)); + + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, -1, dest, 0, 5)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, -1, 5)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 0, 6)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 1, dest, 0, 5)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 1, 5)); + + assertArrayEquals(new byte[5], dest); // yet unmodified + + fun.apply(origin, 0, dest, 0, 5); + assertArrayEquals(new byte[]{1,2,3,4,5}, dest); + + byte[] dest2 = new byte[5]; + fun.apply(origin, 1, dest2, 1, 3); + assertArrayEquals(new byte[]{0,2,3,4,0}, dest2); + } + + private static void checkCopyOfRange(Functions.Function3 fun) { + byte[] bArray = new byte[]{1, 2, 3, 4, 5}; + + assertEquals(bArray.length, fun.apply(bArray, 0, 5).length); + assertNotSame(bArray, fun.apply(bArray, 0, 5)); + + assertArrayEquals(new byte[]{2, 3, 4}, fun.apply(bArray, 1, 4)); + assertArrayEquals(new byte[]{2}, fun.apply(bArray, 1, 2)); + assertArrayEquals(new byte[]{2, 3, 4, 5, 0, 0, 0}, fun.apply(bArray, 1, 8)); + assertArrayEquals(new byte[]{}, fun.apply(bArray, 3, 3)); + + assertThrows(IllegalArgumentException.class, () -> fun.apply(bArray, 1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(bArray, -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(bArray, 6, 6)); + } +} diff --git a/rskj-core/src/test/java/co/rsk/util/Functions.java b/rskj-core/src/test/java/co/rsk/util/Functions.java new file mode 100644 index 00000000000..de687407e28 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/util/Functions.java @@ -0,0 +1,30 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * (derived from ethereumJ library, Copyright (c) 2016 ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.util; + +public interface Functions { + interface Function3 { + TResult apply(T1 arg1, T2 arg2, T3 arg3); + } + + interface Action5 { + void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + } +} diff --git a/rskj-core/src/test/java/org/ethereum/core/TransactionTest.java b/rskj-core/src/test/java/org/ethereum/core/TransactionTest.java index ef7d3ecba56..2ed2e3ec93d 100644 --- a/rskj-core/src/test/java/org/ethereum/core/TransactionTest.java +++ b/rskj-core/src/test/java/org/ethereum/core/TransactionTest.java @@ -23,6 +23,7 @@ import co.rsk.core.RskAddress; import co.rsk.core.TransactionExecutorFactory; import co.rsk.core.genesis.TestGenesisLoader; +import co.rsk.core.types.bytes.Bytes; import co.rsk.crypto.Keccak256; import co.rsk.db.HashMapBlocksIndex; import co.rsk.db.MutableTrieImpl; @@ -508,7 +509,7 @@ protected ProgramResult executeTransaction(Transaction tx) { track.rollback(); - System.out.println("Return value: " + new CallTransaction.IntType("uint").decode(executor.getResult().getHReturn())); + System.out.println("Return value: " + new CallTransaction.IntType("uint").decode(Bytes.of(executor.getResult().getHReturn()))); } // now executing the JSON test transaction diff --git a/rskj-core/src/test/java/org/ethereum/solidity/SolidityTypeTest.java b/rskj-core/src/test/java/org/ethereum/solidity/SolidityTypeTest.java index 23258e97cfd..36659028521 100644 --- a/rskj-core/src/test/java/org/ethereum/solidity/SolidityTypeTest.java +++ b/rskj-core/src/test/java/org/ethereum/solidity/SolidityTypeTest.java @@ -1,5 +1,6 @@ package org.ethereum.solidity; +import co.rsk.core.types.bytes.Bytes; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -16,7 +17,7 @@ void TestDynamicArrayTypeWithInvalidDataSize() { input[31] = 0x10; // Indicating we should have 16 elements in the array should fail try { - dat.decode(input, 0); + dat.decode(Bytes.of(input), 0); Assertions.fail(); } catch (IllegalArgumentException e) { // Only acceptable exception @@ -35,7 +36,7 @@ void TestDynamicArrayTypeWithInvalidDataSize() { input[97] = 0x69; try { - dat.decode(input, 0); + dat.decode(Bytes.of(input), 0); Assertions.fail(); } catch (IllegalArgumentException e) { // Only acceptable exception @@ -60,7 +61,7 @@ void TestDynamicArrayTypeWithInvalidDataSize() { input[163] = 0x69; try { - dat.decode(input, 0); + dat.decode(Bytes.of(input), 0); Assertions.fail(); } catch (IllegalArgumentException e) { // Only acceptable exception @@ -96,7 +97,7 @@ void TestDynamicArrayTypeWithValidDataSize() { input[229] = 0x68; input[230] = 0x75; - Object[] ret = (Object[])dat.decode(input, 0); + Object[] ret = (Object[])dat.decode(Bytes.of(input), 0); Assertions.assertEquals(3, ret.length); Assertions.assertTrue(ret[0].toString().contains("hi")); Assertions.assertTrue(ret[1].toString().contains("ih")); @@ -114,7 +115,7 @@ void TestStaticArrayTypeWithInvalidSize() { // the actual data input[32] = 0x68; input[33] = 0x69; - dat.decode(input, 0); + dat.decode(Bytes.of(input), 0); Assertions.fail("should have failed"); } catch (IllegalArgumentException e) { @@ -130,7 +131,7 @@ void TestStaticArrayTypeWithInvalidSize() { // the actual data input[32] = 0x68; input[33] = 0x69; - dat.decode(input, 0); + dat.decode(Bytes.of(input), 0); Assertions.fail("should have failed"); } catch (IllegalArgumentException e) { @@ -149,7 +150,7 @@ void TestStaticArrayType() { input[32] = 0x68; input[33] = 0x69; - Object[] ret = dat.decode(input, 0); + Object[] ret = dat.decode(Bytes.of(input), 0); Assertions.assertEquals(1, ret.length); Assertions.assertTrue(ret[0].toString().contains("hi")); } @@ -159,7 +160,7 @@ void TestIntType() { // Should fail, the array is smaller than the offset we define try { byte[] input = new byte[] {0x4f, 0x4f}; - SolidityType.IntType.decodeInt(input, 12); + SolidityType.IntType.decodeInt(Bytes.of(input), 12); Assertions.fail("should have failed to deserialize the array"); } catch (IllegalArgumentException e) { // Only acceptable exception @@ -168,11 +169,11 @@ void TestIntType() { // Should get a valid number input[31] = 0x01; - BigInteger value = SolidityType.IntType.decodeInt(input, 0); + BigInteger value = SolidityType.IntType.decodeInt(Bytes.of(input), 0); Assertions.assertEquals(1, value.intValue()); // Should get a valid number - value = SolidityType.IntType.decodeInt(input, 32); + value = SolidityType.IntType.decodeInt(Bytes.of(input), 32); Assertions.assertEquals(0, value.intValue()); } diff --git a/rskj-core/src/test/java/org/ethereum/util/RLPTest.java b/rskj-core/src/test/java/org/ethereum/util/RLPTest.java index 58bbaa5218f..6c5b9d3a2df 100644 --- a/rskj-core/src/test/java/org/ethereum/util/RLPTest.java +++ b/rskj-core/src/test/java/org/ethereum/util/RLPTest.java @@ -1071,4 +1071,103 @@ void testIncorrectDecodeInt(){ byte[] payload = {(byte) 0x84, (byte) 0x00, (byte) 0x00, (byte) 0x84, (byte) 0x00, (byte) 0x0f, (byte) 0xab}; assertThrows(RLPException.class, () -> RLP.decodeInt(payload, 3)); } + + /** + * Get exactly one message payload + */ + private static void fullTraverse(byte[] msgData, int level, int startPos, int endPos, int levelToIndex, Queue index) { + try { + + if (msgData == null || msgData.length == 0) { + return; + } + int pos = startPos; + + while (pos < endPos) { + + if (level == levelToIndex) { + index.add(pos); + } + + // It's a list with a payload more than 55 bytes + // data[0] - 0xF7 = how many next bytes allocated + // for the length of the list + if ((msgData[pos] & 0xFF) >= OFFSET_LONG_LIST) { + + byte lengthOfLength = (byte) (msgData[pos] - OFFSET_LONG_LIST); + int length = calcLength(lengthOfLength, msgData, pos); + + // now we can parse an item for data[1]..data[length] + System.out.println("-- level: [" + level + + "] Found big list length: " + length); + + fullTraverse(msgData, level + 1, pos + lengthOfLength + 1, + pos + lengthOfLength + length, levelToIndex, index); + + pos += lengthOfLength + length + 1; + continue; + } + // It's a list with a payload less than 55 bytes + if ((msgData[pos] & 0xFF) >= OFFSET_SHORT_LIST + && (msgData[pos] & 0xFF) < OFFSET_LONG_LIST) { + + byte length = (byte) ((msgData[pos] & 0xFF) - OFFSET_SHORT_LIST); + + System.out.println("-- level: [" + level + + "] Found small list length: " + length); + + fullTraverse(msgData, level + 1, pos + 1, pos + length + 1, + levelToIndex, index); + + pos += 1 + length; + continue; + } + // It's an item with a payload more than 55 bytes + // data[0] - 0xB7 = how much next bytes allocated for + // the length of the string + if ((msgData[pos] & 0xFF) >= OFFSET_LONG_ITEM + && (msgData[pos] & 0xFF) < OFFSET_SHORT_LIST) { + + byte lengthOfLength = (byte) (msgData[pos] - OFFSET_LONG_ITEM); + int length = calcLength(lengthOfLength, msgData, pos); + + // now we can parse an item for data[1]..data[length] + System.out.println("-- level: [" + level + + "] Found big item length: " + length); + pos += lengthOfLength + length + 1; + + continue; + } + // It's an item less than 55 bytes long, + // data[0] - 0x80 == length of the item + if ((msgData[pos] & 0xFF) > OFFSET_SHORT_ITEM + && (msgData[pos] & 0xFF) < OFFSET_LONG_ITEM) { + + byte length = (byte) ((msgData[pos] & 0xFF) - OFFSET_SHORT_ITEM); + + System.out.println("-- level: [" + level + + "] Found small item length: " + length); + pos += 1 + length; + continue; + } + // null item + if ((msgData[pos] & 0xFF) == OFFSET_SHORT_ITEM) { + System.out.println("-- level: [" + level + + "] Found null item: "); + pos += 1; + continue; + } + // single byte item + if ((msgData[pos] & 0xFF) < OFFSET_SHORT_ITEM) { + System.out.println("-- level: [" + level + + "] Found single item: "); + pos += 1; + continue; + } + } + } catch (Throwable th) { + throw new RuntimeException("RLP wrong encoding", + th.fillInStackTrace()); + } + } } diff --git a/rskj-core/src/test/java/org/ethereum/util/UtilsTest.java b/rskj-core/src/test/java/org/ethereum/util/UtilsTest.java index a42711cb4ec..bb301d035e1 100644 --- a/rskj-core/src/test/java/org/ethereum/util/UtilsTest.java +++ b/rskj-core/src/test/java/org/ethereum/util/UtilsTest.java @@ -19,12 +19,11 @@ package org.ethereum.util; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - +import co.rsk.core.types.bytes.Bytes; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import java.math.BigInteger; @@ -112,7 +111,7 @@ void testAddressStringToBytes() { @Test void TestValidateArrayWithOffset() { - byte[] data = new byte[10]; + Bytes data = Bytes.of(new byte[10]); // Valid indices Utils.validateArrayAllegedSize(data, 1, 0); Utils.validateArrayAllegedSize(data, 8, 1); @@ -137,13 +136,13 @@ void TestValidateArrayWithOffset() { // Only type of exception expected } try { - Utils.validateArrayAllegedSize(new byte[0], 1, 0); + Utils.validateArrayAllegedSize(Bytes.of(new byte[0]), 1, 0); fail("should have failed"); } catch (IllegalArgumentException e) { // Only type of exception expected } - byte[] noData = null; + Bytes noData = null; try { Utils.validateArrayAllegedSize(noData, 1, 1); fail("should have failed"); @@ -156,33 +155,33 @@ void TestValidateArrayWithOffset() { @Test void TestSafeCopyOfRangeWithValidArrays() { - Assertions.assertDoesNotThrow(() -> Utils.safeCopyOfRange(new byte[2], 0, 1)); - Assertions.assertDoesNotThrow(() -> Utils.safeCopyOfRange(new byte[100], 97, 3)); - Assertions.assertDoesNotThrow(() -> Utils.safeCopyOfRange(new byte[0], 0, 0)); + Assertions.assertDoesNotThrow(() -> Utils.safeCopyOfRange(Bytes.of(new byte[2]), 0, 1)); + Assertions.assertDoesNotThrow(() -> Utils.safeCopyOfRange(Bytes.of(new byte[100]), 97, 3)); + Assertions.assertDoesNotThrow(() -> Utils.safeCopyOfRange(Bytes.of(new byte[0]), 0, 0)); } @Test void TestSafeCopyOfRangeWithInvalidArrays() { try { - Utils.safeCopyOfRange(new byte[2], 1, 2); + Utils.safeCopyOfRange(Bytes.of(new byte[2]), 1, 2); fail("should have failed"); } catch (IllegalArgumentException e){ } try { - Utils.safeCopyOfRange(new byte[100], 98, 3); + Utils.safeCopyOfRange(Bytes.of(new byte[100]), 98, 3); fail("should have failed"); } catch (IllegalArgumentException e){ } try { - Utils.safeCopyOfRange(new byte[0], 0, 1); + Utils.safeCopyOfRange(Bytes.of(new byte[0]), 0, 1); fail("should have failed"); } catch (IllegalArgumentException e){ } try { - Utils.safeCopyOfRange(new byte[0], 1, 0); + Utils.safeCopyOfRange(Bytes.of(new byte[0]), 1, 0); fail("should have failed"); } catch (IllegalArgumentException e){