mirror of https://github.com/hyperledger/besu
[NC-1582] Added ExtraData for iBFT 2.0 (#37)
parent
ad82ca265a
commit
fdd08a8820
@ -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