mirror of https://github.com/hyperledger/besu
[NC-1582] Added ExtraData for iBFT 2.0 (#37)
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
bc05f38a03
commit
4644fe0080
@ -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<Signature> seals; |
||||
private final Optional<Vote> vote; |
||||
private final int round; |
||||
private final List<Address> validators; |
||||
|
||||
public Ibft2ExtraData( |
||||
final BytesValue vanityData, |
||||
final List<Signature> seals, |
||||
final Optional<Vote> vote, |
||||
final int round, |
||||
final List<Address> 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<Address> validators = rlpInput.readList(Address::readFrom); |
||||
final Optional<Vote> vote; |
||||
if (rlpInput.nextIsNull()) { |
||||
vote = Optional.empty(); |
||||
rlpInput.skipNext(); |
||||
} else { |
||||
vote = Optional.of(Vote.readFrom(rlpInput)); |
||||
} |
||||
final int round = rlpInput.readInt(); |
||||
final List<Signature> 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<Signature> getSeals() { |
||||
return seals; |
||||
} |
||||
|
||||
public List<Address> getValidators() { |
||||
return validators; |
||||
} |
||||
|
||||
public Optional<Vote> getVote() { |
||||
return vote; |
||||
} |
||||
|
||||
public int getRound() { |
||||
return round; |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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<Address> validators = |
||||
Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); |
||||
final Optional<Vote> vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); |
||||
final int round = 0x00FEDCBA; |
||||
final List<Signature> 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<Address> validators = Lists.newArrayList(); |
||||
final Optional<Vote> 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<Signature> 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<Address> validators = Lists.newArrayList(); |
||||
final Optional<Vote> vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); |
||||
final byte[] roundAsByteArray = new byte[] {(byte) 0xFE, (byte) 0xDC, (byte) 0xBA}; |
||||
final List<Signature> 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<Address> validators = Lists.newArrayList(); |
||||
final int round = 0x00FEDCBA; |
||||
final List<Signature> 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<Address> validators = Lists.newArrayList(); |
||||
Optional<Vote> vote = Optional.empty(); |
||||
final int round = 0x00FEDCBA; |
||||
final List<Signature> 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<Address> validators = Lists.newArrayList(); |
||||
final Optional<Vote> vote = Optional.of(Vote.dropVote(Address.fromHexString("1"))); |
||||
final int round = 0x00FEDCBA; |
||||
final List<Signature> 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<Address> validators = Lists.newArrayList(); |
||||
final Optional<Vote> vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); |
||||
final int round = 0x00FEDCBA; |
||||
final List<Signature> 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<Address> validators = |
||||
Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); |
||||
final Optional<Vote> vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); |
||||
final int round = 0x00FEDCBA; |
||||
final List<Signature> 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<Address> validators = |
||||
Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); |
||||
final Optional<Vote> vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); |
||||
final int round = 0x00FEDCBA; |
||||
final List<Signature> 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<Address> validators = Lists.newArrayList(); |
||||
final Optional<Vote> vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); |
||||
final int round = 0x00FEDCBA; |
||||
final List<Signature> 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<Address> 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<Signature> 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; |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
Loading…
Reference in new issue