From 3bcad4523bba72de2f7f5707a552180c00827915 Mon Sep 17 00:00:00 2001 From: Luis Pinto Date: Thu, 26 Sep 2024 14:14:47 +0100 Subject: [PATCH] fix little-endian order for encoding BASIC_DATA_LEAF values (#70) Signed-off-by: Luis Pinto --- .../trie/verkle/util/SuffixTreeEncoder.java | 87 +++++++--- .../verkle/util/SuffixTreeEncoderTest.java | 155 +++++++++++++++++- 2 files changed, 219 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/util/SuffixTreeEncoder.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/util/SuffixTreeEncoder.java index 57335152..d2e9b31c 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/util/SuffixTreeEncoder.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/util/SuffixTreeEncoder.java @@ -20,56 +20,105 @@ public class SuffixTreeEncoder { private static final int VERSION_OFFSET = 0; + private static final int VERSION_BYTE_SIZE = 1; private static final int CODE_SIZE_OFFSET = 5; + private static final int CODE_SIZE_BYTE_SIZE = 3; private static final int NONCE_OFFSET = 8; + private static final int NONCE_BYTE_SIZE = 8; private static final int BALANCE_OFFSET = 16; + private static final int BALANCE_BYTE_SIZE = 16; + private static final Bytes32 VERSION_VALUE_MASK; private static final Bytes32 CODE_SIZE_VALUE_MASK; private static final Bytes32 NONCE_VALUE_MASK; private static final Bytes32 BALANCE_VALUE_MASK; static { - VERSION_VALUE_MASK = - encodeIntoBasicDataLeaf(Bytes.repeat((byte) 0xff, 1), VERSION_OFFSET).not(); - CODE_SIZE_VALUE_MASK = - encodeIntoBasicDataLeaf(Bytes.repeat((byte) 0xff, 3), CODE_SIZE_OFFSET).not(); - NONCE_VALUE_MASK = encodeIntoBasicDataLeaf(Bytes.repeat((byte) 0xff, 8), NONCE_OFFSET).not(); - BALANCE_VALUE_MASK = - encodeIntoBasicDataLeaf(Bytes.repeat((byte) 0xff, 16), BALANCE_OFFSET).not(); + VERSION_VALUE_MASK = createMask(VERSION_OFFSET, VERSION_BYTE_SIZE); + CODE_SIZE_VALUE_MASK = createMask(CODE_SIZE_OFFSET, CODE_SIZE_BYTE_SIZE); + NONCE_VALUE_MASK = createMask(NONCE_OFFSET, NONCE_BYTE_SIZE); + BALANCE_VALUE_MASK = createMask(BALANCE_OFFSET, BALANCE_BYTE_SIZE); + } + + private static Bytes32 createMask(final int offset, final int size) { + Bytes value = Bytes.repeat((byte) 0xff, size); + return encodeIntoBasicDataLeaf(value, offset).not(); } public static Bytes32 eraseVersion(final Bytes32 value) { return value.and(VERSION_VALUE_MASK); } - public static Bytes32 eraseCodeSize(final Bytes32 value) { + private static Bytes32 eraseCodeSize(final Bytes32 value) { return value.and(CODE_SIZE_VALUE_MASK); } - public static Bytes32 eraseNonce(final Bytes32 value) { + private static Bytes32 eraseNonce(final Bytes32 value) { return value.and(NONCE_VALUE_MASK); } - public static Bytes32 eraseBalance(final Bytes32 value) { + private static Bytes32 eraseBalance(final Bytes32 value) { return value.and(BALANCE_VALUE_MASK); } - public static Bytes32 addVersionIntoValue(final Bytes32 value, final Bytes version) { + /** + * Sets the new version and encodes it inside `value`. + * + * @param value on which to set the version part + * @param version value for the new version + * @return updated value with the new version set + */ + public static Bytes32 setVersionInValue(Bytes32 value, final Bytes version) { + checkSize(version, VERSION_BYTE_SIZE); + value = eraseVersion(value); return value.or(encodeIntoBasicDataLeaf(version, VERSION_OFFSET)); } - public static Bytes32 addCodeSizeIntoValue(final Bytes32 value, final Bytes codeSize) { + /** + * Sets the new code size and encodes it inside `value`. + * + * @param value on which to set the code size part + * @param codeSize value for the new code size + * @return updated value with the new code size set + */ + public static Bytes32 setCodeSizeInValue(Bytes32 value, final Bytes codeSize) { + checkSize(codeSize, CODE_SIZE_BYTE_SIZE); + value = eraseCodeSize(value); return value.or(encodeIntoBasicDataLeaf(codeSize, CODE_SIZE_OFFSET)); } - public static Bytes32 addNonceIntoValue(final Bytes32 value, final Bytes nonce) { + /** + * Sets the new nonce and encodes it inside `value`. + * + * @param value on which to set the nonce part + * @param nonce value for the new nonce + * @return updated value with the new nonce set + */ + public static Bytes32 setNonceInValue(Bytes32 value, final Bytes nonce) { + checkSize(nonce, NONCE_BYTE_SIZE); + value = eraseNonce(value); return value.or(encodeIntoBasicDataLeaf(nonce, NONCE_OFFSET)); } - public static Bytes32 addBalanceIntoValue(final Bytes32 value, final Bytes balance) { + /** + * Sets the new balance and encodes it inside `value`. + * + * @param value on which to set the balance part + * @param balance value for the new balance + * @return updated value with the new balance set + */ + public static Bytes32 setBalanceInValue(Bytes32 value, final Bytes balance) { + checkSize(balance, BALANCE_BYTE_SIZE); + value = eraseBalance(value); return value.or(encodeIntoBasicDataLeaf(balance, BALANCE_OFFSET)); } + private static void checkSize(final Bytes value, final int requiredSize) { + if (value.size() != requiredSize) { + throw new IllegalArgumentException("value should have size=" + requiredSize); + } + } + /** * Encoding of a field into the BasicDataLeaf 32 byte value, using Little-Endian order. * @@ -78,21 +127,21 @@ public static Bytes32 addBalanceIntoValue(final Bytes32 value, final Bytes balan * @throws IllegalArgumentException if `value` does not fit within 32 bytes after being encoded * @return encoded BasicDataLeaf value */ - public static Bytes32 encodeIntoBasicDataLeaf(final Bytes value, final int byteShift) { - Bytes32 value32Bytes = Bytes32.leftPad(value); + static Bytes32 encodeIntoBasicDataLeaf(final Bytes value, final int byteShift) { + Bytes32 value32Bytes = Bytes32.rightPad(value); if (byteShift == 0) { return value32Bytes; } else if (byteShift < 0) { throw new IllegalArgumentException( "invalid byteShift " + byteShift + " must be greater than zero"); - } else if (value32Bytes.numberOfLeadingZeroBytes() < byteShift) { - int valueSizeBytes = 32 - value32Bytes.numberOfLeadingZeroBytes() + byteShift; + } else if (value32Bytes.numberOfTrailingZeroBytes() < byteShift) { + int valueSizeBytes = 32 - value32Bytes.numberOfTrailingZeroBytes() + byteShift; throw new IllegalArgumentException( "value must be 32 bytes but becomes " + valueSizeBytes + " bytes with byteShift " + byteShift); } - return value32Bytes.shiftLeft(byteShift * 8); + return value32Bytes.shiftRight(byteShift * 8); } } diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/util/SuffixTreeEncoderTest.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/util/SuffixTreeEncoderTest.java index e2190474..bbbcc305 100644 --- a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/util/SuffixTreeEncoderTest.java +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/util/SuffixTreeEncoderTest.java @@ -22,13 +22,32 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.ArgumentConverter; +import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; public class SuffixTreeEncoderTest { - public static Stream valuesStart() { + private static class StringToBytes32Converter implements ArgumentConverter { + @Override + public Object convert(Object source, ParameterContext context) + throws ArgumentConversionException { + if (!(source instanceof CharSequence)) { + throw new ArgumentConversionException("source must be a String"); + } + String hexString = (String) source; + return Bytes32.fromHexString(hexString); + } + } + + public static Stream valuesEnd() { return Stream.of( Arguments.of(Bytes32.ZERO, Bytes32.ZERO), Arguments.of( @@ -59,7 +78,7 @@ public static Stream valuesMiddle() { Arguments.of( Bytes.of(0xff), Bytes32.fromHexString( - "0x00000000000000000000000000000000FF000000000000000000000000000000")), + "0x000000000000000000000000000000FF00000000000000000000000000000000")), Arguments.of( Bytes.repeat((byte) 0xff, 12), Bytes32.fromHexString( @@ -71,10 +90,10 @@ public static Stream valuesMiddle() { Arguments.of( Bytes.fromHexString("0x1123d3"), Bytes32.fromHexString( - "0x0000000000000000000000000000001123D30000000000000000000000000000"))); + "0x00000000000000000000000000001123D3000000000000000000000000000000"))); } - public static Stream valuesEnd() { + public static Stream valuesStart() { return Stream.of( Arguments.of(Bytes32.ZERO, Bytes32.ZERO), Arguments.of( @@ -129,4 +148,132 @@ void encodeBytesOutsideRange(final Bytes value, final int byteShift) { IllegalArgumentException.class, () -> SuffixTreeEncoder.encodeIntoBasicDataLeaf(value, byteShift)); } + + @ParameterizedTest + @ValueSource( + strings = { + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0100000000000000000000000000000000000000000000000000000000000000" + }) + void setVersionInValue( + @ConvertWith(StringToBytes32Converter.class) final Bytes32 basicDataLeafValue) { + Bytes version = Bytes.of(10); + assertEquals( + Bytes.fromHexString("0x0A00000000000000000000000000000000000000000000000000000000000000"), + SuffixTreeEncoder.setVersionInValue(basicDataLeafValue, version)); + } + + @Test + void setVersionInValue_differentSizeThrows() { + Bytes version = Bytes.ofUnsignedShort(10); + assertThrows( + IllegalArgumentException.class, + () -> SuffixTreeEncoder.setVersionInValue(Bytes32.ZERO, version)); + } + + @Test + void setVersionInValue_fullLeaf() { + final Bytes32 basicDataLeafValue = + Bytes32.fromHexString("0xAB343123BD1231214BC13213EF23434FF213124423247124FF12312EE12AC234"); + Bytes version = Bytes.of(10); + assertEquals( + Bytes.fromHexString("0x0A343123BD1231214BC13213EF23434FF213124423247124FF12312EE12AC234"), + SuffixTreeEncoder.setVersionInValue(basicDataLeafValue, version)); + } + + @ParameterizedTest + @ValueSource( + strings = { + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000AC000000000000000000000000000000000000000000000000" + }) + void setCodeSizeInValue( + @ConvertWith(StringToBytes32Converter.class) final Bytes32 basicDataLeafValue) { + Bytes codeSize = Bytes.repeat((byte) 0, 3).or(Bytes.of(54)); + assertEquals( + Bytes.fromHexString("0x0000000000000036000000000000000000000000000000000000000000000000"), + SuffixTreeEncoder.setCodeSizeInValue(basicDataLeafValue, codeSize)); + } + + @Test + void setCodeSizeInValue_differentSizeThrows() { + Bytes codeSize = Bytes.ofUnsignedLong(54); + assertThrows( + IllegalArgumentException.class, + () -> SuffixTreeEncoder.setCodeSizeInValue(Bytes32.ZERO, codeSize)); + } + + @Test + void setCodeSizeInValue_fullLeaf() { + final Bytes32 basicDataLeafValue = + Bytes32.fromHexString("0xAB343123BD1231214BC13213EF23434FF213124423247124FF12312EE12AC234"); + Bytes codeSize = Bytes.repeat((byte) 0, 3).or(Bytes.of(54)); + assertEquals( + Bytes.fromHexString("0xAB343123BD0000364BC13213EF23434FF213124423247124FF12312EE12AC234"), + SuffixTreeEncoder.setCodeSizeInValue(basicDataLeafValue, codeSize)); + } + + @ParameterizedTest + @ValueSource( + strings = { + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000100000000000000000000000000000000" + }) + void setNonceInValue( + @ConvertWith(StringToBytes32Converter.class) final Bytes32 basicDataLeafValue) { + Bytes nonce = Bytes.repeat((byte) 0, 8).or(Bytes.of(20)); + assertEquals( + Bytes.fromHexString("0x0000000000000000000000000000001400000000000000000000000000000000"), + SuffixTreeEncoder.setNonceInValue(basicDataLeafValue, nonce)); + } + + @Test + void setNonceInValue_differentSizeThrows() { + Bytes nonce = UInt256.valueOf(54); + assertThrows( + IllegalArgumentException.class, + () -> SuffixTreeEncoder.setNonceInValue(Bytes32.ZERO, nonce)); + } + + @Test + void setNonceInValue_fullLeaf() { + final Bytes32 basicDataLeafValue = + Bytes32.fromHexString("0xAB343123BD1231214BC13213EF23434FF213124423247124FF12312EE12AC234"); + Bytes nonce = Bytes.repeat((byte) 0, 8).or(Bytes.of(20)); + assertEquals( + Bytes.fromHexString("0xAB343123BD1231210000000000000014F213124423247124FF12312EE12AC234"), + SuffixTreeEncoder.setNonceInValue(basicDataLeafValue, nonce)); + } + + @ParameterizedTest + @ValueSource( + strings = { + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000000000F2" + }) + void setBalanceInValue( + @ConvertWith(StringToBytes32Converter.class) final Bytes32 basicDataLeafValue) { + Bytes balance = Bytes.repeat((byte) 0, 16).or(Bytes.fromHexString("0x3635C9ADC5DEA00000")); + assertEquals( + Bytes.fromHexString("0x00000000000000000000000000000000000000000000003635C9ADC5DEA00000"), + SuffixTreeEncoder.setBalanceInValue(basicDataLeafValue, balance)); + } + + @Test + void setBalanceInValue_differentSizeThrows() { + Bytes balance = Bytes.repeat((byte) 0, 32).or(Bytes.fromHexString("0x3635C9ADC5DEA00000")); + assertThrows( + IllegalArgumentException.class, + () -> SuffixTreeEncoder.setBalanceInValue(Bytes32.ZERO, balance)); + } + + @Test + void setBalanceInValue_fullLeaf() { + final Bytes32 basicDataLeafValue = + Bytes32.fromHexString("0xAB343123BD1231214BC13213EF23434FF213124423247124FF12312EE12AC234"); + Bytes balance = Bytes.repeat((byte) 0, 16).or(Bytes.fromHexString("0x3635C9ADC5DEA00000")); + assertEquals( + Bytes.fromHexString("0xAB343123BD1231214BC13213EF23434f000000000000003635C9ADC5DEA00000"), + SuffixTreeEncoder.setBalanceInValue(basicDataLeafValue, balance)); + } }