diff --git a/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/LengthBoundCalculator.java b/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/LengthBoundCalculator.java new file mode 100644 index 00000000000..b14bff82b38 --- /dev/null +++ b/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/LengthBoundCalculator.java @@ -0,0 +1,160 @@ +/* + * Copyright 2020 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.datastructures.util; + +import static tech.pegasys.teku.datastructures.util.SimpleOffsetSerializer.BOOLEAN_SIZE; +import static tech.pegasys.teku.datastructures.util.SimpleOffsetSerializer.UNSIGNED_LONG_SIZE; +import static tech.pegasys.teku.datastructures.util.SimpleOffsetSerializer.getOptionalReflectionInfo; +import static tech.pegasys.teku.datastructures.util.SimpleOffsetSerializer.getRequiredReflectionInfo; +import static tech.pegasys.teku.datastructures.util.SimpleOffsetSerializer.isBitvector; +import static tech.pegasys.teku.datastructures.util.SimpleOffsetSerializer.isPrimitive; +import static tech.pegasys.teku.datastructures.util.SimpleOffsetSerializer.isVariable; +import static tech.pegasys.teku.datastructures.util.SimpleOffsetSerializer.isVector; +import static tech.pegasys.teku.util.config.Constants.BYTES_PER_LENGTH_OFFSET; + +import java.lang.reflect.Field; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.ssz.SSZTypes.Bitlist; +import tech.pegasys.teku.ssz.SSZTypes.Bitvector; +import tech.pegasys.teku.ssz.SSZTypes.Bytes4; +import tech.pegasys.teku.ssz.SSZTypes.SSZList; +import tech.pegasys.teku.ssz.sos.ReflectionInformation; + +public class LengthBoundCalculator { + + static LengthBounds calculateLengthBounds(final Class type) { + final ReflectionInformation reflectionInfo = getRequiredReflectionInfo(type); + LengthBounds lengthBounds = LengthBounds.ZERO; + int sszListCount = 0; + int bitlistCount = 0; + int vectorCount = 0; + int bitvectorCount = 0; + for (Field field : reflectionInfo.getFields()) { + final Class fieldType = field.getType(); + final LengthBounds fieldLengthBounds; + if (getOptionalReflectionInfo(fieldType).isPresent()) { + fieldLengthBounds = calculateLengthBounds(fieldType); + + } else if (fieldType == Bitlist.class) { + fieldLengthBounds = calculateBitlistLength(reflectionInfo, bitlistCount); + bitlistCount++; + + } else if (fieldType == SSZList.class) { + fieldLengthBounds = calculateSszListLength(reflectionInfo, sszListCount); + sszListCount++; + + } else if (isVector(fieldType)) { + fieldLengthBounds = calculateSszVectorLength(reflectionInfo, vectorCount); + vectorCount++; + + } else if (isBitvector(fieldType)) { + fieldLengthBounds = calculateBitvectorLength(reflectionInfo, bitvectorCount); + bitvectorCount++; + + } else if (isPrimitive(fieldType)) { + fieldLengthBounds = new LengthBounds(getPrimitiveLength(fieldType)); + + } else { + throw new IllegalArgumentException( + "Don't know how to calculate length for " + fieldType.getSimpleName()); + } + + if (isVariable(fieldType)) { + // The fixed parts includes an offset in place of the variable length value + lengthBounds = lengthBounds.add(new LengthBounds(BYTES_PER_LENGTH_OFFSET.longValue())); + } + lengthBounds = lengthBounds.add(fieldLengthBounds); + } + return lengthBounds; + } + + private static LengthBounds calculateBitvectorLength( + final ReflectionInformation reflectionInfo, final int bitvectorCount) { + final LengthBounds fieldLengthBounds; + final Integer size = reflectionInfo.getBitvectorSizes().get(bitvectorCount); + final int serializationLength = Bitvector.sszSerializationLength(size); + fieldLengthBounds = new LengthBounds(serializationLength, serializationLength); + return fieldLengthBounds; + } + + private static LengthBounds calculateSszVectorLength( + final ReflectionInformation reflectionInfo, final int vectorCount) { + final LengthBounds fieldLengthBounds; + final Class elementType = reflectionInfo.getVectorElementTypes().get(vectorCount); + final int vectorLength = reflectionInfo.getVectorLengths().get(vectorCount); + final LengthBounds elementLengthBounds = getElementLengthBounds(elementType); + fieldLengthBounds = + new LengthBounds( + vectorLength * elementLengthBounds.getMin(), + vectorLength * elementLengthBounds.getMax()); + return fieldLengthBounds; + } + + private static LengthBounds calculateSszListLength( + final ReflectionInformation reflectionInfo, final int variableFieldCount) { + final LengthBounds fieldLengthBounds; + final Class listElementType = reflectionInfo.getListElementTypes().get(variableFieldCount); + final long listElementMaxSize = reflectionInfo.getListElementMaxSizes().get(variableFieldCount); + final LengthBounds elementLengthBounds = getElementLengthBounds(listElementType); + final long variableFieldOffsetsLength = + isVariable(listElementType) ? BYTES_PER_LENGTH_OFFSET.intValue() * listElementMaxSize : 0; + fieldLengthBounds = + new LengthBounds( + 0, elementLengthBounds.getMax() * listElementMaxSize + variableFieldOffsetsLength); + return fieldLengthBounds; + } + + private static LengthBounds calculateBitlistLength( + final ReflectionInformation reflectionInfo, final int variableFieldCount) { + final LengthBounds fieldLengthBounds; + final long maxSize = reflectionInfo.getBitlistElementMaxSizes().get(variableFieldCount); + fieldLengthBounds = + new LengthBounds( + Bitlist.sszSerializationLength(Math.toIntExact(0)), + Bitlist.sszSerializationLength(Math.toIntExact(maxSize))); + return fieldLengthBounds; + } + + private static LengthBounds getElementLengthBounds(final Class listElementType) { + if (isPrimitive(listElementType)) { + final int primitiveLength = getPrimitiveLength(listElementType); + return new LengthBounds(primitiveLength, primitiveLength); + } + return calculateLengthBounds(listElementType); + } + + private static int getPrimitiveLength(final Class classInfo) { + switch (classInfo.getSimpleName()) { + case "UnsignedLong": + return UNSIGNED_LONG_SIZE; + case "ArrayWrappingBytes32": + case "Bytes32": + return Bytes32.SIZE; + case "Bytes4": + return Bytes4.SIZE; + case "BLSSignature": + return BLSSignature.BLS_SIGNATURE_SIZE; + case "BLSPublicKey": + return BLSPublicKey.BLS_PUBKEY_SIZE; + case "Boolean": + case "boolean": + return BOOLEAN_SIZE; + default: + throw new IllegalArgumentException( + "Unknown length for primitive " + classInfo.getSimpleName()); + } + } +} diff --git a/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/LengthBounds.java b/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/LengthBounds.java new file mode 100644 index 00000000000..9a78cc7ff57 --- /dev/null +++ b/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/LengthBounds.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.datastructures.util; + +import com.google.common.base.MoreObjects; +import java.util.Objects; + +public class LengthBounds { + public static final LengthBounds ZERO = new LengthBounds(0, 0); + private final long min; + private final long max; + + public LengthBounds(final long fixedSize) { + this(fixedSize, fixedSize); + } + + public LengthBounds(final long min, final long max) { + this.min = min; + this.max = max; + } + + public long getMin() { + return min; + } + + public long getMax() { + return max; + } + + public LengthBounds add(final LengthBounds other) { + return new LengthBounds(this.min + other.min, this.max + other.max); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final LengthBounds that = (LengthBounds) o; + return getMin() == that.getMin() && getMax() == that.getMax(); + } + + @Override + public int hashCode() { + return Objects.hash(getMin(), getMax()); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("min", min).add("max", max).toString(); + } +} diff --git a/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/SimpleOffsetSerializer.java b/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/SimpleOffsetSerializer.java index c67e607747c..957e4746783 100644 --- a/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/SimpleOffsetSerializer.java +++ b/ethereum/datastructures/src/main/java/tech/pegasys/teku/datastructures/util/SimpleOffsetSerializer.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.datastructures.util; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static tech.pegasys.teku.util.config.Constants.BYTES_PER_LENGTH_OFFSET; import com.google.common.primitives.UnsignedLong; @@ -79,7 +80,10 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public class SimpleOffsetSerializer { + static final int UNSIGNED_LONG_SIZE = 8; + static final int BOOLEAN_SIZE = 1; public static HashMap classReflectionInfo = new HashMap<>(); + public static HashMap classLengthBounds = new HashMap<>(); public static void setConstants() { List classes = @@ -121,6 +125,10 @@ public static void setConstants() { for (Class classItem : classes) { classReflectionInfo.put(classItem, new ReflectionInformation(classItem)); } + + for (Class classItem : classes) { + classLengthBounds.put(classItem, LengthBoundCalculator.calculateLengthBounds(classItem)); + } } static { @@ -220,6 +228,10 @@ public static T deserialize(Bytes bytes, Class classInfo) { } } + public static LengthBounds getLengthBounds(final Class type) { + return checkNotNull(classLengthBounds.get(type), "Length bounds unknown for type %s", type); + } + private static void assertAllDataRead(SSZReader reader) { if (!reader.isComplete()) { throw new IllegalStateException("Unread data detected."); @@ -337,6 +349,8 @@ private static void deserializeVariableParts( throws InstantiationException, InvocationTargetException, IllegalAccessException { int variableObjectCounter = 0; + int bitlistCounter = 0; + int sszListCounter = 0; for (Integer variableFieldIndex : variableFieldIndices) { Class fieldClass = reflectionInformation.getFields()[variableFieldIndex].getType(); @@ -346,10 +360,9 @@ private static void deserializeVariableParts( : currentObjectStartByte + offsets.get(variableObjectCounter + 1); Object fieldObject = null; if (fieldClass == SSZList.class) { - Class listElementType = - reflectionInformation.getListElementTypes().get(variableObjectCounter); + Class listElementType = reflectionInformation.getListElementTypes().get(sszListCounter); Long listElementMaxSize = - reflectionInformation.getListElementMaxSizes().get(variableObjectCounter); + reflectionInformation.getListElementMaxSizes().get(sszListCounter); SSZMutableList newSSZList = SSZList.createMutable(listElementType, listElementMaxSize); if (!isVariable(listElementType)) { // If SSZList element is fixed size @@ -361,15 +374,13 @@ private static void deserializeVariableParts( deserializeVariableElementList( reader, bytesPointer, listElementType, currentObjectEndByte, newSSZList); } + sszListCounter++; fieldObject = newSSZList; } else if (fieldClass == Bitlist.class) { fieldObject = deserializeBitlist( - reflectionInformation, - reader, - bytesPointer, - variableObjectCounter, - currentObjectEndByte); + reflectionInformation, reader, bytesPointer, bitlistCounter, currentObjectEndByte); + bitlistCounter++; } else if (isContainer(fieldClass)) { fieldObject = deserializeContainer(fieldClass, reader, bytesPointer, currentObjectEndByte); @@ -462,7 +473,7 @@ private static SSZVector deserializeFixedElementVector( return SSZVector.createMutable(newList, classInfo); } - private static ReflectionInformation getRequiredReflectionInfo(Class classInfo) { + static ReflectionInformation getRequiredReflectionInfo(Class classInfo) { final ReflectionInformation reflectionInfo = classReflectionInfo.get(classInfo); checkArgument( reflectionInfo != null, @@ -470,7 +481,7 @@ private static ReflectionInformation getRequiredReflectionInfo(Class classInfo) return reflectionInfo; } - private static Optional getOptionalReflectionInfo(Class classInfo) { + static Optional getOptionalReflectionInfo(Class classInfo) { return Optional.ofNullable(classReflectionInfo.get(classInfo)); } @@ -478,24 +489,24 @@ private static Object deserializePrimitive( Class classInfo, SSZReader reader, MutableInt bytePointer) { switch (classInfo.getSimpleName()) { case "UnsignedLong": - bytePointer.add(8); + bytePointer.add(UNSIGNED_LONG_SIZE); return UnsignedLong.fromLongBits(reader.readUInt64()); case "ArrayWrappingBytes32": case "Bytes32": - bytePointer.add(32); - return Bytes32.wrap(reader.readFixedBytes(32)); + bytePointer.add(Bytes32.SIZE); + return Bytes32.wrap(reader.readFixedBytes(Bytes32.SIZE)); case "Bytes4": - bytePointer.add(4); - return new Bytes4(reader.readFixedBytes(4)); + bytePointer.add(Bytes4.SIZE); + return new Bytes4(reader.readFixedBytes(Bytes4.SIZE)); case "BLSSignature": - bytePointer.add(96); - return BLSSignature.fromBytes(reader.readFixedBytes(96)); + bytePointer.add(BLSSignature.BLS_SIGNATURE_SIZE); + return BLSSignature.fromBytes(reader.readFixedBytes(BLSSignature.BLS_SIGNATURE_SIZE)); case "BLSPublicKey": - bytePointer.add(48); - return BLSPublicKey.fromBytes(reader.readFixedBytes(48)); + bytePointer.add(BLSPublicKey.BLS_PUBKEY_SIZE); + return BLSPublicKey.fromBytes(reader.readFixedBytes(BLSPublicKey.BLS_PUBKEY_SIZE)); case "Boolean": case "boolean": - bytePointer.add(1); + bytePointer.add(BOOLEAN_SIZE); return reader.readBoolean(); default: throw new IllegalArgumentException("Unable to deserialize " + classInfo.getSimpleName()); @@ -507,7 +518,7 @@ private static int readOffset(SSZReader reader, MutableInt bytesPointer) { return reader.readInt32(); } - private static boolean isVariable(Class classInfo) { + static boolean isVariable(Class classInfo) { if (classInfo == SSZList.class || classInfo == Bitlist.class) { return true; } else { @@ -517,18 +528,18 @@ private static boolean isVariable(Class classInfo) { } } - private static boolean isPrimitive(Class classInfo) { + static boolean isPrimitive(Class classInfo) { return !(SSZContainer.class.isAssignableFrom(classInfo) || classInfo == SSZVector.class || classInfo == Bitvector.class || classInfo == VoteTracker.class); } - private static boolean isVector(Class classInfo) { + static boolean isVector(Class classInfo) { return classInfo == SSZVector.class; } - private static boolean isBitvector(Class classInfo) { + static boolean isBitvector(Class classInfo) { return classInfo == Bitvector.class; } diff --git a/ethereum/datastructures/src/test/java/tech/pegasys/teku/datastructures/util/LengthBoundsCalculatorTest.java b/ethereum/datastructures/src/test/java/tech/pegasys/teku/datastructures/util/LengthBoundsCalculatorTest.java new file mode 100644 index 00000000000..4a555677bc9 --- /dev/null +++ b/ethereum/datastructures/src/test/java/tech/pegasys/teku/datastructures/util/LengthBoundsCalculatorTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2020 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.datastructures.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tech.pegasys.teku.datastructures.blocks.BeaconBlock; +import tech.pegasys.teku.datastructures.blocks.BeaconBlockBody; +import tech.pegasys.teku.datastructures.blocks.BeaconBlockHeader; +import tech.pegasys.teku.datastructures.blocks.Eth1Data; +import tech.pegasys.teku.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.datastructures.blocks.SignedBeaconBlockHeader; +import tech.pegasys.teku.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage; +import tech.pegasys.teku.datastructures.networking.libp2p.rpc.GoodbyeMessage; +import tech.pegasys.teku.datastructures.networking.libp2p.rpc.MetadataMessage; +import tech.pegasys.teku.datastructures.networking.libp2p.rpc.StatusMessage; +import tech.pegasys.teku.datastructures.operations.AggregateAndProof; +import tech.pegasys.teku.datastructures.operations.Attestation; +import tech.pegasys.teku.datastructures.operations.AttestationData; +import tech.pegasys.teku.datastructures.operations.AttesterSlashing; +import tech.pegasys.teku.datastructures.operations.Deposit; +import tech.pegasys.teku.datastructures.operations.DepositData; +import tech.pegasys.teku.datastructures.operations.DepositMessage; +import tech.pegasys.teku.datastructures.operations.IndexedAttestation; +import tech.pegasys.teku.datastructures.operations.ProposerSlashing; +import tech.pegasys.teku.datastructures.operations.SignedAggregateAndProof; +import tech.pegasys.teku.datastructures.operations.SignedVoluntaryExit; +import tech.pegasys.teku.datastructures.operations.VoluntaryExit; +import tech.pegasys.teku.datastructures.state.BeaconStateImpl; +import tech.pegasys.teku.datastructures.state.Checkpoint; +import tech.pegasys.teku.datastructures.state.Fork; +import tech.pegasys.teku.datastructures.state.ForkData; +import tech.pegasys.teku.datastructures.state.HistoricalBatch; +import tech.pegasys.teku.datastructures.state.PendingAttestation; +import tech.pegasys.teku.datastructures.state.Validator; +import tech.pegasys.teku.util.config.Constants; + +/** + * This is in the wrong module but we want to be able to access the datastructure types to check + * they produce the right value which is a much more valuable test than using fake classes + */ +public class LengthBoundsCalculatorTest { + + @BeforeAll + static void setConstants() { + Constants.setConstants("mainnet"); + SimpleOffsetSerializer.setConstants(); + } + + @AfterAll + static void restoreConstants() { + Constants.setConstants("minimal"); + SimpleOffsetSerializer.setConstants(); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("generateParameters") + void shouldCalculateCorrectLengthBounds(final Class type, final LengthBounds expected) { + final LengthBounds actual = SimpleOffsetSerializer.getLengthBounds(type); + assertThat(actual).isEqualTo(expected); + } + + // Expected values taken from https://gist.github.com/protolambda/db75c7faa1e94f2464787a480e5d613e + static Stream generateParameters() { + return Stream.of( + Arguments.of(AggregateAndProof.class, new LengthBounds(337, 593)), + Arguments.of(Attestation.class, new LengthBounds(229, 485)), + Arguments.of(AttestationData.class, new LengthBounds(128, 128)), + Arguments.of(AttesterSlashing.class, new LengthBounds(464, 33232)), + Arguments.of(BeaconBlock.class, new LengthBounds(304, 157656)), + Arguments.of(BeaconBlockBody.class, new LengthBounds(220, 157572)), + Arguments.of(BeaconBlockHeader.class, new LengthBounds(112, 112)), + Arguments.of(BeaconStateImpl.class, new LengthBounds(2687377, 141837542965649L)), + Arguments.of(Checkpoint.class, new LengthBounds(40, 40)), + Arguments.of(Deposit.class, new LengthBounds(1240, 1240)), + Arguments.of(DepositData.class, new LengthBounds(184, 184)), + Arguments.of(DepositMessage.class, new LengthBounds(88, 88)), + Arguments.of(Eth1Data.class, new LengthBounds(72, 72)), + Arguments.of(Fork.class, new LengthBounds(16, 16)), + Arguments.of(ForkData.class, new LengthBounds(36, 36)), + Arguments.of(HistoricalBatch.class, new LengthBounds(524288, 524288)), + Arguments.of(IndexedAttestation.class, new LengthBounds(228, 16612)), + Arguments.of(PendingAttestation.class, new LengthBounds(149, 405)), + Arguments.of(ProposerSlashing.class, new LengthBounds(416, 416)), + Arguments.of(SignedAggregateAndProof.class, new LengthBounds(437, 693)), + Arguments.of(SignedBeaconBlock.class, new LengthBounds(404, 157756)), + Arguments.of(SignedBeaconBlockHeader.class, new LengthBounds(208, 208)), + Arguments.of(SignedVoluntaryExit.class, new LengthBounds(112, 112)), + Arguments.of(Validator.class, new LengthBounds(121, 121)), + Arguments.of(VoluntaryExit.class, new LengthBounds(16, 16)), + Arguments.of(MetadataMessage.class, new LengthBounds(16, 16)), + Arguments.of(StatusMessage.class, new LengthBounds(84, 84)), + Arguments.of(GoodbyeMessage.class, new LengthBounds(8, 8)), + Arguments.of(BeaconBlocksByRangeRequestMessage.class, new LengthBounds(24, 24))); + } +} diff --git a/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bitlist.java b/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bitlist.java index 0f7c359826f..212a21deb45 100644 --- a/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bitlist.java +++ b/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bitlist.java @@ -110,12 +110,16 @@ public int getCurrentSize() { @SuppressWarnings("NarrowingCompoundAssignment") public Bytes serialize() { int len = size; - byte[] array = new byte[(len / 8) + 1]; + byte[] array = new byte[sszSerializationLength(len)]; IntStream.range(0, len).forEach(i -> array[i / 8] |= ((data.get(i) ? 1 : 0) << (i % 8))); array[len / 8] |= 1 << (len % 8); return Bytes.wrap(array); } + public static int sszSerializationLength(final int size) { + return (size / 8) + 1; + } + public static Bitlist fromBytes(Bytes bytes, long maxSize) { int numBytes = bytes.size(); checkArgument(numBytes > 0, "Bitlist must contain at least one byte"); diff --git a/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bitvector.java b/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bitvector.java index ac31f26ae2b..c7022c2f591 100644 --- a/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bitvector.java +++ b/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bitvector.java @@ -111,7 +111,7 @@ public static Bitvector fromBytes(Bytes bytes, int size) { return new Bitvector(bitset, size); } - private static int sszSerializationLength(final int size) { + public static int sszSerializationLength(final int size) { return (size + 7) / 8; } diff --git a/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bytes4.java b/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bytes4.java index 5fca30e60b1..cfa1b51b711 100644 --- a/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bytes4.java +++ b/ssz/src/main/java/tech/pegasys/teku/ssz/SSZTypes/Bytes4.java @@ -21,6 +21,7 @@ import org.apache.tuweni.bytes.MutableBytes; public class Bytes4 { + public static final int SIZE = 4; private Bytes bytes;