[NC-1582] Added iBFT 2.0 Hashing functions and header validation rules (#119)

Roberto Saltini 6 years ago committed by GitHub
parent 8e9a1ddc72
commit 77a3df36ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/ValidatorVote.java
  2. 7
      consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteTally.java
  3. 12
      consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteType.java
  4. 30
      consensus/common/src/test/java/net/consensys/pantheon/consensus/common/VoteTypeTest.java
  5. 95
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockHashing.java
  6. 76
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockHeaderValidationRulesetFactory.java
  7. 32
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java
  8. 26
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftHelpers.java
  9. 13
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftVoteType.java
  10. 53
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/headervalidationrules/IbftCoinbaseValidationRule.java
  11. 124
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/headervalidationrules/IbftExtraDataValidationRule.java
  12. 180
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockHashingTest.java
  13. 318
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockHeaderValidationRulesetFactoryTest.java
  14. 100
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraDataFixture.java
  15. 125
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraDataTest.java
  16. 29
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftVoteTypeTest.java
  17. 104
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/headervalidationrules/IbftCoinbaseValidationRuleTest.java
  18. 253
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/headervalidationrules/IbftExtraDataValidationRuleTest.java
  19. 8
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Util.java
  20. 33
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/UtilTest.java

@ -0,0 +1,19 @@
/*
* 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.common;
public interface ValidatorVote {
boolean isAddVote();
boolean isDropVote();
}

@ -54,15 +54,16 @@ public class VoteTally implements ValidatorProvider {
*
* @param proposer the address of the validator casting the vote via block proposal
* @param subject the validator the vote is about
* @param voteType the type of vote, either add or drop
* @param validatorVote the type of vote, either add or drop
*/
public void addVote(final Address proposer, final Address subject, final VoteType voteType) {
public void addVote(
final Address proposer, final Address subject, final ValidatorVote validatorVote) {
final Set<Address> addVotesForSubject =
addVotesBySubject.computeIfAbsent(subject, target -> new HashSet<>());
final Set<Address> removeVotesForSubject =
removeVotesBySubject.computeIfAbsent(subject, target -> new HashSet<>());
if (voteType == VoteType.ADD) {
if (validatorVote.isAddVote()) {
addVotesForSubject.add(proposer);
removeVotesForSubject.remove(proposer);
} else {

@ -14,7 +14,7 @@ package tech.pegasys.pantheon.consensus.common;
import java.util.Optional;
public enum VoteType {
public enum VoteType implements ValidatorVote {
ADD(0xFFFFFFFFFFFFFFFFL),
DROP(0x0L);
@ -36,4 +36,14 @@ public enum VoteType {
}
return Optional.empty();
}
@Override
public boolean isAddVote() {
return this.equals(ADD);
}
@Override
public boolean isDropVote() {
return this.equals(DROP);
}
}

@ -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 net.consensys.pantheon.consensus.common;
import static org.assertj.core.api.Java6Assertions.assertThat;
import tech.pegasys.pantheon.consensus.common.VoteType;
import org.junit.Test;
public class VoteTypeTest {
@Test
public void testValidatorVoteMethodImplementation() {
assertThat(VoteType.ADD.isAddVote()).isTrue();
assertThat(VoteType.ADD.isDropVote()).isFalse();
assertThat(VoteType.DROP.isAddVote()).isFalse();
assertThat(VoteType.DROP.isDropVote()).isTrue();
}
}

@ -0,0 +1,95 @@
/*
* 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.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.BlockHeaderBuilder;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class IbftBlockHashing {
/**
* Constructs a hash of the block header suitable for signing as a committed seal. The extra data
* in the hash uses an empty list for the committed seals.
*
* @param header The header for which a proposer seal is to be calculated (with or without extra
* data)
* @param ibftExtraData The extra data block which is to be inserted to the header once seal is
* calculated
* @return the hash of the header including the validator and proposer seal in the extra data
*/
public static Hash calculateDataHashForCommittedSeal(
final BlockHeader header, final IbftExtraData ibftExtraData) {
return Hash.hash(serializeHeader(header, ibftExtraData::encodeWithoutCommitSeals));
}
/**
* Constructs a hash of the block header, but omits the committerSeals and sets round number to 0
* (as these change on each of the potentially circulated blocks at the current chain height).
*
* @param header The header for which a block hash is to be calculated
* @return the hash of the header to be used when referencing the header on the blockchain
*/
public static Hash calculateHashOfIbftBlockOnChain(final BlockHeader header) {
final IbftExtraData ibftExtraData = IbftExtraData.decode(header.getExtraData());
return Hash.hash(
serializeHeader(header, ibftExtraData::encodeWithoutCommitSealsAndRoundNumber));
}
/**
* Recovers the {@link Address} for each validator that contributed a committed seal to the block.
*
* @param header the block header that was signed by the committed seals
* @param ibftExtraData the parsed {@link IbftExtraData} from the header
* @return the addresses of validators that provided a committed seal
*/
public static List<Address> recoverCommitterAddresses(
final BlockHeader header, final IbftExtraData ibftExtraData) {
final Hash committerHash =
IbftBlockHashing.calculateDataHashForCommittedSeal(header, ibftExtraData);
return ibftExtraData
.getSeals()
.stream()
.map(p -> Util.signatureToAddress(p, committerHash))
.collect(Collectors.toList());
}
private static BytesValue serializeHeader(
final BlockHeader header, final Supplier<BytesValue> extraDataSerializer) {
// create a block header which is a copy of the header supplied as parameter except of the
// extraData field
BlockHeaderBuilder builder = BlockHeaderBuilder.fromHeader(header);
builder.blockHashFunction(IbftBlockHashing::calculateHashOfIbftBlockOnChain);
// set the extraData field using the supplied extraDataSerializer if the block height is not 0
if (header.getNumber() == 0) {
builder.extraData(header.getExtraData());
} else {
builder.extraData(extraDataSerializer.get());
}
final BytesValueRLPOutput out = new BytesValueRLPOutput();
builder.buildBlockHeader().writeTo(out);
return out.encoded();
}
}

@ -0,0 +1,76 @@
/*
* 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.consensus.ibft.headervalidationrules.IbftCoinbaseValidationRule;
import tech.pegasys.pantheon.consensus.ibft.headervalidationrules.IbftExtraDataValidationRule;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator;
import tech.pegasys.pantheon.ethereum.mainnet.headervalidationrules.AncestryValidationRule;
import tech.pegasys.pantheon.ethereum.mainnet.headervalidationrules.ConstantFieldValidationRule;
import tech.pegasys.pantheon.ethereum.mainnet.headervalidationrules.GasLimitRangeAndDeltaValidationRule;
import tech.pegasys.pantheon.ethereum.mainnet.headervalidationrules.GasUsageValidationRule;
import tech.pegasys.pantheon.ethereum.mainnet.headervalidationrules.TimestampBoundedByFutureParameter;
import tech.pegasys.pantheon.ethereum.mainnet.headervalidationrules.TimestampMoreRecentThanParent;
import tech.pegasys.pantheon.util.uint.UInt256;
public class IbftBlockHeaderValidationRulesetFactory {
/**
* Produces a BlockHeaderValidator configured for assessing ibft block headers which are to form
* part of the BlockChain (i.e. not proposed blocks, which do not contain commit seals)
*
* @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks.
* @return BlockHeaderValidator configured for assessing ibft block headers
*/
public static BlockHeaderValidator<IbftContext> ibftBlockHeaderValidator(
final long secondsBetweenBlocks) {
return createValidator(secondsBetweenBlocks, true);
}
/**
* Produces a BlockHeaderValidator configured for assessing IBFT proposed blocks (i.e. blocks
* which need to be vetted by the validators, and do not contain commit seals).
*
* @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks.
* @return BlockHeaderValidator configured for assessing ibft block headers
*/
public static BlockHeaderValidator<IbftContext> ibftProposedBlockValidator(
final long secondsBetweenBlocks) {
return createValidator(secondsBetweenBlocks, false);
}
private static BlockHeaderValidator<IbftContext> createValidator(
final long secondsBetweenBlocks, final boolean validateCommitSeals) {
return new BlockHeaderValidator.Builder<IbftContext>()
.addRule(new AncestryValidationRule())
.addRule(new GasUsageValidationRule())
.addRule(new GasLimitRangeAndDeltaValidationRule(5000, 0x7fffffffffffffffL))
.addRule(new TimestampBoundedByFutureParameter(1))
.addRule(new TimestampMoreRecentThanParent(secondsBetweenBlocks))
.addRule(
new ConstantFieldValidationRule<>(
"MixHash", BlockHeader::getMixHash, IbftHelpers.EXPECTED_MIX_HASH))
.addRule(
new ConstantFieldValidationRule<>(
"OmmersHash", BlockHeader::getOmmersHash, Hash.EMPTY_LIST_HASH))
.addRule(
new ConstantFieldValidationRule<>(
"Difficulty", BlockHeader::getDifficulty, UInt256.ONE))
.addRule(new ConstantFieldValidationRule<>("Nonce", BlockHeader::getNonce, 0L))
.addRule(new IbftExtraDataValidationRule(validateCommitSeals))
.addRule(new IbftCoinbaseValidationRule())
.build();
}
}

@ -29,7 +29,7 @@ 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 class IbftExtraData {
public static final int EXTRA_VANITY_LENGTH = 32;
@ -39,7 +39,7 @@ public class Ibft2ExtraData {
private final int round;
private final List<Address> validators;
public Ibft2ExtraData(
public IbftExtraData(
final BytesValue vanityData,
final List<Signature> seals,
final Optional<Vote> vote,
@ -57,7 +57,7 @@ public class Ibft2ExtraData {
this.vote = vote;
}
public static Ibft2ExtraData decode(final BytesValue input) {
public static IbftExtraData decode(final BytesValue input) {
checkArgument(
input.size() > EXTRA_VANITY_LENGTH,
"Invalid BytesValue supplied - too short to produce a valid IBFT Extra Data object.");
@ -78,10 +78,29 @@ public class Ibft2ExtraData {
final List<Signature> seals = rlpInput.readList(rlp -> Signature.decode(rlp.readBytesValue()));
rlpInput.leaveList();
return new Ibft2ExtraData(vanityData, seals, vote, round, validators);
return new IbftExtraData(vanityData, seals, vote, round, validators);
}
public BytesValue encode() {
return encode(EncodingType.ALL);
}
public BytesValue encodeWithoutCommitSeals() {
return encode(EncodingType.EXCLUDE_COMMIT_SEALS);
}
public BytesValue encodeWithoutCommitSealsAndRoundNumber() {
return encode(EncodingType.EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER);
}
private enum EncodingType {
ALL,
EXCLUDE_COMMIT_SEALS,
EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER
}
private BytesValue encode(final EncodingType encodingType) {
final BytesValueRLPOutput encoder = new BytesValueRLPOutput();
encoder.startList();
encoder.writeBytesValue(vanityData);
@ -91,8 +110,13 @@ public class Ibft2ExtraData {
} else {
encoder.writeNull();
}
if (encodingType != EncodingType.EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER) {
encoder.writeInt(round);
if (encodingType != EncodingType.EXCLUDE_COMMIT_SEALS) {
encoder.writeList(seals, (committer, rlp) -> rlp.writeBytesValue(committer.encodedBytes()));
}
}
encoder.endList();
return encoder.encoded();

@ -0,0 +1,26 @@
/*
* 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.Hash;
import tech.pegasys.pantheon.ethereum.core.Util;
public class IbftHelpers {
public static final Hash EXPECTED_MIX_HASH =
Hash.fromHexString("0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365");
public static int calculateRequiredValidatorQuorum(final int validatorCount) {
return Util.fastDivCeiling(2 * validatorCount, 3);
}
}

@ -12,11 +12,12 @@
*/
package tech.pegasys.pantheon.consensus.ibft;
import tech.pegasys.pantheon.consensus.common.ValidatorVote;
import tech.pegasys.pantheon.ethereum.rlp.RLPException;
import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput;
public enum IbftVoteType {
public enum IbftVoteType implements ValidatorVote {
ADD((byte) 0xFF),
DROP((byte) 0x00);
@ -44,4 +45,14 @@ public enum IbftVoteType {
public void writeTo(final RLPOutput rlpOutput) {
rlpOutput.writeByte(voteValue);
}
@Override
public boolean isAddVote() {
return this.equals(ADD);
}
@Override
public boolean isDropVote() {
return this.equals(DROP);
}
}

@ -0,0 +1,53 @@
/*
* 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.headervalidationrules;
import tech.pegasys.pantheon.consensus.common.ValidatorProvider;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.mainnet.AttachedBlockHeaderValidationRule;
import java.util.Collection;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Ensures that the coinbase (which corresponds to the block proposer) is included in the list of
* validators
*/
public class IbftCoinbaseValidationRule implements AttachedBlockHeaderValidationRule<IbftContext> {
private static final Logger LOGGER = LogManager.getLogger(IbftCoinbaseValidationRule.class);
@Override
public boolean validate(
final BlockHeader header,
final BlockHeader parent,
final ProtocolContext<IbftContext> context) {
final ValidatorProvider validatorProvider = context.getConsensusState().getVoteTally();
Address proposer = header.getCoinbase();
final Collection<Address> storedValidators = validatorProvider.getCurrentValidators();
if (!storedValidators.contains(proposer)) {
LOGGER.trace("Block proposer is not a member of the validators.");
return false;
}
return true;
}
}

@ -0,0 +1,124 @@
/*
* 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.headervalidationrules;
import static tech.pegasys.pantheon.consensus.ibft.IbftHelpers.calculateRequiredValidatorQuorum;
import tech.pegasys.pantheon.consensus.common.ValidatorProvider;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.mainnet.AttachedBlockHeaderValidationRule;
import tech.pegasys.pantheon.ethereum.rlp.RLPException;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.Iterables;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Ensures the byte content of the extraData field can be deserialised into an appropriate
* structure, and that the structure created contains data matching expectations from preceding
* blocks.
*/
public class IbftExtraDataValidationRule implements AttachedBlockHeaderValidationRule<IbftContext> {
private static final Logger LOGGER = LogManager.getLogger(IbftExtraDataValidationRule.class);
private final boolean validateCommitSeals;
public IbftExtraDataValidationRule(final boolean validateCommitSeals) {
this.validateCommitSeals = validateCommitSeals;
}
@Override
public boolean validate(
final BlockHeader header,
final BlockHeader parent,
final ProtocolContext<IbftContext> context) {
return validateExtraData(header, context);
}
/**
* Responsible for determining the validity of the extra data field. Ensures:
*
* <ul>
* <li>Bytes in the extra data field can be decoded as per IBFT specification
* <li>Proposer (derived from the proposerSeal) is a member of the validators
* <li>Committers (derived from committerSeals) are all members of the validators
* </ul>
*
* @param header the block header containing the extraData to be validated.
* @return True if the extraData successfully produces an IstanbulExtraData object, false
* otherwise
*/
private boolean validateExtraData(
final BlockHeader header, final ProtocolContext<IbftContext> context) {
try {
final ValidatorProvider validatorProvider = context.getConsensusState().getVoteTally();
final IbftExtraData ibftExtraData = IbftExtraData.decode(header.getExtraData());
final Collection<Address> storedValidators = validatorProvider.getCurrentValidators();
if (validateCommitSeals) {
final List<Address> committers =
IbftBlockHashing.recoverCommitterAddresses(header, ibftExtraData);
if (!validateCommitters(committers, storedValidators)) {
return false;
}
}
if (!Iterables.elementsEqual(ibftExtraData.getValidators(), storedValidators)) {
LOGGER.trace(
"Incorrect validators. Expected {} but got {}.",
storedValidators,
ibftExtraData.getValidators());
return false;
}
} catch (final RLPException ex) {
LOGGER.trace("ExtraData field was unable to be deserialised into an IBFT Struct.", ex);
return false;
} catch (final IllegalArgumentException ex) {
LOGGER.trace("Failed to verify extra data", ex);
return false;
}
return true;
}
private boolean validateCommitters(
final Collection<Address> committers, final Collection<Address> storedValidators) {
final int minimumSealsRequired = calculateRequiredValidatorQuorum(storedValidators.size());
if (committers.size() < minimumSealsRequired) {
LOGGER.trace(
"Insufficient committers to seal block. (Required {}, received {})",
minimumSealsRequired,
committers.size());
return false;
}
if (!storedValidators.containsAll(committers)) {
LOGGER.trace("Not all committers are in the locally maintained validator list.");
return false;
}
return true;
}
}

@ -0,0 +1,180 @@
/*
* 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 java.util.Collections.emptyList;
import static org.assertj.core.api.Java6Assertions.assertThat;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.crypto.SECP256K1.PrivateKey;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.BlockHeaderBuilder;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.LogsBloomFilter;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.Test;
public class IbftBlockHashingTest {
private static final List<KeyPair> COMMITTERS_KEY_PAIRS = committersKeyPairs();
private static final List<Address> VALIDATORS =
Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2"));
private static final Optional<Vote> VOTE = Optional.of(Vote.authVote(Address.fromHexString("3")));
private static final int ROUND = 0x00FEDCBA;
private static final BytesValue VANITY_DATA = vanityBytes();
private static final BlockHeader HEADER_TO_BE_HASHED = headerToBeHashed();
private static final Hash EXPECTED_HEADER_HASH = expectedHeaderHash();
@Test
public void testCalculateHashOfIbft2BlockOnChain() {
Hash actualHeaderHash = IbftBlockHashing.calculateHashOfIbftBlockOnChain(HEADER_TO_BE_HASHED);
assertThat(actualHeaderHash).isEqualTo(EXPECTED_HEADER_HASH);
}
@Test
public void testRecoverCommitterAddresses() {
List<Address> actualCommitterAddresses =
IbftBlockHashing.recoverCommitterAddresses(
HEADER_TO_BE_HASHED, IbftExtraData.decode(HEADER_TO_BE_HASHED.getExtraData()));
List<Address> expectedCommitterAddresses =
COMMITTERS_KEY_PAIRS
.stream()
.map(keyPair -> Util.publicKeyToAddress(keyPair.getPublicKey()))
.collect(Collectors.toList());
assertThat(actualCommitterAddresses).isEqualTo(expectedCommitterAddresses);
}
@Test
public void testCalculateDataHashForCommittedSeal() {
Hash dataHahsForCommittedSeal =
IbftBlockHashing.calculateDataHashForCommittedSeal(
HEADER_TO_BE_HASHED, IbftExtraData.decode(HEADER_TO_BE_HASHED.getExtraData()));
BlockHeaderBuilder builder = setHeaderFieldsExceptForExtraData();
List<Signature> commitSeals =
COMMITTERS_KEY_PAIRS
.stream()
.map(keyPair -> SECP256K1.sign(dataHahsForCommittedSeal, keyPair))
.collect(Collectors.toList());
IbftExtraData extraDataWithCommitSeals =
new IbftExtraData(VANITY_DATA, commitSeals, VOTE, ROUND, VALIDATORS);
builder.extraData(extraDataWithCommitSeals.encode());
BlockHeader actualHeader = builder.buildBlockHeader();
assertThat(actualHeader).isEqualTo(HEADER_TO_BE_HASHED);
}
private static List<KeyPair> committersKeyPairs() {
return IntStream.rangeClosed(1, 4)
.mapToObj(i -> KeyPair.create(PrivateKey.create(UInt256.of(i).getBytes())))
.collect(Collectors.toList());
}
private static BlockHeaderBuilder setHeaderFieldsExceptForExtraData() {
final BlockHeaderBuilder builder = new BlockHeaderBuilder();
builder.parentHash(
Hash.fromHexString("0xa7762d3307dbf2ae6a1ae1b09cf61c7603722b2379731b6b90409cdb8c8288a0"));
builder.ommersHash(
Hash.fromHexString("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"));
builder.coinbase(Address.fromHexString("0x0000000000000000000000000000000000000000"));
builder.stateRoot(
Hash.fromHexString("0xca07595b82f908822971b7e848398e3395e59ee52565c7ef3603df1a1fa7bc80"));
builder.transactionsRoot(
Hash.fromHexString("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"));
builder.receiptsRoot(
Hash.fromHexString("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"));
builder.logsBloom(
LogsBloomFilter.fromHexString(
"0x000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000"));
builder.difficulty(UInt256.ONE);
builder.number(1);
builder.gasLimit(4704588);
builder.gasUsed(0);
builder.timestamp(1530674616);
builder.mixHash(
Hash.fromHexString("0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365"));
builder.nonce(0);
builder.blockHashFunction(IbftBlockHashing::calculateHashOfIbftBlockOnChain);
return builder;
}
private static BytesValue vanityBytes() {
final byte[] vanity_bytes = new byte[32];
for (int i = 0; i < vanity_bytes.length; i++) {
vanity_bytes[i] = (byte) i;
}
return BytesValue.wrap(vanity_bytes);
}
private static BlockHeader headerToBeHashed() {
BlockHeaderBuilder builder = setHeaderFieldsExceptForExtraData();
builder.extraData(
new IbftExtraData(VANITY_DATA, emptyList(), VOTE, ROUND, VALIDATORS)
.encodeWithoutCommitSeals());
BytesValueRLPOutput rlpForHeaderFroCommittersSigning = new BytesValueRLPOutput();
builder.buildBlockHeader().writeTo(rlpForHeaderFroCommittersSigning);
List<Signature> commitSeals =
COMMITTERS_KEY_PAIRS
.stream()
.map(
keyPair ->
SECP256K1.sign(Hash.hash(rlpForHeaderFroCommittersSigning.encoded()), keyPair))
.collect(Collectors.toList());
IbftExtraData extraDataWithCommitSeals =
new IbftExtraData(VANITY_DATA, commitSeals, VOTE, ROUND, VALIDATORS);
builder.extraData(extraDataWithCommitSeals.encode());
return builder.buildBlockHeader();
}
private static Hash expectedHeaderHash() {
BlockHeaderBuilder builder = setHeaderFieldsExceptForExtraData();
builder.extraData(
new IbftExtraData(VANITY_DATA, emptyList(), VOTE, 0, VALIDATORS)
.encodeWithoutCommitSealsAndRoundNumber());
BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
builder.buildBlockHeader().writeTo(rlpOutput);
return Hash.hash(rlpOutput.encoded());
}
}

@ -0,0 +1,318 @@
/*
* 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 java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.pantheon.consensus.ibft.IbftProtocolContextFixture.protocolContext;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator;
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.List;
import java.util.Optional;
import org.junit.Test;
public class IbftBlockHeaderValidationRulesetFactoryTest {
@Test
public void ibftValidateHeaderPasses() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, parentHeader).buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isTrue();
}
@Test
public void ibftValidateHeaderFailsOnExtraData() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, emptyList(), parentHeader).buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
@Test
public void ibftValidateHeaderFailsOnCoinbaseData() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final Address nonProposerAddress = Util.publicKeyToAddress(KeyPair.generate().getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, parentHeader)
.coinbase(nonProposerAddress)
.buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
@Test
public void ibftValidateHeaderFailsOnNonce() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, parentHeader).nonce(3).buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
@Test
public void ibftValidateHeaderFailsOnTimestamp() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, parentHeader)
.timestamp(100)
.buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
@Test
public void ibftValidateHeaderFailsOnMixHash() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, parentHeader)
.mixHash(Hash.EMPTY_TRIE_HASH)
.buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
@Test
public void ibftValidateHeaderFailsOnOmmers() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, parentHeader)
.ommersHash(Hash.EMPTY_TRIE_HASH)
.buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
@Test
public void ibftValidateHeaderFailsOnDifficulty() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, parentHeader)
.difficulty(UInt256.of(5))
.buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
@Test
public void ibftValidateHeaderFailsOnAncestor() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, null).buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
@Test
public void ibftValidateHeaderFailsOnGasUsage() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, parentHeader)
.gasLimit(5_000)
.gasUsed(6_000)
.buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
@Test
public void ibftValidateHeaderFailsOnGasLimitRange() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerKeyPair.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
final BlockHeader parentHeader =
getPresetHeaderBuilder(1, proposerKeyPair, validators, null).buildHeader();
final BlockHeader blockHeader =
getPresetHeaderBuilder(2, proposerKeyPair, validators, parentHeader)
.gasLimit(4999)
.buildHeader();
final BlockHeaderValidator<IbftContext> validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5);
assertThat(
validator.validateHeader(
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FULL))
.isFalse();
}
private BlockHeaderTestFixture getPresetHeaderBuilder(
final long number,
final KeyPair proposerKeyPair,
final List<Address> validators,
final BlockHeader parent) {
final BlockHeaderTestFixture builder = new BlockHeaderTestFixture();
if (parent != null) {
builder.parentHash(parent.getHash());
}
builder.number(number);
builder.gasLimit(5000);
builder.timestamp(6000 * number);
builder.mixHash(
Hash.fromHexString("0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365"));
builder.ommersHash(Hash.EMPTY_LIST_HASH);
builder.nonce(0);
builder.difficulty(UInt256.ONE);
builder.coinbase(Util.publicKeyToAddress(proposerKeyPair.getPublicKey()));
final IbftExtraData ibftExtraData =
IbftExtraDataFixture.createExtraData(
builder.buildHeader(),
BytesValue.wrap(new byte[IbftExtraData.EXTRA_VANITY_LENGTH]),
Optional.of(Vote.authVote(Address.fromHexString("1"))),
validators,
singletonList(proposerKeyPair),
0xDEADBEEF);
builder.extraData(ibftExtraData.encode());
return builder;
}
}

@ -0,0 +1,100 @@
/*
* 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 java.util.Collections.emptyList;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class IbftExtraDataFixture {
public static IbftExtraData createExtraData(
final BlockHeader header,
final BytesValue vanityData,
final Optional<Vote> vote,
final List<Address> validators,
final List<KeyPair> committerKeyPairs) {
return createExtraData(header, vanityData, vote, validators, committerKeyPairs, 0);
}
public static IbftExtraData createExtraData(
final BlockHeader header,
final BytesValue vanityData,
final Optional<Vote> vote,
final List<Address> validators,
final List<KeyPair> committerKeyPairs,
final int roundNumber) {
return createExtraData(
header, vanityData, vote, validators, committerKeyPairs, roundNumber, false);
}
public static IbftExtraData createExtraData(
final BlockHeader header,
final BytesValue vanityData,
final Optional<Vote> vote,
final List<Address> validators,
final List<KeyPair> committerKeyPairs,
final int baseRoundNumber,
final boolean useDifferentRoundNumbersForCommittedSeals) {
final IbftExtraData ibftExtraDataNoCommittedSeals =
new IbftExtraData(vanityData, emptyList(), vote, baseRoundNumber, validators);
// if useDifferentRoundNumbersForCommittedSeals is true then each committed seal will be
// calculated for an extraData field with a different round number
List<Signature> commitSeals =
IntStream.range(0, committerKeyPairs.size())
.mapToObj(
i -> {
final int round =
useDifferentRoundNumbersForCommittedSeals
? ibftExtraDataNoCommittedSeals.getRound() + i
: ibftExtraDataNoCommittedSeals.getRound();
IbftExtraData extraDataForCommittedSealCalculation =
new IbftExtraData(
ibftExtraDataNoCommittedSeals.getVanityData(),
emptyList(),
ibftExtraDataNoCommittedSeals.getVote(),
round,
ibftExtraDataNoCommittedSeals.getValidators());
final Hash headerHashForCommitters =
IbftBlockHashing.calculateDataHashForCommittedSeal(
header, extraDataForCommittedSealCalculation);
return SECP256K1.sign(headerHashForCommitters, committerKeyPairs.get(i));
})
.collect(Collectors.toList());
return new IbftExtraData(
ibftExtraDataNoCommittedSeals.getVanityData(),
commitSeals,
ibftExtraDataNoCommittedSeals.getVote(),
ibftExtraDataNoCommittedSeals.getRound(),
ibftExtraDataNoCommittedSeals.getValidators());
}
}

@ -31,7 +31,7 @@ import java.util.Random;
import com.google.common.collect.Lists;
import org.junit.Test;
public class Ibft2ExtraDataTest {
public class IbftExtraDataTest {
private final String RAW_HEX_ENCODING_STRING =
"f8f1a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea9400000000000000000000000000000000000"
+ "00001940000000000000000000000000000000000000002d794000000000000000000000000000000000000000181ff8400fedc"
@ -39,10 +39,10 @@ public class Ibft2ExtraDataTest {
+ "0000000000000000000000000000000000a00b84100000000000000000000000000000000000000000000000000000000000000"
+ "0a000000000000000000000000000000000000000000000000000000000000000100";
private final Ibft2ExtraData DECODED_EXTRA_DATA_FOR_RAW_HEX_ENCODING_STRING =
private final IbftExtraData DECODED_EXTRA_DATA_FOR_RAW_HEX_ENCODING_STRING =
getDecodedExtraDataForRawHexEncodingString();
private static Ibft2ExtraData getDecodedExtraDataForRawHexEncodingString() {
private static IbftExtraData getDecodedExtraDataForRawHexEncodingString() {
final List<Address> validators =
Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2"));
final Optional<Vote> vote = Optional.of(Vote.authVote(Address.fromHexString("1")));
@ -56,7 +56,7 @@ public class Ibft2ExtraDataTest {
final byte[] vanity_bytes = createNonEmptyVanityData();
final BytesValue vanity_data = BytesValue.wrap(vanity_bytes);
return new Ibft2ExtraData(vanity_data, committerSeals, vote, round, validators);
return new IbftExtraData(vanity_data, committerSeals, vote, round, validators);
}
@Test
@ -90,7 +90,7 @@ public class Ibft2ExtraDataTest {
final BytesValue bufferToInject = encoder.encoded();
final Ibft2ExtraData extraData = Ibft2ExtraData.decode(bufferToInject);
final IbftExtraData extraData = IbftExtraData.decode(bufferToInject);
assertThat(extraData.getVanityData()).isEqualTo(vanity_data);
assertThat(extraData.getVote()).isEqualTo(vote);
@ -100,7 +100,7 @@ public class Ibft2ExtraDataTest {
}
/**
* This test specifically verifies that {@link Ibft2ExtraData#decode(BytesValue)} uses {@link
* This test specifically verifies that {@link IbftExtraData#decode(BytesValue)} uses {@link
* RLPInput#readInt()} rather than {@link RLPInput#readIntScalar()} to decode the round number
*/
@Test
@ -134,8 +134,7 @@ public class Ibft2ExtraDataTest {
final BytesValue bufferToInject = encoder.encoded();
assertThatThrownBy(() -> Ibft2ExtraData.decode(bufferToInject))
.isInstanceOf(RLPException.class);
assertThatThrownBy(() -> IbftExtraData.decode(bufferToInject)).isInstanceOf(RLPException.class);
}
@Test
@ -163,7 +162,7 @@ public class Ibft2ExtraDataTest {
final BytesValue bufferToInject = encoder.encoded();
final Ibft2ExtraData extraData = Ibft2ExtraData.decode(bufferToInject);
final IbftExtraData extraData = IbftExtraData.decode(bufferToInject);
assertThat(extraData.getVanityData()).isEqualTo(vanity_data);
assertThat(extraData.getVote().isPresent()).isEqualTo(false);
@ -183,10 +182,10 @@ public class Ibft2ExtraDataTest {
final byte[] vanity_bytes = new byte[32];
final BytesValue vanity_data = BytesValue.wrap(vanity_bytes);
final Ibft2ExtraData expectedExtraData =
new Ibft2ExtraData(vanity_data, committerSeals, vote, round, validators);
IbftExtraData expectedExtraData =
new IbftExtraData(vanity_data, committerSeals, vote, round, validators);
final Ibft2ExtraData actualExtraData = Ibft2ExtraData.decode(expectedExtraData.encode());
IbftExtraData actualExtraData = IbftExtraData.decode(expectedExtraData.encode());
assertThat(actualExtraData).isEqualToComparingFieldByField(expectedExtraData);
}
@ -220,7 +219,7 @@ public class Ibft2ExtraDataTest {
final BytesValue bufferToInject = encoder.encoded();
final Ibft2ExtraData extraData = Ibft2ExtraData.decode(bufferToInject);
final IbftExtraData extraData = IbftExtraData.decode(bufferToInject);
assertThat(extraData.getVanityData()).isEqualTo(vanity_data);
assertThat(extraData.getVote()).isEqualTo(vote);
@ -240,10 +239,10 @@ public class Ibft2ExtraDataTest {
final byte[] vanity_bytes = new byte[32];
final BytesValue vanity_data = BytesValue.wrap(vanity_bytes);
final Ibft2ExtraData expectedExtraData =
new Ibft2ExtraData(vanity_data, committerSeals, vote, round, validators);
IbftExtraData expectedExtraData =
new IbftExtraData(vanity_data, committerSeals, vote, round, validators);
final Ibft2ExtraData actualExtraData = Ibft2ExtraData.decode(expectedExtraData.encode());
IbftExtraData actualExtraData = IbftExtraData.decode(expectedExtraData.encode());
assertThat(actualExtraData).isEqualToComparingFieldByField(expectedExtraData);
}
@ -282,7 +281,7 @@ public class Ibft2ExtraDataTest {
final BytesValue bufferToInject = encoder.encoded();
final Ibft2ExtraData extraData = Ibft2ExtraData.decode(bufferToInject);
final IbftExtraData extraData = IbftExtraData.decode(bufferToInject);
assertThat(extraData.getVanityData()).isEqualTo(vanity_data);
assertThat(extraData.getVote()).isEqualTo(vote);
@ -306,10 +305,10 @@ public class Ibft2ExtraDataTest {
final byte[] vanity_bytes = createNonEmptyVanityData();
final BytesValue vanity_data = BytesValue.wrap(vanity_bytes);
final Ibft2ExtraData expectedExtraData =
new Ibft2ExtraData(vanity_data, committerSeals, vote, round, validators);
IbftExtraData expectedExtraData =
new IbftExtraData(vanity_data, committerSeals, vote, round, validators);
final Ibft2ExtraData actualExtraData = Ibft2ExtraData.decode(expectedExtraData.encode());
IbftExtraData actualExtraData = IbftExtraData.decode(expectedExtraData.encode());
assertThat(actualExtraData).isEqualToComparingFieldByField(expectedExtraData);
}
@ -324,14 +323,87 @@ public class Ibft2ExtraDataTest {
@Test
public void decodingOfKnownRawHexStringMatchesKnowExtraDataObject() {
final Ibft2ExtraData expectedExtraData = DECODED_EXTRA_DATA_FOR_RAW_HEX_ENCODING_STRING;
final IbftExtraData expectedExtraData = DECODED_EXTRA_DATA_FOR_RAW_HEX_ENCODING_STRING;
final BytesValue rawDecoding = BytesValue.fromHexString(RAW_HEX_ENCODING_STRING);
final Ibft2ExtraData actualExtraData = Ibft2ExtraData.decode(rawDecoding);
BytesValue rawDecoding = BytesValue.fromHexString(RAW_HEX_ENCODING_STRING);
IbftExtraData actualExtraData = IbftExtraData.decode(rawDecoding);
assertThat(actualExtraData).isEqualToComparingFieldByField(expectedExtraData);
}
@Test
public void testEncodeWithoutCommitSeals() {
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);
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.endList();
BytesValue expectedEncoding = encoder.encoded();
BytesValue actualEncoding =
new IbftExtraData(vanity_data, committerSeals, vote, round, validators)
.encodeWithoutCommitSeals();
assertThat(actualEncoding).isEqualTo(expectedEncoding);
}
@Test
public void testEncodeWithoutCommitSealsAndRoundNumber() {
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);
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.endList();
BytesValue expectedEncoding = encoder.encoded();
BytesValue actualEncoding =
new IbftExtraData(vanity_data, committerSeals, vote, round, validators)
.encodeWithoutCommitSealsAndRoundNumber();
assertThat(actualEncoding).isEqualTo(expectedEncoding);
}
@Test
public void incorrectlyStructuredRlpThrowsException() {
final List<Address> validators = Lists.newArrayList();
@ -362,8 +434,7 @@ public class Ibft2ExtraDataTest {
final BytesValue bufferToInject = encoder.encoded();
assertThatThrownBy(() -> Ibft2ExtraData.decode(bufferToInject))
.isInstanceOf(RLPException.class);
assertThatThrownBy(() -> IbftExtraData.decode(bufferToInject)).isInstanceOf(RLPException.class);
}
@Test
@ -400,8 +471,7 @@ public class Ibft2ExtraDataTest {
final BytesValue bufferToInject = encoder.encoded();
assertThatThrownBy(() -> Ibft2ExtraData.decode(bufferToInject))
.isInstanceOf(RLPException.class);
assertThatThrownBy(() -> IbftExtraData.decode(bufferToInject)).isInstanceOf(RLPException.class);
}
private static byte[] createNonEmptyVanityData() {
@ -409,7 +479,6 @@ public class Ibft2ExtraDataTest {
for (int i = 0; i < vanity_bytes.length; i++) {
vanity_bytes[i] = (byte) (i + 1);
}
return vanity_bytes;
}
}

@ -0,0 +1,29 @@
/*
* 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 org.junit.Test;
public class IbftVoteTypeTest {
@Test
public void testValidatorVoteMethodImplementation() {
assertThat(IbftVoteType.ADD.isAddVote()).isTrue();
assertThat(IbftVoteType.ADD.isDropVote()).isFalse();
assertThat(IbftVoteType.DROP.isAddVote()).isFalse();
assertThat(IbftVoteType.DROP.isDropVote()).isTrue();
}
}

@ -0,0 +1,104 @@
/*
* 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.headervalidationrules;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.consensus.common.VoteTally;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraDataFixture;
import tech.pegasys.pantheon.consensus.ibft.Vote;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.List;
import java.util.Optional;
import com.google.common.collect.Lists;
import org.junit.Test;
public class IbftCoinbaseValidationRuleTest {
public static BlockHeader createProposedBlockHeader(
final KeyPair proposerKeyPair,
final List<Address> validators,
final List<KeyPair> committerKeyPairs) {
final BlockHeaderTestFixture builder = new BlockHeaderTestFixture();
builder.number(1); // must NOT be block 0, as that should not contain seals at all
builder.coinbase(Util.publicKeyToAddress(proposerKeyPair.getPublicKey()));
final BlockHeader header = builder.buildHeader();
final IbftExtraData ibftExtraData =
IbftExtraDataFixture.createExtraData(
header,
BytesValue.wrap(new byte[IbftExtraData.EXTRA_VANITY_LENGTH]),
Optional.of(Vote.authVote(Address.fromHexString("1"))),
validators,
committerKeyPairs);
builder.extraData(ibftExtraData.encode());
return builder.buildHeader();
}
@Test
public void proposerInValidatorListPassesValidation() {
final KeyPair proposerKeyPair = KeyPair.generate();
final Address proposerAddress =
Address.extract(Hash.hash(proposerKeyPair.getPublicKey().getEncodedBytes()));
final List<Address> validators = Lists.newArrayList(proposerAddress);
final List<KeyPair> committers = Lists.newArrayList(proposerKeyPair);
final VoteTally voteTally = new VoteTally(validators);
final ProtocolContext<IbftContext> context =
new ProtocolContext<>(null, null, new IbftContext(voteTally, null));
final IbftCoinbaseValidationRule coinbaseValidationRule = new IbftCoinbaseValidationRule();
BlockHeader header = createProposedBlockHeader(proposerKeyPair, validators, committers);
assertThat(coinbaseValidationRule.validate(header, null, context)).isTrue();
}
@Test
public void proposerNotInValidatorListFailsValidation() {
final KeyPair proposerKeyPair = KeyPair.generate();
final KeyPair otherValidatorKeyPair = KeyPair.generate();
final Address otherValidatorNodeAddress =
Address.extract(Hash.hash(otherValidatorKeyPair.getPublicKey().getEncodedBytes()));
final List<Address> validators = Lists.newArrayList(otherValidatorNodeAddress);
final List<KeyPair> committers = Lists.newArrayList(otherValidatorKeyPair);
final VoteTally voteTally = new VoteTally(validators);
final ProtocolContext<IbftContext> context =
new ProtocolContext<>(null, null, new IbftContext(voteTally, null));
final IbftCoinbaseValidationRule coinbaseValidationRule = new IbftCoinbaseValidationRule();
BlockHeader header = createProposedBlockHeader(proposerKeyPair, validators, committers);
assertThat(coinbaseValidationRule.validate(header, null, context)).isFalse();
}
}

@ -0,0 +1,253 @@
/*
* 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.headervalidationrules;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.consensus.common.VoteTally;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraDataFixture;
import tech.pegasys.pantheon.consensus.ibft.Vote;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import com.google.common.collect.Lists;
import org.junit.Test;
public class IbftExtraDataValidationRuleTest {
public static BlockHeader createProposedBlockHeader(
final List<Address> validators,
final List<KeyPair> committerKeyPairs,
final boolean useDifferentRoundNumbersForCommittedSeals) {
final int BASE_ROUND_NUMBER = 5;
final BlockHeaderTestFixture builder = new BlockHeaderTestFixture();
builder.number(1); // must NOT be block 0, as that should not contain seals at all
final BlockHeader header = builder.buildHeader();
final IbftExtraData ibftExtraData =
IbftExtraDataFixture.createExtraData(
header,
BytesValue.wrap(new byte[IbftExtraData.EXTRA_VANITY_LENGTH]),
Optional.of(Vote.authVote(Address.fromHexString("1"))),
validators,
committerKeyPairs,
BASE_ROUND_NUMBER,
useDifferentRoundNumbersForCommittedSeals);
builder.extraData(ibftExtraData.encode());
return builder.buildHeader();
}
@Test
public void correctlyConstructedHeaderPassesValidation() {
final List<KeyPair> committerKeyPairs =
IntStream.range(0, 2).mapToObj(i -> KeyPair.generate()).collect(Collectors.toList());
final List<Address> committerAddresses =
committerKeyPairs
.stream()
.map(keyPair -> Util.publicKeyToAddress(keyPair.getPublicKey()))
.sorted()
.collect(Collectors.toList());
final VoteTally voteTally = new VoteTally(committerAddresses);
final ProtocolContext<IbftContext> context =
new ProtocolContext<>(null, null, new IbftContext(voteTally, null));
final IbftExtraDataValidationRule extraDataValidationRule =
new IbftExtraDataValidationRule(true);
BlockHeader header = createProposedBlockHeader(committerAddresses, committerKeyPairs, false);
assertThat(extraDataValidationRule.validate(header, null, context)).isTrue();
}
@Test
public void insufficientCommitSealsFailsValidation() {
final KeyPair committerKeyPair = KeyPair.generate();
final Address committerAddress =
Address.extract(Hash.hash(committerKeyPair.getPublicKey().getEncodedBytes()));
final List<Address> validators = singletonList(committerAddress);
final VoteTally voteTally = new VoteTally(validators);
final ProtocolContext<IbftContext> context =
new ProtocolContext<>(null, null, new IbftContext(voteTally, null));
final IbftExtraDataValidationRule extraDataValidationRule =
new IbftExtraDataValidationRule(true);
final BlockHeader header = createProposedBlockHeader(validators, emptyList(), false);
// Note that no committer seals are in the header's IBFT extra data.
final IbftExtraData headerExtraData = IbftExtraData.decode(header.getExtraData());
assertThat(headerExtraData.getSeals().size()).isEqualTo(0);
assertThat(extraDataValidationRule.validate(header, null, context)).isFalse();
}
@Test
public void outOfOrderValidatorListFailsValidation() {
final List<KeyPair> committerKeyPairs =
IntStream.range(0, 2).mapToObj(i -> KeyPair.generate()).collect(Collectors.toList());
final List<Address> committerAddresses =
committerKeyPairs
.stream()
.map(keyPair -> Util.publicKeyToAddress(keyPair.getPublicKey()))
.sorted()
.collect(Collectors.toList());
final List<Address> validators = Lists.reverse(committerAddresses);
final VoteTally voteTally = new VoteTally(validators);
final ProtocolContext<IbftContext> context =
new ProtocolContext<>(null, null, new IbftContext(voteTally, null));
final IbftExtraDataValidationRule extraDataValidationRule =
new IbftExtraDataValidationRule(true);
BlockHeader header = createProposedBlockHeader(validators, committerKeyPairs, false);
assertThat(extraDataValidationRule.validate(header, null, context)).isFalse();
}
@Test
public void mismatchingReportedValidatorsVsLocallyStoredListFailsValidation() {
final List<KeyPair> committerKeyPairs =
IntStream.range(0, 2).mapToObj(i -> KeyPair.generate()).collect(Collectors.toList());
final List<Address> validators =
IntStream.range(0, 2)
.mapToObj(i -> Util.publicKeyToAddress(KeyPair.generate().getPublicKey()))
.collect(Collectors.toList());
final VoteTally voteTally = new VoteTally(validators);
final ProtocolContext<IbftContext> context =
new ProtocolContext<>(null, null, new IbftContext(voteTally, null));
final IbftExtraDataValidationRule extraDataValidationRule =
new IbftExtraDataValidationRule(true);
BlockHeader header = createProposedBlockHeader(validators, committerKeyPairs, false);
assertThat(extraDataValidationRule.validate(header, null, context)).isFalse();
}
@Test
public void committerNotInValidatorListFailsValidation() {
final KeyPair committerKeyPair = KeyPair.generate();
final Address committerAddress = Util.publicKeyToAddress(committerKeyPair.getPublicKey());
final List<Address> validators = singletonList(committerAddress);
final VoteTally voteTally = new VoteTally(validators);
// Insert an extraData block with committer seals.
final KeyPair nonValidatorKeyPair = KeyPair.generate();
BlockHeader header =
createProposedBlockHeader(validators, singletonList(nonValidatorKeyPair), false);
final ProtocolContext<IbftContext> context =
new ProtocolContext<>(null, null, new IbftContext(voteTally, null));
final IbftExtraDataValidationRule extraDataValidationRule =
new IbftExtraDataValidationRule(true);
assertThat(extraDataValidationRule.validate(header, null, context)).isFalse();
}
@Test
public void ratioOfCommittersToValidatorsAffectValidation() {
assertThat(subExecution(4, 4, false)).isEqualTo(true);
assertThat(subExecution(4, 3, false)).isEqualTo(true);
assertThat(subExecution(4, 2, false)).isEqualTo(false);
assertThat(subExecution(5, 4, false)).isEqualTo(true);
assertThat(subExecution(5, 3, false)).isEqualTo(false);
assertThat(subExecution(5, 2, false)).isEqualTo(false);
assertThat(subExecution(6, 4, false)).isEqualTo(true);
assertThat(subExecution(6, 3, false)).isEqualTo(false);
assertThat(subExecution(6, 2, false)).isEqualTo(false);
assertThat(subExecution(7, 5, false)).isEqualTo(true);
assertThat(subExecution(7, 4, false)).isEqualTo(false);
assertThat(subExecution(8, 6, false)).isEqualTo(true);
assertThat(subExecution(8, 5, false)).isEqualTo(false);
assertThat(subExecution(8, 4, false)).isEqualTo(false);
assertThat(subExecution(9, 6, false)).isEqualTo(true);
assertThat(subExecution(9, 5, false)).isEqualTo(false);
assertThat(subExecution(9, 4, false)).isEqualTo(false);
assertThat(subExecution(10, 7, false)).isEqualTo(true);
assertThat(subExecution(10, 6, false)).isEqualTo(false);
assertThat(subExecution(12, 8, false)).isEqualTo(true);
assertThat(subExecution(12, 7, false)).isEqualTo(false);
assertThat(subExecution(12, 6, false)).isEqualTo(false);
}
@Test
public void validationFailsIfCommittedSealsAreForDifferentRounds() {
assertThat(subExecution(2, 2, true)).isEqualTo(false);
assertThat(subExecution(4, 4, true)).isEqualTo(false);
}
private boolean subExecution(
final int validatorCount,
final int committerCount,
final boolean useDifferentRoundNumbersForCommittedSeals) {
final List<Address> validators = Lists.newArrayList();
final List<KeyPair> committerKeys = Lists.newArrayList();
for (int i = 0; i < validatorCount; i++) { // need -1 to account for proposer
final KeyPair committerKeyPair = KeyPair.generate();
committerKeys.add(committerKeyPair);
validators.add(Address.extract(Hash.hash(committerKeyPair.getPublicKey().getEncodedBytes())));
}
Collections.sort(validators);
final VoteTally voteTally = new VoteTally(validators);
BlockHeader header =
createProposedBlockHeader(
validators,
committerKeys.subList(0, committerCount),
useDifferentRoundNumbersForCommittedSeals);
final ProtocolContext<IbftContext> context =
new ProtocolContext<>(null, null, new IbftContext(voteTally, null));
final IbftExtraDataValidationRule extraDataValidationRule =
new IbftExtraDataValidationRule(true);
return extraDataValidationRule.validate(header, null, context);
}
}

@ -34,4 +34,12 @@ public class Util {
public static Address publicKeyToAddress(final PublicKey publicKey) {
return Address.extract(Hash.hash(publicKey.getEncodedBytes()));
}
/**
* Implements a fast version of ceiling(numerator/denominator) that does not require using
* floating point math
*/
public static int fastDivCeiling(final int numerator, final int denominator) {
return ((numerator - 1) / denominator) + 1;
}
}

@ -0,0 +1,33 @@
/*
* 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.ethereum.core;
import static org.assertj.core.api.Java6Assertions.assertThat;
import org.junit.Test;
public class UtilTest {
@Test
public void testFastDivCeil() {
assertThat(Util.fastDivCeiling(0, 3)).isEqualTo(1);
assertThat(Util.fastDivCeiling(1, 3)).isEqualTo(1);
assertThat(Util.fastDivCeiling(2, 3)).isEqualTo(1);
assertThat(Util.fastDivCeiling(3, 3)).isEqualTo(1);
assertThat(Util.fastDivCeiling(4, 3)).isEqualTo(2);
assertThat(Util.fastDivCeiling(5, 3)).isEqualTo(2);
assertThat(Util.fastDivCeiling(6, 3)).isEqualTo(2);
assertThat(Util.fastDivCeiling(7, 3)).isEqualTo(3);
}
}
Loading…
Cancel
Save