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