mirror of https://github.com/hyperledger/besu
[NC-1582] Added iBFT 2.0 Hashing functions and header validation rules (#119)
parent
8e9a1ddc72
commit
77a3df36ae
@ -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(); |
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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()); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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…
Reference in new issue