mirror of https://github.com/hyperledger/besu
Separating Validator voting from block serialisation values (#263)
The consensus-mechanism specific block values have been separated from the VoteTallyUpdater such that there is a single updater rather than one per consensus mechanism. This has necessitated the creation of a custom serialiser/ deserialiser for each mechanism instead. This change will ultimatley bleed through to the proposed votes and their insertion to mined blocks.
parent
cc4f4df6a5
commit
c2e98152e0
@ -1,76 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.clique; |
|
||||||
|
|
||||||
import static org.apache.logging.log4j.LogManager.getLogger; |
|
||||||
|
|
||||||
import tech.pegasys.pantheon.consensus.common.EpochManager; |
|
||||||
import tech.pegasys.pantheon.consensus.common.VoteTally; |
|
||||||
import tech.pegasys.pantheon.consensus.common.VoteType; |
|
||||||
import tech.pegasys.pantheon.ethereum.chain.Blockchain; |
|
||||||
import tech.pegasys.pantheon.ethereum.core.Address; |
|
||||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
|
||||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import org.apache.logging.log4j.Logger; |
|
||||||
|
|
||||||
public class CliqueVoteTallyUpdater { |
|
||||||
|
|
||||||
private static final Logger LOG = getLogger(); |
|
||||||
public static final Address NO_VOTE_SUBJECT = Address.wrap(BytesValue.wrap(new byte[20])); |
|
||||||
|
|
||||||
private final EpochManager epochManager; |
|
||||||
|
|
||||||
public CliqueVoteTallyUpdater(final EpochManager epochManager) { |
|
||||||
this.epochManager = epochManager; |
|
||||||
} |
|
||||||
|
|
||||||
public VoteTally buildVoteTallyFromBlockchain(final Blockchain blockchain) { |
|
||||||
final long chainHeadBlockNumber = blockchain.getChainHeadBlockNumber(); |
|
||||||
final long epochBlockNumber = epochManager.getLastEpochBlock(chainHeadBlockNumber); |
|
||||||
LOG.debug("Loading validator voting state starting from block {}", epochBlockNumber); |
|
||||||
final BlockHeader epochBlock = blockchain.getBlockHeader(epochBlockNumber).get(); |
|
||||||
final List<Address> initialValidators = |
|
||||||
CliqueExtraData.decode(epochBlock.getExtraData()).getValidators(); |
|
||||||
final VoteTally voteTally = new VoteTally(initialValidators); |
|
||||||
for (long blockNumber = epochBlockNumber + 1; |
|
||||||
blockNumber <= chainHeadBlockNumber; |
|
||||||
blockNumber++) { |
|
||||||
updateForBlock(blockchain.getBlockHeader(blockNumber).get(), voteTally); |
|
||||||
} |
|
||||||
return voteTally; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Update the vote tally to reflect changes caused by appending a new block to the chain. |
|
||||||
* |
|
||||||
* @param header the header of the block being added |
|
||||||
* @param voteTally the vote tally to update |
|
||||||
*/ |
|
||||||
public void updateForBlock(final BlockHeader header, final VoteTally voteTally) { |
|
||||||
final Address candidate = header.getCoinbase(); |
|
||||||
if (epochManager.isEpochBlock(header.getNumber())) { |
|
||||||
// epoch blocks are not allowed to include a vote
|
|
||||||
voteTally.discardOutstandingVotes(); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (!candidate.equals(NO_VOTE_SUBJECT)) { |
|
||||||
final CliqueExtraData extraData = CliqueExtraData.decode(header.getExtraData()); |
|
||||||
final Address proposer = CliqueBlockHashing.recoverProposerAddress(header, extraData); |
|
||||||
voteTally.addVote(proposer, candidate, VoteType.fromNonce(header.getNonce()).get()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,70 @@ |
|||||||
|
/* |
||||||
|
* 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.clique; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.consensus.common.CastVote; |
||||||
|
import tech.pegasys.pantheon.consensus.common.ValidatorVotePolarity; |
||||||
|
import tech.pegasys.pantheon.consensus.common.VoteBlockInterface; |
||||||
|
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.util.bytes.BytesValue; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableBiMap; |
||||||
|
|
||||||
|
public class CliqueVotingBlockInterface implements VoteBlockInterface { |
||||||
|
|
||||||
|
public static final Address NO_VOTE_SUBJECT = |
||||||
|
Address.wrap(BytesValue.wrap(new byte[Address.SIZE])); |
||||||
|
|
||||||
|
private static final ImmutableBiMap<ValidatorVotePolarity, Long> voteToValue = |
||||||
|
ImmutableBiMap.of( |
||||||
|
ValidatorVotePolarity.ADD, 0xFFFFFFFFFFFFFFFFL, |
||||||
|
ValidatorVotePolarity.DROP, 0x0L); |
||||||
|
|
||||||
|
@Override |
||||||
|
public Optional<CastVote> extractVoteFromHeader(final BlockHeader header) { |
||||||
|
final Address candidate = header.getCoinbase(); |
||||||
|
if (!candidate.equals(NO_VOTE_SUBJECT)) { |
||||||
|
final CliqueExtraData cliqueExtraData = CliqueExtraData.decode(header.getExtraData()); |
||||||
|
final Address proposer = CliqueBlockHashing.recoverProposerAddress(header, cliqueExtraData); |
||||||
|
final ValidatorVotePolarity votePolarity = voteToValue.inverse().get(header.getNonce()); |
||||||
|
final Address recipient = header.getCoinbase(); |
||||||
|
|
||||||
|
return Optional.of(new CastVote(votePolarity, proposer, recipient)); |
||||||
|
} |
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BlockHeaderBuilder insertVoteToHeaderBuilder( |
||||||
|
final BlockHeaderBuilder builder, final Optional<CastVote> vote) { |
||||||
|
if (vote.isPresent()) { |
||||||
|
final CastVote voteToCast = vote.get(); |
||||||
|
builder.nonce(voteToValue.get(voteToCast.getVotePolarity())); |
||||||
|
builder.coinbase(voteToCast.getRecipient()); |
||||||
|
} else { |
||||||
|
builder.nonce(voteToValue.get(ValidatorVotePolarity.DROP)); |
||||||
|
builder.coinbase(NO_VOTE_SUBJECT); |
||||||
|
} |
||||||
|
return builder; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<Address> validatorsInBlock(final BlockHeader header) { |
||||||
|
return CliqueExtraData.decode(header.getExtraData()).getValidators(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,126 @@ |
|||||||
|
/* |
||||||
|
* 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.clique; |
||||||
|
|
||||||
|
import static java.util.Collections.singletonList; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static tech.pegasys.pantheon.consensus.common.ValidatorVotePolarity.ADD; |
||||||
|
import static tech.pegasys.pantheon.consensus.common.ValidatorVotePolarity.DROP; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.consensus.common.CastVote; |
||||||
|
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Address; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.AddressHelpers; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeaderBuilder; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Util; |
||||||
|
import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHashFunction; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
public class CliqueVotingBlockInterfaceTest { |
||||||
|
|
||||||
|
private static final KeyPair proposerKeys = KeyPair.generate(); |
||||||
|
private static final Address proposerAddress = |
||||||
|
Util.publicKeyToAddress(proposerKeys.getPublicKey()); |
||||||
|
private static final List<Address> validatorList = singletonList(proposerAddress); |
||||||
|
|
||||||
|
private final CliqueVotingBlockInterface blockInterface = new CliqueVotingBlockInterface(); |
||||||
|
|
||||||
|
private final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
||||||
|
|
||||||
|
private final BlockHeader header = |
||||||
|
TestHelpers.createCliqueSignedBlockHeader(headerBuilder, proposerKeys, validatorList); |
||||||
|
|
||||||
|
private final BlockHeaderBuilder builder = |
||||||
|
BlockHeaderBuilder.fromHeader(headerBuilder.buildHeader()) |
||||||
|
.blockHashFunction(MainnetBlockHashFunction::createHash); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void headerWithZeroCoinbaseReturnsAnEmptyVote() { |
||||||
|
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
||||||
|
headerBuilder.coinbase(AddressHelpers.ofValue(0)); |
||||||
|
|
||||||
|
assertThat(blockInterface.extractVoteFromHeader(headerBuilder.buildHeader())).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void headerWithNonceOfZeroReportsDropVote() { |
||||||
|
headerBuilder.coinbase(AddressHelpers.ofValue(1)).nonce(0L); |
||||||
|
|
||||||
|
final Optional<CastVote> extractedVote = blockInterface.extractVoteFromHeader(header); |
||||||
|
|
||||||
|
assertThat(extractedVote).contains(new CastVote(DROP, proposerAddress, header.getCoinbase())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void headerWithNonceOfMaxLongReportsAddVote() { |
||||||
|
headerBuilder.coinbase(AddressHelpers.ofValue(2)).nonce(0xFFFFFFFFFFFFFFFFL); |
||||||
|
|
||||||
|
final BlockHeader header = |
||||||
|
TestHelpers.createCliqueSignedBlockHeader(headerBuilder, proposerKeys, validatorList); |
||||||
|
final Optional<CastVote> extractedVote = blockInterface.extractVoteFromHeader(header); |
||||||
|
|
||||||
|
assertThat(extractedVote).contains(new CastVote(ADD, proposerAddress, header.getCoinbase())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void blendingAddVoteToHeaderResultsInHeaderWithNonceOfMaxLong() { |
||||||
|
|
||||||
|
final CastVote vote = new CastVote(ADD, AddressHelpers.ofValue(1), AddressHelpers.ofValue(2)); |
||||||
|
final BlockHeaderBuilder builderWithVote = |
||||||
|
blockInterface.insertVoteToHeaderBuilder(builder, Optional.of(vote)); |
||||||
|
|
||||||
|
final BlockHeader header = builderWithVote.buildBlockHeader(); |
||||||
|
|
||||||
|
assertThat(header.getCoinbase()).isEqualTo(vote.getRecipient()); |
||||||
|
assertThat(header.getNonce()).isEqualTo(0xFFFFFFFFFFFFFFFFL); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void blendingDropVoteToHeaderResultsInHeaderWithNonceOfZero() { |
||||||
|
|
||||||
|
final CastVote vote = new CastVote(DROP, AddressHelpers.ofValue(1), AddressHelpers.ofValue(2)); |
||||||
|
final BlockHeaderBuilder builderWithVote = |
||||||
|
blockInterface.insertVoteToHeaderBuilder(builder, Optional.of(vote)); |
||||||
|
|
||||||
|
final BlockHeader header = builderWithVote.buildBlockHeader(); |
||||||
|
|
||||||
|
assertThat(header.getCoinbase()).isEqualTo(vote.getRecipient()); |
||||||
|
assertThat(header.getNonce()).isEqualTo(0x0L); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void nonVoteBlendedIntoHeaderResultsInACoinbaseOfZero() { |
||||||
|
final BlockHeaderBuilder builderWithVote = |
||||||
|
blockInterface.insertVoteToHeaderBuilder(builder, Optional.empty()); |
||||||
|
|
||||||
|
final BlockHeader header = builderWithVote.buildBlockHeader(); |
||||||
|
|
||||||
|
assertThat(header.getCoinbase()).isEqualTo(AddressHelpers.ofValue(0)); |
||||||
|
assertThat(header.getNonce()).isEqualTo(0x0L); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void extractsValidatorsFromHeader() { |
||||||
|
final BlockHeader header = |
||||||
|
TestHelpers.createCliqueSignedBlockHeader(headerBuilder, proposerKeys, validatorList); |
||||||
|
final List<Address> extractedValidators = blockInterface.validatorsInBlock(header); |
||||||
|
|
||||||
|
assertThat(extractedValidators).isEqualTo(validatorList); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.ethereum.core.Address; |
||||||
|
|
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
import com.google.common.base.Preconditions; |
||||||
|
|
||||||
|
public class CastVote { |
||||||
|
|
||||||
|
private final ValidatorVotePolarity votePolarity; |
||||||
|
private final Address proposer; |
||||||
|
private final Address recipient; |
||||||
|
|
||||||
|
public CastVote( |
||||||
|
final ValidatorVotePolarity votePolarity, final Address proposer, final Address recipient) { |
||||||
|
Preconditions.checkNotNull(votePolarity); |
||||||
|
Preconditions.checkNotNull(proposer); |
||||||
|
Preconditions.checkNotNull(recipient); |
||||||
|
this.votePolarity = votePolarity; |
||||||
|
this.proposer = proposer; |
||||||
|
this.recipient = recipient; |
||||||
|
} |
||||||
|
|
||||||
|
public ValidatorVotePolarity getVotePolarity() { |
||||||
|
return votePolarity; |
||||||
|
} |
||||||
|
|
||||||
|
public Address getProposer() { |
||||||
|
return proposer; |
||||||
|
} |
||||||
|
|
||||||
|
public Address getRecipient() { |
||||||
|
return recipient; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(final Object o) { |
||||||
|
if (this == o) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (o == null || getClass() != o.getClass()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
CastVote castVote = (CastVote) o; |
||||||
|
return votePolarity == castVote.votePolarity |
||||||
|
&& Objects.equals(proposer, castVote.proposer) |
||||||
|
&& Objects.equals(recipient, castVote.recipient); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return Objects.hash(votePolarity, proposer, recipient); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
|
||||||
|
/** |
||||||
|
* Determines if a validator vote is indicating that they should be added, or removed. This does not |
||||||
|
* attempt to determine how said vote should be serialised/deserialised. |
||||||
|
*/ |
||||||
|
public enum ValidatorVotePolarity implements ValidatorVote { |
||||||
|
ADD, |
||||||
|
DROP; |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isAddVote() { |
||||||
|
return this.equals(ADD); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isDropVote() { |
||||||
|
return this.equals(DROP); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2018 ConsenSys AG. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||||
|
* specific language governing permissions and limitations under the License. |
||||||
|
*/ |
||||||
|
package tech.pegasys.pantheon.consensus.common; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.ethereum.core.Address; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeaderBuilder; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
public interface VoteBlockInterface { |
||||||
|
|
||||||
|
Optional<CastVote> extractVoteFromHeader(final BlockHeader header); |
||||||
|
|
||||||
|
BlockHeaderBuilder insertVoteToHeaderBuilder( |
||||||
|
final BlockHeaderBuilder builder, final Optional<CastVote> vote); |
||||||
|
|
||||||
|
List<Address> validatorsInBlock(final BlockHeader header); |
||||||
|
} |
@ -1,35 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.common.VoteTally; |
|
||||||
import tech.pegasys.pantheon.ethereum.chain.Blockchain; |
|
||||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
|
||||||
|
|
||||||
public interface VoteTallyUpdater { |
|
||||||
/** |
|
||||||
* Create a new VoteTally based on the current blockchain state. |
|
||||||
* |
|
||||||
* @param blockchain the blockchain to load the current state from |
|
||||||
* @return a VoteTally reflecting the state of the blockchain head |
|
||||||
*/ |
|
||||||
VoteTally buildVoteTallyFromBlockchain(final Blockchain blockchain); |
|
||||||
|
|
||||||
/** |
|
||||||
* Update the vote tally to reflect changes caused by appending a new block to the chain. |
|
||||||
* |
|
||||||
* @param header the header of the block being added |
|
||||||
* @param voteTally the vote tally to update |
|
||||||
*/ |
|
||||||
void updateForBlock(final BlockHeader header, final VoteTally voteTally); |
|
||||||
} |
|
@ -0,0 +1,70 @@ |
|||||||
|
/* |
||||||
|
* 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.ibftlegacy; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.consensus.common.CastVote; |
||||||
|
import tech.pegasys.pantheon.consensus.common.ValidatorVotePolarity; |
||||||
|
import tech.pegasys.pantheon.consensus.common.VoteBlockInterface; |
||||||
|
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.util.bytes.BytesValue; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableBiMap; |
||||||
|
|
||||||
|
public class IbftLegacyVotingBlockInterface implements VoteBlockInterface { |
||||||
|
|
||||||
|
private static final Address NO_VOTE_SUBJECT = |
||||||
|
Address.wrap(BytesValue.wrap(new byte[Address.SIZE])); |
||||||
|
|
||||||
|
private static final ImmutableBiMap<ValidatorVotePolarity, Long> voteToValue = |
||||||
|
ImmutableBiMap.of( |
||||||
|
ValidatorVotePolarity.ADD, 0xFFFFFFFFFFFFFFFFL, |
||||||
|
ValidatorVotePolarity.DROP, 0x0L); |
||||||
|
|
||||||
|
@Override |
||||||
|
public Optional<CastVote> extractVoteFromHeader(final BlockHeader header) { |
||||||
|
final Address candidate = header.getCoinbase(); |
||||||
|
if (!candidate.equals(NO_VOTE_SUBJECT)) { |
||||||
|
final IbftExtraData ibftExtraData = IbftExtraData.decode(header.getExtraData()); |
||||||
|
final Address proposer = IbftBlockHashing.recoverProposerAddress(header, ibftExtraData); |
||||||
|
final ValidatorVotePolarity votePolarity = voteToValue.inverse().get(header.getNonce()); |
||||||
|
final Address recipient = header.getCoinbase(); |
||||||
|
|
||||||
|
return Optional.of(new CastVote(votePolarity, proposer, recipient)); |
||||||
|
} |
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BlockHeaderBuilder insertVoteToHeaderBuilder( |
||||||
|
final BlockHeaderBuilder builder, final Optional<CastVote> vote) { |
||||||
|
if (vote.isPresent()) { |
||||||
|
final CastVote voteToCast = vote.get(); |
||||||
|
builder.nonce(voteToValue.get(voteToCast.getVotePolarity())); |
||||||
|
builder.coinbase(voteToCast.getRecipient()); |
||||||
|
} else { |
||||||
|
builder.nonce(voteToValue.get(ValidatorVotePolarity.DROP)); |
||||||
|
builder.coinbase(NO_VOTE_SUBJECT); |
||||||
|
} |
||||||
|
return builder; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<Address> validatorsInBlock(final BlockHeader header) { |
||||||
|
return IbftExtraData.decode(header.getExtraData()).getValidators(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,127 @@ |
|||||||
|
/* |
||||||
|
* 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.ibftlegacy; |
||||||
|
|
||||||
|
import static java.util.Collections.singletonList; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static tech.pegasys.pantheon.consensus.common.ValidatorVotePolarity.ADD; |
||||||
|
import static tech.pegasys.pantheon.consensus.common.ValidatorVotePolarity.DROP; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.consensus.common.CastVote; |
||||||
|
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Address; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.AddressHelpers; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeaderBuilder; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Util; |
||||||
|
import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHashFunction; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
public class IbftLegacyVotingBlockInterfaceTest { |
||||||
|
|
||||||
|
private static final KeyPair proposerKeys = KeyPair.generate(); |
||||||
|
private static final Address proposerAddress = |
||||||
|
Util.publicKeyToAddress(proposerKeys.getPublicKey()); |
||||||
|
private static final List<Address> validatorList = singletonList(proposerAddress); |
||||||
|
|
||||||
|
private final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
||||||
|
private final IbftLegacyVotingBlockInterface blockInterface = |
||||||
|
new IbftLegacyVotingBlockInterface(); |
||||||
|
private final BlockHeaderBuilder builder = |
||||||
|
BlockHeaderBuilder.fromHeader(headerBuilder.buildHeader()) |
||||||
|
.blockHashFunction(MainnetBlockHashFunction::createHash); |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setup() { |
||||||
|
// must set "number" to ensure extradata is correctly deserialised during hashing.
|
||||||
|
headerBuilder.coinbase(AddressHelpers.ofValue(0)).number(1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void headerWithZeroCoinbaseReturnsAnEmptyVote() { |
||||||
|
assertThat(blockInterface.extractVoteFromHeader(headerBuilder.buildHeader())).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void headerWithNonceOfZeroReportsDropVote() { |
||||||
|
headerBuilder.nonce(0x0L).coinbase(AddressHelpers.ofValue(2)); |
||||||
|
final BlockHeader header = |
||||||
|
TestHelpers.createIbftSignedBlockHeader(headerBuilder, proposerKeys, validatorList); |
||||||
|
final Optional<CastVote> extractedVote = blockInterface.extractVoteFromHeader(header); |
||||||
|
|
||||||
|
assertThat(extractedVote).contains(new CastVote(DROP, proposerAddress, header.getCoinbase())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void headerWithNonceOfMaxLongReportsAddVote() { |
||||||
|
headerBuilder.nonce(0xFFFFFFFFFFFFFFFFL).coinbase(AddressHelpers.ofValue(2)); |
||||||
|
|
||||||
|
final BlockHeader header = |
||||||
|
TestHelpers.createIbftSignedBlockHeader(headerBuilder, proposerKeys, validatorList); |
||||||
|
final Optional<CastVote> extractedVote = blockInterface.extractVoteFromHeader(header); |
||||||
|
|
||||||
|
assertThat(extractedVote).contains(new CastVote(ADD, proposerAddress, header.getCoinbase())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void blendingAddVoteToHeaderResultsInHeaderWithNonceOfMaxLong() { |
||||||
|
final CastVote vote = new CastVote(ADD, AddressHelpers.ofValue(1), AddressHelpers.ofValue(2)); |
||||||
|
final BlockHeaderBuilder builderWithVote = |
||||||
|
blockInterface.insertVoteToHeaderBuilder(builder, Optional.of(vote)); |
||||||
|
|
||||||
|
final BlockHeader header = builderWithVote.buildBlockHeader(); |
||||||
|
|
||||||
|
assertThat(header.getCoinbase()).isEqualTo(vote.getRecipient()); |
||||||
|
assertThat(header.getNonce()).isEqualTo(0xFFFFFFFFFFFFFFFFL); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void blendingDropVoteToHeaderResultsInHeaderWithNonceOfZero() { |
||||||
|
final CastVote vote = new CastVote(DROP, AddressHelpers.ofValue(1), AddressHelpers.ofValue(2)); |
||||||
|
final BlockHeaderBuilder builderWithVote = |
||||||
|
blockInterface.insertVoteToHeaderBuilder(builder, Optional.of(vote)); |
||||||
|
|
||||||
|
final BlockHeader header = builderWithVote.buildBlockHeader(); |
||||||
|
|
||||||
|
assertThat(header.getCoinbase()).isEqualTo(vote.getRecipient()); |
||||||
|
assertThat(header.getNonce()).isEqualTo(0x0L); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void nonVoteBlendedIntoHeaderResultsInACoinbaseOfZero() { |
||||||
|
final BlockHeaderBuilder builderWithVote = |
||||||
|
blockInterface.insertVoteToHeaderBuilder(builder, Optional.empty()); |
||||||
|
|
||||||
|
final BlockHeader header = builderWithVote.buildBlockHeader(); |
||||||
|
|
||||||
|
assertThat(header.getCoinbase()).isEqualTo(AddressHelpers.ofValue(0)); |
||||||
|
assertThat(header.getNonce()).isEqualTo(0x0L); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void extractsValidatorsFromHeader() { |
||||||
|
final BlockHeader header = |
||||||
|
TestHelpers.createIbftSignedBlockHeader(headerBuilder, proposerKeys, validatorList); |
||||||
|
|
||||||
|
final IbftLegacyVotingBlockInterface serDeser = new IbftLegacyVotingBlockInterface(); |
||||||
|
final List<Address> extractedValidators = serDeser.validatorsInBlock(header); |
||||||
|
|
||||||
|
assertThat(extractedValidators).isEqualTo(validatorList); |
||||||
|
} |
||||||
|
} |
@ -1,198 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.ibftlegacy; |
|
||||||
|
|
||||||
import static java.util.Arrays.asList; |
|
||||||
import static java.util.Collections.emptyList; |
|
||||||
import static java.util.Collections.singletonList; |
|
||||||
import static org.assertj.core.api.Assertions.assertThat; |
|
||||||
import static org.mockito.Mockito.mock; |
|
||||||
import static org.mockito.Mockito.verify; |
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions; |
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions; |
|
||||||
import static org.mockito.Mockito.when; |
|
||||||
|
|
||||||
import tech.pegasys.pantheon.consensus.common.EpochManager; |
|
||||||
import tech.pegasys.pantheon.consensus.common.VoteTally; |
|
||||||
import tech.pegasys.pantheon.consensus.common.VoteType; |
|
||||||
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.chain.MutableBlockchain; |
|
||||||
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.util.bytes.BytesValue; |
|
||||||
|
|
||||||
import java.math.BigInteger; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Optional; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
|
|
||||||
public class IbftVoteTallyUpdaterTest { |
|
||||||
|
|
||||||
private static final long EPOCH_LENGTH = 30_000; |
|
||||||
public static final Signature INVALID_SEAL = |
|
||||||
Signature.create(BigInteger.ONE, BigInteger.ONE, (byte) 0); |
|
||||||
private final VoteTally voteTally = mock(VoteTally.class); |
|
||||||
private final MutableBlockchain blockchain = mock(MutableBlockchain.class); |
|
||||||
private final KeyPair proposerKeyPair = KeyPair.generate(); |
|
||||||
private final Address proposerAddress = |
|
||||||
Address.extract(Hash.hash(proposerKeyPair.getPublicKey().getEncodedBytes())); |
|
||||||
private final Address subject = Address.fromHexString("007f4a23ca00cd043d25c2888c1aa5688f81a344"); |
|
||||||
private final Address validator1 = |
|
||||||
Address.fromHexString("00dae27b350bae20c5652124af5d8b5cba001ec1"); |
|
||||||
|
|
||||||
private final IbftVoteTallyUpdater updater = |
|
||||||
new IbftVoteTallyUpdater(new EpochManager(EPOCH_LENGTH)); |
|
||||||
|
|
||||||
@Test |
|
||||||
public void voteTallyUpdatedWithVoteFromBlock() { |
|
||||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
|
||||||
headerBuilder.number(1); |
|
||||||
headerBuilder.nonce(VoteType.ADD.getNonceValue()); |
|
||||||
headerBuilder.coinbase(subject); |
|
||||||
addProposer(headerBuilder); |
|
||||||
final BlockHeader header = headerBuilder.buildHeader(); |
|
||||||
|
|
||||||
updater.updateForBlock(header, voteTally); |
|
||||||
|
|
||||||
verify(voteTally).addVote(proposerAddress, subject, VoteType.ADD); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void voteTallyNotUpdatedWhenBlockHasNoVoteSubject() { |
|
||||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
|
||||||
headerBuilder.number(1); |
|
||||||
headerBuilder.nonce(VoteType.ADD.getNonceValue()); |
|
||||||
headerBuilder.coinbase(Address.fromHexString("0000000000000000000000000000000000000000")); |
|
||||||
addProposer(headerBuilder); |
|
||||||
final BlockHeader header = headerBuilder.buildHeader(); |
|
||||||
|
|
||||||
updater.updateForBlock(header, voteTally); |
|
||||||
|
|
||||||
verifyZeroInteractions(voteTally); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void outstandingVotesDiscardedWhenEpochReached() { |
|
||||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
|
||||||
headerBuilder.number(EPOCH_LENGTH); |
|
||||||
headerBuilder.nonce(VoteType.ADD.getNonceValue()); |
|
||||||
headerBuilder.coinbase(Address.fromHexString("0000000000000000000000000000000000000000")); |
|
||||||
addProposer(headerBuilder); |
|
||||||
final BlockHeader header = headerBuilder.buildHeader(); |
|
||||||
|
|
||||||
updater.updateForBlock(header, voteTally); |
|
||||||
|
|
||||||
verify(voteTally).discardOutstandingVotes(); |
|
||||||
verifyNoMoreInteractions(voteTally); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void buildVoteTallyByExtractingValidatorsFromGenesisBlock() { |
|
||||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
|
||||||
headerBuilder.number(0); |
|
||||||
headerBuilder.nonce(VoteType.ADD.getNonceValue()); |
|
||||||
headerBuilder.coinbase(Address.fromHexString("0000000000000000000000000000000000000000")); |
|
||||||
addProposer(headerBuilder, asList(subject, validator1)); |
|
||||||
final BlockHeader header = headerBuilder.buildHeader(); |
|
||||||
|
|
||||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(EPOCH_LENGTH); |
|
||||||
when(blockchain.getBlockHeader(EPOCH_LENGTH)).thenReturn(Optional.of(header)); |
|
||||||
|
|
||||||
final VoteTally voteTally = updater.buildVoteTallyFromBlockchain(blockchain); |
|
||||||
assertThat(voteTally.getCurrentValidators()).containsExactly(subject, validator1); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void buildVoteTallyByExtractingValidatorsFromEpochBlock() { |
|
||||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
|
||||||
headerBuilder.number(EPOCH_LENGTH); |
|
||||||
headerBuilder.nonce(VoteType.ADD.getNonceValue()); |
|
||||||
headerBuilder.coinbase(Address.fromHexString("0000000000000000000000000000000000000000")); |
|
||||||
addProposer(headerBuilder, asList(subject, validator1)); |
|
||||||
final BlockHeader header = headerBuilder.buildHeader(); |
|
||||||
|
|
||||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(EPOCH_LENGTH); |
|
||||||
when(blockchain.getBlockHeader(EPOCH_LENGTH)).thenReturn(Optional.of(header)); |
|
||||||
|
|
||||||
final VoteTally voteTally = updater.buildVoteTallyFromBlockchain(blockchain); |
|
||||||
assertThat(voteTally.getCurrentValidators()).containsExactly(subject, validator1); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void addVotesFromBlocksAfterMostRecentEpoch() { |
|
||||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
|
||||||
headerBuilder.number(EPOCH_LENGTH); |
|
||||||
headerBuilder.nonce(VoteType.ADD.getNonceValue()); |
|
||||||
headerBuilder.coinbase(Address.fromHexString("0000000000000000000000000000000000000000")); |
|
||||||
addProposer(headerBuilder, singletonList(validator1)); |
|
||||||
final BlockHeader epochHeader = headerBuilder.buildHeader(); |
|
||||||
|
|
||||||
headerBuilder.number(EPOCH_LENGTH + 1); |
|
||||||
headerBuilder.coinbase(subject); |
|
||||||
final BlockHeader voteBlockHeader = headerBuilder.buildHeader(); |
|
||||||
|
|
||||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(EPOCH_LENGTH + 1); |
|
||||||
when(blockchain.getBlockHeader(EPOCH_LENGTH)).thenReturn(Optional.of(epochHeader)); |
|
||||||
when(blockchain.getBlockHeader(EPOCH_LENGTH + 1)).thenReturn(Optional.of(voteBlockHeader)); |
|
||||||
|
|
||||||
final VoteTally voteTally = updater.buildVoteTallyFromBlockchain(blockchain); |
|
||||||
assertThat(voteTally.getCurrentValidators()).containsExactly(subject, validator1); |
|
||||||
} |
|
||||||
|
|
||||||
private void addProposer(final BlockHeaderTestFixture builder) { |
|
||||||
addProposer(builder, singletonList(proposerAddress)); |
|
||||||
} |
|
||||||
|
|
||||||
private void addProposer(final BlockHeaderTestFixture builder, final List<Address> validators) { |
|
||||||
|
|
||||||
final IbftExtraData initialIbftExtraData = |
|
||||||
new IbftExtraData( |
|
||||||
BytesValue.wrap(new byte[IbftExtraData.EXTRA_VANITY_LENGTH]), |
|
||||||
emptyList(), |
|
||||||
INVALID_SEAL, |
|
||||||
validators); |
|
||||||
|
|
||||||
builder.extraData(initialIbftExtraData.encode()); |
|
||||||
final BlockHeader header = builder.buildHeader(); |
|
||||||
final Hash proposerSealHash = |
|
||||||
IbftBlockHashing.calculateDataHashForProposerSeal(header, initialIbftExtraData); |
|
||||||
|
|
||||||
final Signature proposerSignature = SECP256K1.sign(proposerSealHash, proposerKeyPair); |
|
||||||
|
|
||||||
final IbftExtraData proposedData = |
|
||||||
new IbftExtraData( |
|
||||||
BytesValue.wrap(new byte[IbftExtraData.EXTRA_VANITY_LENGTH]), |
|
||||||
singletonList(proposerSignature), |
|
||||||
proposerSignature, |
|
||||||
validators); |
|
||||||
|
|
||||||
final Hash headerHashForCommitters = |
|
||||||
IbftBlockHashing.calculateDataHashForCommittedSeal(header, proposedData); |
|
||||||
final Signature proposerAsCommitterSignature = |
|
||||||
SECP256K1.sign(headerHashForCommitters, proposerKeyPair); |
|
||||||
|
|
||||||
final IbftExtraData sealedData = |
|
||||||
new IbftExtraData( |
|
||||||
BytesValue.wrap(new byte[IbftExtraData.EXTRA_VANITY_LENGTH]), |
|
||||||
singletonList(proposerAsCommitterSignature), |
|
||||||
proposerSignature, |
|
||||||
validators); |
|
||||||
|
|
||||||
builder.extraData(sealedData.encode()); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,55 @@ |
|||||||
|
/* |
||||||
|
* 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.ibftlegacy; |
||||||
|
|
||||||
|
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.BlockHeaderTestFixture; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||||
|
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class TestHelpers { |
||||||
|
|
||||||
|
public static BlockHeader createIbftSignedBlockHeader( |
||||||
|
final BlockHeaderTestFixture blockHeaderBuilder, |
||||||
|
final KeyPair signer, |
||||||
|
final List<Address> validators) { |
||||||
|
|
||||||
|
final IbftExtraData unsignedExtraData = |
||||||
|
new IbftExtraData(BytesValue.wrap(new byte[32]), Collections.emptyList(), null, validators); |
||||||
|
blockHeaderBuilder.extraData(unsignedExtraData.encode()); |
||||||
|
|
||||||
|
final Hash signingHash = |
||||||
|
IbftBlockHashing.calculateDataHashForProposerSeal( |
||||||
|
blockHeaderBuilder.buildHeader(), unsignedExtraData); |
||||||
|
|
||||||
|
final Signature proposerSignature = SECP256K1.sign(signingHash, signer); |
||||||
|
|
||||||
|
final IbftExtraData signedExtraData = |
||||||
|
new IbftExtraData( |
||||||
|
unsignedExtraData.getVanityData(), |
||||||
|
unsignedExtraData.getSeals(), |
||||||
|
proposerSignature, |
||||||
|
unsignedExtraData.getValidators()); |
||||||
|
|
||||||
|
blockHeaderBuilder.extraData(signedExtraData.encode()); |
||||||
|
|
||||||
|
return blockHeaderBuilder.buildHeader(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue