diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/Ibft2ExtraData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/Ibft2ExtraData.java new file mode 100644 index 0000000000..e80891b75e --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/Ibft2ExtraData.java @@ -0,0 +1,121 @@ +/* + * Copyright 2018 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.pantheon.consensus.ibft; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import tech.pegasys.pantheon.crypto.SECP256K1.Signature; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.util.List; +import java.util.Optional; + +/** + * Represents the data structure stored in the extraData field of the BlockHeader used when + * operating under an IBFT 2.0 consensus mechanism. + */ +public class Ibft2ExtraData { + + public static final int EXTRA_VANITY_LENGTH = 32; + + private final BytesValue vanityData; + private final List seals; + private final Optional vote; + private final int round; + private final List
validators; + + public Ibft2ExtraData( + final BytesValue vanityData, + final List seals, + final Optional vote, + final int round, + final List
validators) { + + checkNotNull(vanityData); + checkNotNull(seals); + checkNotNull(validators); + + this.vanityData = vanityData; + this.seals = seals; + this.round = round; + this.validators = validators; + this.vote = vote; + } + + public static Ibft2ExtraData decode(final BytesValue input) { + checkArgument( + input.size() > EXTRA_VANITY_LENGTH, + "Invalid BytesValue supplied - too short to produce a valid IBFT Extra Data object."); + + final RLPInput rlpInput = new BytesValueRLPInput(input, false); + + rlpInput.enterList(); // This accounts for the "root node" which contains IBFT data items. + final BytesValue vanityData = rlpInput.readBytesValue(); + final List
validators = rlpInput.readList(Address::readFrom); + final Optional vote; + if (rlpInput.nextIsNull()) { + vote = Optional.empty(); + rlpInput.skipNext(); + } else { + vote = Optional.of(Vote.readFrom(rlpInput)); + } + final int round = rlpInput.readInt(); + final List seals = rlpInput.readList(rlp -> Signature.decode(rlp.readBytesValue())); + rlpInput.leaveList(); + + return new Ibft2ExtraData(vanityData, seals, vote, round, validators); + } + + public BytesValue encode() { + final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); + encoder.startList(); + encoder.writeBytesValue(vanityData); + encoder.writeList(validators, (validator, rlp) -> rlp.writeBytesValue(validator)); + if (vote.isPresent()) { + vote.get().writeTo(encoder); + } else { + encoder.writeNull(); + } + encoder.writeInt(round); + encoder.writeList(seals, (committer, rlp) -> rlp.writeBytesValue(committer.encodedBytes())); + encoder.endList(); + + return encoder.encoded(); + } + + // Accessors + public BytesValue getVanityData() { + return vanityData; + } + + public List getSeals() { + return seals; + } + + public List
getValidators() { + return validators; + } + + public Optional getVote() { + return vote; + } + + public int getRound() { + return round; + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/Ibft2VoteType.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/Ibft2VoteType.java new file mode 100644 index 0000000000..d505679abe --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/Ibft2VoteType.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018 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.pantheon.consensus.ibft; + +import tech.pegasys.pantheon.ethereum.rlp.RLPException; +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; + +public enum Ibft2VoteType { + ADD((byte) 0xFF), + DROP((byte) 0x00); + + private final byte voteValue; + + Ibft2VoteType(final byte voteValue) { + this.voteValue = voteValue; + } + + public byte getVoteValue() { + return voteValue; + } + + public static Ibft2VoteType readFrom(final RLPInput rlpInput) { + byte encodedByteValue = rlpInput.readByte(); + for (final Ibft2VoteType voteType : values()) { + if (voteType.voteValue == encodedByteValue) { + return voteType; + } + } + + throw new RLPException("Invalid Ibft2VoteType RLP encoding"); + } + + public void writeTo(final RLPOutput rlpOutput) { + rlpOutput.writeByte(voteValue); + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/Vote.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/Vote.java new file mode 100644 index 0000000000..0faf37aa01 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/Vote.java @@ -0,0 +1,87 @@ +/* + * Copyright 2018 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.pantheon.consensus.ibft; + +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; + +import com.google.common.base.Objects; + +public class Vote { + private final Address recipient; + private final Ibft2VoteType voteType; + + private Vote(final Address recipient, final Ibft2VoteType voteType) { + this.recipient = recipient; + this.voteType = voteType; + } + + public static Vote authVote(final Address address) { + return new Vote(address, Ibft2VoteType.ADD); + } + + public static Vote dropVote(final Address address) { + return new Vote(address, Ibft2VoteType.DROP); + } + + public Address getRecipient() { + return recipient; + } + + public boolean isAuth() { + return voteType.equals(Ibft2VoteType.ADD); + } + + public boolean isDrop() { + return voteType.equals(Ibft2VoteType.DROP); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final Vote vote1 = (Vote) o; + return recipient.equals(vote1.recipient) && voteType.equals(vote1.voteType); + } + + @Override + public int hashCode() { + return Objects.hashCode(recipient, voteType); + } + + public Ibft2VoteType getVoteType() { + return voteType; + } + + public void writeTo(final RLPOutput rlpOutput) { + rlpOutput.startList(); + rlpOutput.writeBytesValue(recipient); + voteType.writeTo(rlpOutput); + rlpOutput.endList(); + } + + public static Vote readFrom(final RLPInput rlpInput) { + + rlpInput.enterList(); + final Address recipient = Address.readFrom(rlpInput); + final Ibft2VoteType vote = Ibft2VoteType.readFrom(rlpInput); + rlpInput.leaveList(); + + return new Vote(recipient, vote); + } +} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/Ibft2ExtraDataTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/Ibft2ExtraDataTest.java new file mode 100644 index 0000000000..4aa801039c --- /dev/null +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/Ibft2ExtraDataTest.java @@ -0,0 +1,415 @@ +/* + * Copyright 2018 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.pantheon.consensus.ibft; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import tech.pegasys.pantheon.crypto.SECP256K1.Signature; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; +import tech.pegasys.pantheon.ethereum.rlp.RLPException; +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Random; + +import com.google.common.collect.Lists; +import org.junit.Test; + +public class Ibft2ExtraDataTest { + private final String RAW_HEX_ENCODING_STRING = + "f8f1a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea9400000000000000000000000000000000000" + + "00001940000000000000000000000000000000000000002d794000000000000000000000000000000000000000181ff8400fedc" + + "baf886b841000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000" + + "0000000000000000000000000000000000a00b84100000000000000000000000000000000000000000000000000000000000000" + + "0a000000000000000000000000000000000000000000000000000000000000000100"; + + private final Ibft2ExtraData DECODED_EXTRA_DATA_FOR_RAW_HEX_ENCODING_STRING = + getDecodedExtraDataForRawHexEncodingString(); + + private static Ibft2ExtraData getDecodedExtraDataForRawHexEncodingString() { + final List
validators = + Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); + final Optional vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); + final int round = 0x00FEDCBA; + final List committerSeals = + Arrays.asList( + Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 0), + Signature.create(BigInteger.TEN, BigInteger.ONE, (byte) 0)); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = createNonEmptyVanityData(); + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + return new Ibft2ExtraData(vanity_data, committerSeals, vote, round, validators); + } + + @Test + public void correctlyCodedRoundConstitutesValidContent() { + final List
validators = Lists.newArrayList(); + final Optional vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); + final int round = 0x00FEDCBA; + final byte[] roundAsByteArray = new byte[] {(byte) 0x00, (byte) 0xFE, (byte) 0xDC, (byte) 0xBA}; + final List committerSeals = Lists.newArrayList(); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = new byte[32]; + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); + encoder.startList(); + encoder.writeBytesValue(vanity_data); + encoder.writeList(validators, (validator, rlp) -> rlp.writeBytesValue(validator)); + + // encoded vote + encoder.startList(); + encoder.writeBytesValue(vote.get().getRecipient()); + vote.get().getVoteType().writeTo(encoder); + encoder.endList(); + + // This is to verify that the decoding works correctly when the round is encoded as 4 bytes + encoder.writeBytesValue(BytesValue.wrap(roundAsByteArray)); + encoder.writeList( + committerSeals, (committer, rlp) -> rlp.writeBytesValue(committer.encodedBytes())); + encoder.endList(); + + final BytesValue bufferToInject = encoder.encoded(); + + final Ibft2ExtraData extraData = Ibft2ExtraData.decode(bufferToInject); + + assertThat(extraData.getVanityData()).isEqualTo(vanity_data); + assertThat(extraData.getVote()).isEqualTo(vote); + assertThat(extraData.getRound()).isEqualTo(round); + assertThat(extraData.getSeals()).isEqualTo(committerSeals); + assertThat(extraData.getValidators()).isEqualTo(validators); + } + + /** + * This test specifically verifies that {@link Ibft2ExtraData#decode(BytesValue)} uses {@link + * RLPInput#readInt()} rather than {@link RLPInput#readIntScalar()} to decode the round number + */ + @Test + public void incorrectlyEncodedRoundThrowsRlpException() { + final List
validators = Lists.newArrayList(); + final Optional vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); + final byte[] roundAsByteArray = new byte[] {(byte) 0xFE, (byte) 0xDC, (byte) 0xBA}; + final List committerSeals = Lists.newArrayList(); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = new byte[32]; + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); + encoder.startList(); + encoder.writeBytesValue(vanity_data); + encoder.writeList(validators, (validator, rlp) -> rlp.writeBytesValue(validator)); + + // encoded vote + encoder.startList(); + encoder.writeBytesValue(vote.get().getRecipient()); + vote.get().getVoteType().writeTo(encoder); + encoder.endList(); + + // This is to verify that the decoding throws an exception when the round number is not encoded + // in 4 byte format + encoder.writeBytesValue(BytesValue.wrap(roundAsByteArray)); + encoder.writeList( + committerSeals, (committer, rlp) -> rlp.writeBytesValue(committer.encodedBytes())); + encoder.endList(); + + final BytesValue bufferToInject = encoder.encoded(); + + assertThatThrownBy(() -> Ibft2ExtraData.decode(bufferToInject)) + .isInstanceOf(RLPException.class); + } + + @Test + public void nullVoteAndListConstituteValidContent() { + final List
validators = Lists.newArrayList(); + final int round = 0x00FEDCBA; + final List committerSeals = Lists.newArrayList(); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = new byte[32]; + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); + encoder.startList(); + encoder.writeBytesValue(vanity_data); + encoder.writeList(validators, (validator, rlp) -> rlp.writeBytesValue(validator)); + + // encode an empty vote + encoder.writeNull(); + + encoder.writeInt(round); + encoder.writeList( + committerSeals, (committer, rlp) -> rlp.writeBytesValue(committer.encodedBytes())); + encoder.endList(); + + final BytesValue bufferToInject = encoder.encoded(); + + final Ibft2ExtraData extraData = Ibft2ExtraData.decode(bufferToInject); + + assertThat(extraData.getVanityData()).isEqualTo(vanity_data); + assertThat(extraData.getVote().isPresent()).isEqualTo(false); + assertThat(extraData.getRound()).isEqualTo(round); + assertThat(extraData.getSeals()).isEqualTo(committerSeals); + assertThat(extraData.getValidators()).isEqualTo(validators); + } + + @Test + public void emptyVoteAndListIsEncodedCorrectly() { + final List
validators = Lists.newArrayList(); + Optional vote = Optional.empty(); + final int round = 0x00FEDCBA; + final List committerSeals = Lists.newArrayList(); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = new byte[32]; + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + Ibft2ExtraData expectedExtraData = + new Ibft2ExtraData(vanity_data, committerSeals, vote, round, validators); + + Ibft2ExtraData actualExtraData = Ibft2ExtraData.decode(expectedExtraData.encode()); + + assertThat(actualExtraData).isEqualToComparingFieldByField(expectedExtraData); + } + + @Test + public void emptyListConstituteValidContent() { + final List
validators = Lists.newArrayList(); + final Optional vote = Optional.of(Vote.dropVote(Address.fromHexString("1"))); + final int round = 0x00FEDCBA; + final List committerSeals = Lists.newArrayList(); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = new byte[32]; + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); + encoder.startList(); + encoder.writeBytesValue(vanity_data); + encoder.writeList(validators, (validator, rlp) -> rlp.writeBytesValue(validator)); + + // encoded vote + encoder.startList(); + encoder.writeBytesValue(vote.get().getRecipient()); + vote.get().getVoteType().writeTo(encoder); + encoder.endList(); + + encoder.writeInt(round); + encoder.writeList( + committerSeals, (committer, rlp) -> rlp.writeBytesValue(committer.encodedBytes())); + encoder.endList(); + + final BytesValue bufferToInject = encoder.encoded(); + + final Ibft2ExtraData extraData = Ibft2ExtraData.decode(bufferToInject); + + assertThat(extraData.getVanityData()).isEqualTo(vanity_data); + assertThat(extraData.getVote()).isEqualTo(vote); + assertThat(extraData.getRound()).isEqualTo(round); + assertThat(extraData.getSeals()).isEqualTo(committerSeals); + assertThat(extraData.getValidators()).isEqualTo(validators); + } + + @Test + public void emptyListsAreEncodedAndDecodedCorrectly() { + final List
validators = Lists.newArrayList(); + final Optional vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); + final int round = 0x00FEDCBA; + final List committerSeals = Lists.newArrayList(); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = new byte[32]; + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + Ibft2ExtraData expectedExtraData = + new Ibft2ExtraData(vanity_data, committerSeals, vote, round, validators); + + Ibft2ExtraData actualExtraData = Ibft2ExtraData.decode(expectedExtraData.encode()); + + assertThat(actualExtraData).isEqualToComparingFieldByField(expectedExtraData); + } + + @Test + public void fullyPopulatedDataProducesCorrectlyFormedExtraDataObject() { + final List
validators = + Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); + final Optional vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); + final int round = 0x00FEDCBA; + final List committerSeals = + Arrays.asList( + Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 0), + Signature.create(BigInteger.TEN, BigInteger.ONE, (byte) 0)); + + // Create randomised vanity data. + final byte[] vanity_bytes = createNonEmptyVanityData(); + new Random().nextBytes(vanity_bytes); + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); + encoder.startList(); // This is required to create a "root node" for all RLP'd data + encoder.writeBytesValue(vanity_data); + encoder.writeList(validators, (validator, rlp) -> rlp.writeBytesValue(validator)); + + // encoded vote + encoder.startList(); + encoder.writeBytesValue(vote.get().getRecipient()); + vote.get().getVoteType().writeTo(encoder); + encoder.endList(); + + encoder.writeInt(round); + encoder.writeList( + committerSeals, (committer, rlp) -> rlp.writeBytesValue(committer.encodedBytes())); + encoder.endList(); + + final BytesValue bufferToInject = encoder.encoded(); + + final Ibft2ExtraData extraData = Ibft2ExtraData.decode(bufferToInject); + + assertThat(extraData.getVanityData()).isEqualTo(vanity_data); + assertThat(extraData.getVote()).isEqualTo(vote); + assertThat(extraData.getRound()).isEqualTo(round); + assertThat(extraData.getSeals()).isEqualTo(committerSeals); + assertThat(extraData.getValidators()).isEqualTo(validators); + } + + @Test + public void fullyPopulatedDataIsEncodedAndDecodedCorrectly() { + final List
validators = + Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); + final Optional vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); + final int round = 0x00FEDCBA; + final List committerSeals = + Arrays.asList( + Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 0), + Signature.create(BigInteger.TEN, BigInteger.ONE, (byte) 0)); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = createNonEmptyVanityData(); + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + Ibft2ExtraData expectedExtraData = + new Ibft2ExtraData(vanity_data, committerSeals, vote, round, validators); + + Ibft2ExtraData actualExtraData = Ibft2ExtraData.decode(expectedExtraData.encode()); + + assertThat(actualExtraData).isEqualToComparingFieldByField(expectedExtraData); + } + + @Test + public void encodingMatchesKnownRawHexString() { + BytesValue expectedRawDecoding = BytesValue.fromHexString(RAW_HEX_ENCODING_STRING); + assertThat(DECODED_EXTRA_DATA_FOR_RAW_HEX_ENCODING_STRING.encode()) + .isEqualTo(expectedRawDecoding); + } + + @Test + public void decodingOfKnownRawHexStringMatchesKnowExtraDataObject() { + + final Ibft2ExtraData expectedExtraData = DECODED_EXTRA_DATA_FOR_RAW_HEX_ENCODING_STRING; + + BytesValue rawDecoding = BytesValue.fromHexString(RAW_HEX_ENCODING_STRING); + Ibft2ExtraData actualExtraData = Ibft2ExtraData.decode(rawDecoding); + + assertThat(actualExtraData).isEqualToComparingFieldByField(expectedExtraData); + } + + @Test + public void incorrectlyStructuredRlpThrowsException() { + final List
validators = Lists.newArrayList(); + final Optional vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); + final int round = 0x00FEDCBA; + final List committerSeals = Lists.newArrayList(); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = new byte[32]; + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); + encoder.startList(); + encoder.writeBytesValue(vanity_data); + encoder.writeList(validators, (validator, rlp) -> rlp.writeBytesValue(validator)); + + // encoded vote + encoder.startList(); + encoder.writeBytesValue(vote.get().getRecipient()); + vote.get().getVoteType().writeTo(encoder); + encoder.endList(); + + encoder.writeInt(round); + encoder.writeList( + committerSeals, (committer, rlp) -> rlp.writeBytesValue(committer.encodedBytes())); + encoder.writeLong(1); + encoder.endList(); + + final BytesValue bufferToInject = encoder.encoded(); + + assertThatThrownBy(() -> Ibft2ExtraData.decode(bufferToInject)) + .isInstanceOf(RLPException.class); + } + + @Test + public void incorrectVoteTypeThrowsException() { + final List
validators = + Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); + final Address voteRecipient = Address.fromHexString("1"); + final byte voteType = (byte) 0xAA; + final int round = 0x00FEDCBA; + final List committerSeals = + Arrays.asList( + Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 0), + Signature.create(BigInteger.TEN, BigInteger.ONE, (byte) 0)); + + // Create a byte buffer with no data. + final byte[] vanity_bytes = new byte[32]; + final BytesValue vanity_data = BytesValue.wrap(vanity_bytes); + + final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); + encoder.startList(); + encoder.writeBytesValue(vanity_data); + encoder.writeList(validators, (validator, rlp) -> rlp.writeBytesValue(validator)); + + // encode vote + encoder.startList(); + encoder.writeBytesValue(voteRecipient); + encoder.writeByte(voteType); + encoder.endList(); + + encoder.writeInt(round); + encoder.writeList( + committerSeals, (committer, rlp) -> rlp.writeBytesValue(committer.encodedBytes())); + encoder.endList(); + + final BytesValue bufferToInject = encoder.encoded(); + + assertThatThrownBy(() -> Ibft2ExtraData.decode(bufferToInject)) + .isInstanceOf(RLPException.class); + } + + private static byte[] createNonEmptyVanityData() { + final byte[] vanity_bytes = new byte[32]; + for (int i = 0; i < vanity_bytes.length; i++) { + vanity_bytes[i] = (byte) (i + 1); + } + + return vanity_bytes; + } +} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/VoteTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/VoteTest.java new file mode 100644 index 0000000000..81f297e86d --- /dev/null +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/VoteTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2018 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.pantheon.consensus.ibft; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +import tech.pegasys.pantheon.ethereum.core.Address; + +import org.junit.Test; + +public class VoteTest { + @Test + public void testStaticVoteCreationMethods() { + assertThat(Vote.authVote(Address.fromHexString("1")).isAuth()).isEqualTo(true); + assertThat(Vote.authVote(Address.fromHexString("1")).isDrop()).isEqualTo(false); + + assertThat(Vote.dropVote(Address.fromHexString("1")).isAuth()).isEqualTo(false); + assertThat(Vote.dropVote(Address.fromHexString("1")).isDrop()).isEqualTo(true); + } +}