From c2e98152e0220df7700e931af296d801d143748c Mon Sep 17 00:00:00 2001
From: tmohay <37158202+rain-on@users.noreply.github.com>
Date: Fri, 16 Nov 2018 13:19:13 +1100
Subject: [PATCH] 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.
---
.../clique/CliqueVoteTallyUpdater.java | 76 -------
.../clique/CliqueVotingBlockInterface.java | 70 +++++++
.../consensus/clique/VoteTallyCache.java | 5 +-
.../CoinbaseHeaderValidationRule.java | 4 +-
.../jsonrpc/CliqueJsonRpcMethodsFactory.java | 8 +-
.../CliqueVotingBlockInterfaceTest.java | 126 +++++++++++
.../consensus/clique/VoteTallyCacheTest.java | 7 +-
consensus/common/build.gradle | 1 +
.../pantheon/consensus/common/CastVote.java | 67 ++++++
.../common/ValidatorVotePolarity.java | 32 +++
.../consensus/common/VoteBlockInterface.java | 30 +++
.../pantheon/consensus/common/VoteTally.java | 33 ++-
.../consensus/common/VoteTallyUpdater.java} | 30 +--
.../consensus/common/VoteProposerTest.java | 12 +-
.../consensus/common/VoteTallyTest.java | 152 +++++++-------
.../common/VoteTallyUpdaterTest.java} | 92 +++-----
.../consensus/ibft/IbftBlockImporter.java | 1 +
.../consensus/ibft/VoteTallyUpdater.java | 35 ----
.../consensus/ibft/IbftBlockImporterTest.java | 1 +
.../IbftLegacyVotingBlockInterface.java | 70 +++++++
.../ibftlegacy/IbftProtocolSchedule.java | 3 +-
.../IbftLegacyVotingBlockInterfaceTest.java | 127 +++++++++++
.../ibftlegacy/IbftVoteTallyUpdaterTest.java | 198 ------------------
.../consensus/ibftlegacy/TestHelpers.java | 55 +++++
.../controller/CliquePantheonController.java | 7 +-
.../controller/IbftPantheonController.java | 6 +-
26 files changed, 738 insertions(+), 510 deletions(-)
delete mode 100644 consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueVoteTallyUpdater.java
create mode 100644 consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueVotingBlockInterface.java
create mode 100644 consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueVotingBlockInterfaceTest.java
create mode 100644 consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/CastVote.java
create mode 100644 consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/ValidatorVotePolarity.java
create mode 100644 consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteBlockInterface.java
rename consensus/{ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftVoteTallyUpdater.java => common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteTallyUpdater.java} (69%)
rename consensus/{clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueVoteTallyUpdaterTest.java => common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteTallyUpdaterTest.java} (60%)
delete mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/VoteTallyUpdater.java
create mode 100644 consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftLegacyVotingBlockInterface.java
create mode 100644 consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftLegacyVotingBlockInterfaceTest.java
delete mode 100644 consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftVoteTallyUpdaterTest.java
create mode 100644 consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/TestHelpers.java
diff --git a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueVoteTallyUpdater.java b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueVoteTallyUpdater.java
deleted file mode 100644
index dd7ce79698..0000000000
--- a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueVoteTallyUpdater.java
+++ /dev/null
@@ -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
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());
- }
- }
-}
diff --git a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueVotingBlockInterface.java b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueVotingBlockInterface.java
new file mode 100644
index 0000000000..5efff4cbc0
--- /dev/null
+++ b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueVotingBlockInterface.java
@@ -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 voteToValue =
+ ImmutableBiMap.of(
+ ValidatorVotePolarity.ADD, 0xFFFFFFFFFFFFFFFFL,
+ ValidatorVotePolarity.DROP, 0x0L);
+
+ @Override
+ public Optional 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 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 validatorsInBlock(final BlockHeader header) {
+ return CliqueExtraData.decode(header.getExtraData()).getValidators();
+ }
+}
diff --git a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/VoteTallyCache.java b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/VoteTallyCache.java
index cb1d5794c5..02ad1012ea 100644
--- a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/VoteTallyCache.java
+++ b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/VoteTallyCache.java
@@ -16,6 +16,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import tech.pegasys.pantheon.consensus.common.EpochManager;
import tech.pegasys.pantheon.consensus.common.VoteTally;
+import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater;
import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
@@ -32,14 +33,14 @@ public class VoteTallyCache {
private final Blockchain blockchain;
private final EpochManager epochManager;
- private final CliqueVoteTallyUpdater voteTallyUpdater;
+ private final VoteTallyUpdater voteTallyUpdater;
private final Cache voteTallyCache =
CacheBuilder.newBuilder().maximumSize(100).build();
public VoteTallyCache(
final Blockchain blockchain,
- final CliqueVoteTallyUpdater voteTallyUpdater,
+ final VoteTallyUpdater voteTallyUpdater,
final EpochManager epochManager) {
checkNotNull(blockchain);
checkNotNull(voteTallyUpdater);
diff --git a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/headervalidationrules/CoinbaseHeaderValidationRule.java b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/headervalidationrules/CoinbaseHeaderValidationRule.java
index 5b271aa40b..d055de2912 100644
--- a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/headervalidationrules/CoinbaseHeaderValidationRule.java
+++ b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/headervalidationrules/CoinbaseHeaderValidationRule.java
@@ -12,7 +12,7 @@
*/
package tech.pegasys.pantheon.consensus.clique.headervalidationrules;
-import tech.pegasys.pantheon.consensus.clique.CliqueVoteTallyUpdater;
+import tech.pegasys.pantheon.consensus.clique.CliqueVotingBlockInterface;
import tech.pegasys.pantheon.consensus.common.EpochManager;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.mainnet.DetachedBlockHeaderValidationRule;
@@ -30,7 +30,7 @@ public class CoinbaseHeaderValidationRule implements DetachedBlockHeaderValidati
// are allowed to be cast on epoch blocks
public boolean validate(final BlockHeader header, final BlockHeader parent) {
if (epochManager.isEpochBlock(header.getNumber())) {
- return header.getCoinbase().equals(CliqueVoteTallyUpdater.NO_VOTE_SUBJECT);
+ return header.getCoinbase().equals(CliqueVotingBlockInterface.NO_VOTE_SUBJECT);
}
return true;
}
diff --git a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/jsonrpc/CliqueJsonRpcMethodsFactory.java b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/jsonrpc/CliqueJsonRpcMethodsFactory.java
index e1967cde00..c5d07e4b51 100644
--- a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/jsonrpc/CliqueJsonRpcMethodsFactory.java
+++ b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/jsonrpc/CliqueJsonRpcMethodsFactory.java
@@ -13,7 +13,7 @@
package tech.pegasys.pantheon.consensus.clique.jsonrpc;
import tech.pegasys.pantheon.consensus.clique.CliqueContext;
-import tech.pegasys.pantheon.consensus.clique.CliqueVoteTallyUpdater;
+import tech.pegasys.pantheon.consensus.clique.CliqueVotingBlockInterface;
import tech.pegasys.pantheon.consensus.clique.VoteTallyCache;
import tech.pegasys.pantheon.consensus.clique.jsonrpc.methods.CliqueGetSigners;
import tech.pegasys.pantheon.consensus.clique.jsonrpc.methods.CliqueGetSignersAtHash;
@@ -22,6 +22,7 @@ import tech.pegasys.pantheon.consensus.clique.jsonrpc.methods.Discard;
import tech.pegasys.pantheon.consensus.clique.jsonrpc.methods.Propose;
import tech.pegasys.pantheon.consensus.common.EpochManager;
import tech.pegasys.pantheon.consensus.common.VoteProposer;
+import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.db.WorldStateArchive;
@@ -70,7 +71,8 @@ public class CliqueJsonRpcMethodsFactory {
private VoteTallyCache createVoteTallyCache(
final ProtocolContext context, final MutableBlockchain blockchain) {
final EpochManager epochManager = context.getConsensusState().getEpochManager();
- final CliqueVoteTallyUpdater cliqueVoteTallyUpdater = new CliqueVoteTallyUpdater(epochManager);
- return new VoteTallyCache(blockchain, cliqueVoteTallyUpdater, epochManager);
+ final VoteTallyUpdater voteTallyUpdater =
+ new VoteTallyUpdater(epochManager, new CliqueVotingBlockInterface());
+ return new VoteTallyCache(blockchain, voteTallyUpdater, epochManager);
}
}
diff --git a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueVotingBlockInterfaceTest.java b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueVotingBlockInterfaceTest.java
new file mode 100644
index 0000000000..8f9c92e1e9
--- /dev/null
+++ b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueVotingBlockInterfaceTest.java
@@ -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 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 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 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 extractedValidators = blockInterface.validatorsInBlock(header);
+
+ assertThat(extractedValidators).isEqualTo(validatorList);
+ }
+}
diff --git a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/VoteTallyCacheTest.java b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/VoteTallyCacheTest.java
index cee3be4686..d767d25239 100644
--- a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/VoteTallyCacheTest.java
+++ b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/VoteTallyCacheTest.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import tech.pegasys.pantheon.consensus.common.EpochManager;
+import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.AddressHelpers;
@@ -79,7 +80,7 @@ public class VoteTallyCacheTest {
@Test
public void parentBlockVoteTallysAreCachedWhenChildVoteTallyRequested() {
- final CliqueVoteTallyUpdater tallyUpdater = mock(CliqueVoteTallyUpdater.class);
+ final VoteTallyUpdater tallyUpdater = mock(VoteTallyUpdater.class);
final VoteTallyCache cache =
new VoteTallyCache(blockChain, tallyUpdater, new EpochManager(30_000));
@@ -105,7 +106,7 @@ public class VoteTallyCacheTest {
@Test
public void exceptionThrownIfNoParentBlockExists() {
- final CliqueVoteTallyUpdater tallyUpdater = mock(CliqueVoteTallyUpdater.class);
+ final VoteTallyUpdater tallyUpdater = mock(VoteTallyUpdater.class);
final VoteTallyCache cache =
new VoteTallyCache(blockChain, tallyUpdater, new EpochManager(30_000));
@@ -119,7 +120,7 @@ public class VoteTallyCacheTest {
@Test
public void walkBackStopsWhenACachedVoteTallyIsFound() {
- final CliqueVoteTallyUpdater tallyUpdater = mock(CliqueVoteTallyUpdater.class);
+ final VoteTallyUpdater tallyUpdater = mock(VoteTallyUpdater.class);
final VoteTallyCache cache =
new VoteTallyCache(blockChain, tallyUpdater, new EpochManager(30_000));
diff --git a/consensus/common/build.gradle b/consensus/common/build.gradle
index 4a297ed24c..0fdf5ec215 100644
--- a/consensus/common/build.gradle
+++ b/consensus/common/build.gradle
@@ -30,6 +30,7 @@ dependencies {
implementation project(':util')
implementation 'com.google.guava:guava'
+ testImplementation project(':crypto')
testImplementation project( path: ':ethereum:core', configuration: 'testSupportArtifacts')
testImplementation 'junit:junit'
testImplementation "org.assertj:assertj-core"
diff --git a/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/CastVote.java b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/CastVote.java
new file mode 100644
index 0000000000..6d58017559
--- /dev/null
+++ b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/CastVote.java
@@ -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);
+ }
+}
diff --git a/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/ValidatorVotePolarity.java b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/ValidatorVotePolarity.java
new file mode 100644
index 0000000000..ce2cb5d9d0
--- /dev/null
+++ b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/ValidatorVotePolarity.java
@@ -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);
+ }
+}
diff --git a/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteBlockInterface.java b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteBlockInterface.java
new file mode 100644
index 0000000000..b8e7050027
--- /dev/null
+++ b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteBlockInterface.java
@@ -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 extractVoteFromHeader(final BlockHeader header);
+
+ BlockHeaderBuilder insertVoteToHeaderBuilder(
+ final BlockHeaderBuilder builder, final Optional vote);
+
+ List validatorsInBlock(final BlockHeader header);
+}
diff --git a/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteTally.java b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteTally.java
index d86d57c8d4..2f861e1885 100644
--- a/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteTally.java
+++ b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteTally.java
@@ -52,35 +52,32 @@ public class VoteTally implements ValidatorProvider {
* Add a vote to the current tally. The current validator list will be updated if this vote takes
* the tally past the required votes to approve the change.
*
- * @param proposer the address of the validator casting the vote via block proposal
- * @param subject the validator the vote is about
- * @param validatorVote the type of vote, either add or drop
+ * @param castVote The vote which was cast in a block header.
*/
- public void addVote(
- final Address proposer, final Address subject, final ValidatorVote validatorVote) {
+ public void addVote(final CastVote castVote) {
final Set addVotesForSubject =
- addVotesBySubject.computeIfAbsent(subject, target -> new HashSet<>());
+ addVotesBySubject.computeIfAbsent(castVote.getRecipient(), target -> new HashSet<>());
final Set removeVotesForSubject =
- removeVotesBySubject.computeIfAbsent(subject, target -> new HashSet<>());
+ removeVotesBySubject.computeIfAbsent(castVote.getRecipient(), target -> new HashSet<>());
- if (validatorVote.isAddVote()) {
- addVotesForSubject.add(proposer);
- removeVotesForSubject.remove(proposer);
+ if (castVote.getVotePolarity().isAddVote()) {
+ addVotesForSubject.add(castVote.getProposer());
+ removeVotesForSubject.remove(castVote.getProposer());
} else {
- removeVotesForSubject.add(proposer);
- addVotesForSubject.remove(proposer);
+ removeVotesForSubject.add(castVote.getProposer());
+ addVotesForSubject.remove(castVote.getProposer());
}
final int validatorLimit = validatorLimit();
if (addVotesForSubject.size() >= validatorLimit) {
- currentValidators.add(subject);
- discardOutstandingVotesFor(subject);
+ currentValidators.add(castVote.getRecipient());
+ discardOutstandingVotesFor(castVote.getRecipient());
}
if (removeVotesForSubject.size() >= validatorLimit) {
- currentValidators.remove(subject);
- discardOutstandingVotesFor(subject);
- addVotesBySubject.values().forEach(votes -> votes.remove(subject));
- removeVotesBySubject.values().forEach(votes -> votes.remove(subject));
+ currentValidators.remove(castVote.getRecipient());
+ discardOutstandingVotesFor(castVote.getRecipient());
+ addVotesBySubject.values().forEach(votes -> votes.remove(castVote.getRecipient()));
+ removeVotesBySubject.values().forEach(votes -> votes.remove(castVote.getRecipient()));
}
}
diff --git a/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftVoteTallyUpdater.java b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteTallyUpdater.java
similarity index 69%
rename from consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftVoteTallyUpdater.java
rename to consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteTallyUpdater.java
index 928c26284b..393a20fbf3 100644
--- a/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftVoteTallyUpdater.java
+++ b/consensus/common/src/main/java/tech/pegasys/pantheon/consensus/common/VoteTallyUpdater.java
@@ -10,18 +10,14 @@
* 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;
+package tech.pegasys.pantheon.consensus.common;
-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.consensus.ibft.VoteTallyUpdater;
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 java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -30,15 +26,17 @@ import org.apache.logging.log4j.Logger;
* Provides the logic to extract vote tally state from the blockchain and update it as blocks are
* added.
*/
-public class IbftVoteTallyUpdater implements VoteTallyUpdater {
+public class VoteTallyUpdater {
private static final Logger LOG = LogManager.getLogger();
- private static final Address NO_VOTE_SUBJECT = Address.wrap(BytesValue.wrap(new byte[20]));
private final EpochManager epochManager;
+ private final VoteBlockInterface blockInterface;
- public IbftVoteTallyUpdater(final EpochManager epochManager) {
+ public VoteTallyUpdater(
+ final EpochManager epochManager, final VoteBlockInterface blockInterface) {
this.epochManager = epochManager;
+ this.blockInterface = blockInterface;
}
/**
@@ -47,14 +45,12 @@ public class IbftVoteTallyUpdater implements VoteTallyUpdater {
* @param blockchain the blockchain to load the current state from
* @return a VoteTally reflecting the state of the blockchain head
*/
- @Override
public VoteTally buildVoteTallyFromBlockchain(final Blockchain blockchain) {
final long chainHeadBlockNumber = blockchain.getChainHeadBlockNumber();
final long epochBlockNumber = epochManager.getLastEpochBlock(chainHeadBlockNumber);
LOG.info("Loading validator voting state starting from block {}", epochBlockNumber);
final BlockHeader epochBlock = blockchain.getBlockHeader(epochBlockNumber).get();
- final List initialValidators =
- IbftExtraData.decode(epochBlock.getExtraData()).getValidators();
+ final List initialValidators = blockInterface.validatorsInBlock(epochBlock);
final VoteTally voteTally = new VoteTally(initialValidators);
for (long blockNumber = epochBlockNumber + 1;
blockNumber <= chainHeadBlockNumber;
@@ -70,18 +66,12 @@ public class IbftVoteTallyUpdater implements VoteTallyUpdater {
* @param header the header of the block being added
* @param voteTally the vote tally to update
*/
- @Override
public void updateForBlock(final BlockHeader header, final VoteTally voteTally) {
- final Address candidate = header.getCoinbase();
if (epochManager.isEpochBlock(header.getNumber())) {
voteTally.discardOutstandingVotes();
return;
}
-
- if (!candidate.equals(NO_VOTE_SUBJECT)) {
- final IbftExtraData ibftExtraData = IbftExtraData.decode(header.getExtraData());
- final Address proposer = IbftBlockHashing.recoverProposerAddress(header, ibftExtraData);
- voteTally.addVote(proposer, candidate, VoteType.fromNonce(header.getNonce()).get());
- }
+ final Optional vote = blockInterface.extractVoteFromHeader(header);
+ vote.ifPresent(voteTally::addVote);
}
}
diff --git a/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteProposerTest.java b/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteProposerTest.java
index c31eb027f5..723cb2e94a 100644
--- a/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteProposerTest.java
+++ b/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteProposerTest.java
@@ -13,8 +13,8 @@
package tech.pegasys.pantheon.consensus.common;
import static org.assertj.core.api.Assertions.assertThat;
-import static tech.pegasys.pantheon.consensus.common.VoteType.ADD;
-import static tech.pegasys.pantheon.consensus.common.VoteType.DROP;
+import static tech.pegasys.pantheon.consensus.common.ValidatorVotePolarity.ADD;
+import static tech.pegasys.pantheon.consensus.common.ValidatorVotePolarity.DROP;
import tech.pegasys.pantheon.ethereum.core.Address;
@@ -158,7 +158,7 @@ public class VoteProposerTest {
proposer.drop(a1);
final VoteTally tally = new VoteTally(Arrays.asList(a2, a3));
- tally.addVote(localAddress, a1, ADD);
+ tally.addVote(new CastVote(ADD, localAddress, a1));
assertThat(proposer.getVote(localAddress, tally))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.DROP)));
@@ -174,7 +174,7 @@ public class VoteProposerTest {
proposer.auth(a1);
final VoteTally tally = new VoteTally(Arrays.asList(a2, a3));
- tally.addVote(localAddress, a1, DROP);
+ tally.addVote(new CastVote(DROP, localAddress, a1));
assertThat(proposer.getVote(localAddress, tally))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.ADD)));
@@ -188,7 +188,7 @@ public class VoteProposerTest {
final Address a3 = Address.fromHexString("3");
final VoteTally tally = new VoteTally(Arrays.asList(a1, a2, a3));
- tally.addVote(localAddress, a1, ADD);
+ tally.addVote(new CastVote(ADD, localAddress, a1));
proposer.drop(a1);
@@ -204,7 +204,7 @@ public class VoteProposerTest {
final Address a3 = Address.fromHexString("3");
final VoteTally tally = new VoteTally(Arrays.asList(a1, a2, a3));
- tally.addVote(localAddress, a1, DROP);
+ tally.addVote(new CastVote(DROP, localAddress, a1));
proposer.auth(a1);
diff --git a/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteTallyTest.java b/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteTallyTest.java
index 459975b010..65db65ab1f 100644
--- a/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteTallyTest.java
+++ b/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteTallyTest.java
@@ -15,6 +15,8 @@ package tech.pegasys.pantheon.consensus.common;
import static java.util.Arrays.asList;
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.ethereum.core.Address;
@@ -36,8 +38,8 @@ public class VoteTallyTest {
@Test
public void validatorsAreNotAddedBeforeRequiredVoteCountReached() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator5, VoteType.ADD);
- voteTally.addVote(validator2, validator5, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator1, validator5));
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
@@ -46,9 +48,9 @@ public class VoteTallyTest {
@Test
public void validatorAddedToListWhenMoreThanHalfOfProposersVoteToAdd() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator5, VoteType.ADD);
- voteTally.addVote(validator2, validator5, VoteType.ADD);
- voteTally.addVote(validator3, validator5, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator1, validator5));
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
+ voteTally.addVote(new CastVote(ADD, validator3, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4, validator5);
@@ -58,9 +60,9 @@ public class VoteTallyTest {
public void validatorsAreAddedInCorrectOrder() {
final VoteTally voteTally =
new VoteTally(asList(validator1, validator2, validator3, validator5));
- voteTally.addVote(validator1, validator4, VoteType.ADD);
- voteTally.addVote(validator2, validator4, VoteType.ADD);
- voteTally.addVote(validator3, validator4, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator1, validator4));
+ voteTally.addVote(new CastVote(ADD, validator2, validator4));
+ voteTally.addVote(new CastVote(ADD, validator3, validator4));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4, validator5);
@@ -69,9 +71,9 @@ public class VoteTallyTest {
@Test
public void duplicateVotesFromSameProposerAreIgnored() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator5, VoteType.ADD);
- voteTally.addVote(validator2, validator5, VoteType.ADD);
- voteTally.addVote(validator2, validator5, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator1, validator5));
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
@@ -80,10 +82,10 @@ public class VoteTallyTest {
@Test
public void proposerChangingAddVoteToDropBeforeLimitReachedDiscardsAddVote() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator5, VoteType.ADD);
- voteTally.addVote(validator1, validator5, VoteType.DROP);
- voteTally.addVote(validator2, validator5, VoteType.ADD);
- voteTally.addVote(validator3, validator5, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator1, validator5));
+ voteTally.addVote(new CastVote(DROP, validator1, validator5));
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
+ voteTally.addVote(new CastVote(ADD, validator3, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
@@ -92,10 +94,10 @@ public class VoteTallyTest {
@Test
public void proposerChangingAddVoteToDropAfterLimitReachedPreservesAddVote() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator5, VoteType.ADD);
- voteTally.addVote(validator2, validator5, VoteType.ADD);
- voteTally.addVote(validator3, validator5, VoteType.ADD);
- voteTally.addVote(validator1, validator5, VoteType.DROP);
+ voteTally.addVote(new CastVote(ADD, validator1, validator5));
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
+ voteTally.addVote(new CastVote(ADD, validator3, validator5));
+ voteTally.addVote(new CastVote(DROP, validator1, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4, validator5);
@@ -105,23 +107,23 @@ public class VoteTallyTest {
public void clearVotesAboutAValidatorWhenItIsAdded() {
final VoteTally voteTally = fourValidators();
// Vote to add validator5
- voteTally.addVote(validator1, validator5, VoteType.ADD);
- voteTally.addVote(validator2, validator5, VoteType.ADD);
- voteTally.addVote(validator3, validator5, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator1, validator5));
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
+ voteTally.addVote(new CastVote(ADD, validator3, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4, validator5);
// Then vote it back out
- voteTally.addVote(validator2, validator5, VoteType.DROP);
- voteTally.addVote(validator3, validator5, VoteType.DROP);
- voteTally.addVote(validator4, validator5, VoteType.DROP);
+ voteTally.addVote(new CastVote(DROP, validator2, validator5));
+ voteTally.addVote(new CastVote(DROP, validator3, validator5));
+ voteTally.addVote(new CastVote(DROP, validator4, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
// And then start voting to add it back in, but validator1's vote should have been discarded
- voteTally.addVote(validator2, validator5, VoteType.ADD);
- voteTally.addVote(validator3, validator5, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
+ voteTally.addVote(new CastVote(ADD, validator3, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
}
@@ -129,7 +131,7 @@ public class VoteTallyTest {
@Test
public void requiresASingleVoteWhenThereIsOnlyOneValidator() {
final VoteTally voteTally = new VoteTally(singletonList(validator1));
- voteTally.addVote(validator1, validator2, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator1, validator2));
assertThat(voteTally.getCurrentValidators()).containsExactly(validator1, validator2);
}
@@ -137,11 +139,11 @@ public class VoteTallyTest {
@Test
public void requiresTwoVotesWhenThereAreTwoValidators() {
final VoteTally voteTally = new VoteTally(asList(validator1, validator2));
- voteTally.addVote(validator1, validator3, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator1, validator3));
assertThat(voteTally.getCurrentValidators()).containsExactly(validator1, validator2);
- voteTally.addVote(validator2, validator3, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator2, validator3));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3);
}
@@ -149,10 +151,10 @@ public class VoteTallyTest {
@Test
public void resetVotes() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator5, VoteType.ADD);
- voteTally.addVote(validator2, validator5, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator1, validator5));
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
voteTally.discardOutstandingVotes();
- voteTally.addVote(validator3, validator5, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator3, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
@@ -161,8 +163,8 @@ public class VoteTallyTest {
@Test
public void validatorsAreNotRemovedBeforeRequiredVoteCountReached() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator4, VoteType.DROP);
- voteTally.addVote(validator2, validator4, VoteType.DROP);
+ voteTally.addVote(new CastVote(DROP, validator1, validator4));
+ voteTally.addVote(new CastVote(DROP, validator2, validator4));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
@@ -171,9 +173,9 @@ public class VoteTallyTest {
@Test
public void validatorRemovedFromListWhenMoreThanHalfOfProposersVoteToDrop() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator4, VoteType.DROP);
- voteTally.addVote(validator2, validator4, VoteType.DROP);
- voteTally.addVote(validator3, validator4, VoteType.DROP);
+ voteTally.addVote(new CastVote(DROP, validator1, validator4));
+ voteTally.addVote(new CastVote(DROP, validator2, validator4));
+ voteTally.addVote(new CastVote(DROP, validator3, validator4));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3);
@@ -182,9 +184,9 @@ public class VoteTallyTest {
@Test
public void validatorsAreInCorrectOrderAfterRemoval() {
final VoteTally voteTally = new VoteTally(asList(validator1, validator2, validator4));
- voteTally.addVote(validator1, validator3, VoteType.DROP);
- voteTally.addVote(validator2, validator3, VoteType.DROP);
- voteTally.addVote(validator4, validator3, VoteType.DROP);
+ voteTally.addVote(new CastVote(DROP, validator1, validator3));
+ voteTally.addVote(new CastVote(DROP, validator2, validator3));
+ voteTally.addVote(new CastVote(DROP, validator4, validator3));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator4);
@@ -193,9 +195,9 @@ public class VoteTallyTest {
@Test
public void duplicateDropVotesFromSameProposerAreIgnored() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator4, VoteType.DROP);
- voteTally.addVote(validator2, validator4, VoteType.DROP);
- voteTally.addVote(validator2, validator4, VoteType.DROP);
+ voteTally.addVote(new CastVote(DROP, validator1, validator4));
+ voteTally.addVote(new CastVote(DROP, validator2, validator4));
+ voteTally.addVote(new CastVote(DROP, validator2, validator4));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
@@ -204,10 +206,10 @@ public class VoteTallyTest {
@Test
public void proposerChangingDropVoteToAddBeforeLimitReachedDiscardsDropVote() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator4, VoteType.DROP);
- voteTally.addVote(validator1, validator4, VoteType.ADD);
- voteTally.addVote(validator2, validator4, VoteType.DROP);
- voteTally.addVote(validator3, validator4, VoteType.DROP);
+ voteTally.addVote(new CastVote(DROP, validator1, validator4));
+ voteTally.addVote(new CastVote(ADD, validator1, validator4));
+ voteTally.addVote(new CastVote(DROP, validator2, validator4));
+ voteTally.addVote(new CastVote(DROP, validator3, validator4));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
@@ -216,10 +218,10 @@ public class VoteTallyTest {
@Test
public void proposerChangingDropVoteToAddAfterLimitReachedPreservesDropVote() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator4, VoteType.DROP);
- voteTally.addVote(validator2, validator4, VoteType.DROP);
- voteTally.addVote(validator3, validator4, VoteType.DROP);
- voteTally.addVote(validator1, validator4, VoteType.ADD);
+ voteTally.addVote(new CastVote(DROP, validator1, validator4));
+ voteTally.addVote(new CastVote(DROP, validator2, validator4));
+ voteTally.addVote(new CastVote(DROP, validator3, validator4));
+ voteTally.addVote(new CastVote(ADD, validator1, validator4));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3);
@@ -228,19 +230,19 @@ public class VoteTallyTest {
@Test
public void removedValidatorsVotesAreDiscarded() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator4, validator5, VoteType.ADD);
- voteTally.addVote(validator4, validator3, VoteType.DROP);
+ voteTally.addVote(new CastVote(ADD, validator4, validator5));
+ voteTally.addVote(new CastVote(DROP, validator4, validator3));
- voteTally.addVote(validator1, validator4, VoteType.DROP);
- voteTally.addVote(validator2, validator4, VoteType.DROP);
- voteTally.addVote(validator3, validator4, VoteType.DROP);
+ voteTally.addVote(new CastVote(DROP, validator1, validator4));
+ voteTally.addVote(new CastVote(DROP, validator2, validator4));
+ voteTally.addVote(new CastVote(DROP, validator3, validator4));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3);
// Now adding only requires 2 votes (>50% of the 3 remaining validators)
// but validator4's vote no longer counts
- voteTally.addVote(validator1, validator5, VoteType.ADD);
- voteTally.addVote(validator1, validator3, VoteType.DROP);
+ voteTally.addVote(new CastVote(ADD, validator1, validator5));
+ voteTally.addVote(new CastVote(DROP, validator1, validator3));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3);
@@ -251,23 +253,23 @@ public class VoteTallyTest {
final VoteTally voteTally =
new VoteTally(asList(validator1, validator2, validator3, validator4, validator5));
// Vote to remove validator5
- voteTally.addVote(validator1, validator5, VoteType.DROP);
- voteTally.addVote(validator2, validator5, VoteType.DROP);
- voteTally.addVote(validator3, validator5, VoteType.DROP);
+ voteTally.addVote(new CastVote(DROP, validator1, validator5));
+ voteTally.addVote(new CastVote(DROP, validator2, validator5));
+ voteTally.addVote(new CastVote(DROP, validator3, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
// Then vote it back in
- voteTally.addVote(validator2, validator5, VoteType.ADD);
- voteTally.addVote(validator3, validator5, VoteType.ADD);
- voteTally.addVote(validator4, validator5, VoteType.ADD);
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
+ voteTally.addVote(new CastVote(ADD, validator3, validator5));
+ voteTally.addVote(new CastVote(ADD, validator4, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4, validator5);
// And then start voting to drop it again, but validator1's vote should have been discarded
- voteTally.addVote(validator2, validator5, VoteType.DROP);
- voteTally.addVote(validator3, validator5, VoteType.DROP);
+ voteTally.addVote(new CastVote(DROP, validator2, validator5));
+ voteTally.addVote(new CastVote(DROP, validator3, validator5));
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4, validator5);
}
@@ -275,25 +277,25 @@ public class VoteTallyTest {
@Test
public void trackMultipleOngoingVotesIndependently() {
final VoteTally voteTally = fourValidators();
- voteTally.addVote(validator1, validator5, VoteType.ADD);
- voteTally.addVote(validator1, validator3, VoteType.DROP);
+ voteTally.addVote(new CastVote(ADD, validator1, validator5));
+ voteTally.addVote(new CastVote(DROP, validator1, validator3));
- voteTally.addVote(validator2, validator5, VoteType.ADD);
- voteTally.addVote(validator2, validator1, VoteType.DROP);
+ voteTally.addVote(new CastVote(ADD, validator2, validator5));
+ voteTally.addVote(new CastVote(DROP, validator2, validator1));
// Neither vote has enough votes to complete.
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4);
- voteTally.addVote(validator3, validator5, VoteType.ADD);
- voteTally.addVote(validator3, validator1, VoteType.DROP);
+ voteTally.addVote(new CastVote(ADD, validator3, validator5));
+ voteTally.addVote(new CastVote(DROP, validator3, validator1));
// Validator 5 now has 3 votes and is added
assertThat(voteTally.getCurrentValidators())
.containsExactly(validator1, validator2, validator3, validator4, validator5);
- voteTally.addVote(validator4, validator5, VoteType.ADD);
- voteTally.addVote(validator4, validator1, VoteType.DROP);
+ voteTally.addVote(new CastVote(ADD, validator4, validator5));
+ voteTally.addVote(new CastVote(DROP, validator4, validator1));
// Validator 1 now gets dropped.
assertThat(voteTally.getCurrentValidators())
diff --git a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueVoteTallyUpdaterTest.java b/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteTallyUpdaterTest.java
similarity index 60%
rename from consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueVoteTallyUpdaterTest.java
rename to consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteTallyUpdaterTest.java
index 7c36dc5a91..8cf8bffc8f 100644
--- a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueVoteTallyUpdaterTest.java
+++ b/consensus/common/src/test/java/tech/pegasys/pantheon/consensus/common/VoteTallyUpdaterTest.java
@@ -10,41 +10,32 @@
* 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;
+package tech.pegasys.pantheon.consensus.common;
import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
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 static tech.pegasys.pantheon.consensus.common.ValidatorVotePolarity.ADD;
-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 CliqueVoteTallyUpdaterTest {
+public class VoteTallyUpdaterTest {
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();
@@ -54,30 +45,31 @@ public class CliqueVoteTallyUpdaterTest {
private final Address validator1 =
Address.fromHexString("00dae27b350bae20c5652124af5d8b5cba001ec1");
- private final CliqueVoteTallyUpdater updater =
- new CliqueVoteTallyUpdater(new EpochManager(EPOCH_LENGTH));
+ private final VoteBlockInterface serialiser = mock(VoteBlockInterface.class);
+
+ private final VoteTallyUpdater updater =
+ new VoteTallyUpdater(new EpochManager(EPOCH_LENGTH), serialiser);
@Test
- public void voteTallyUpdatedWithVoteFromBlock() {
+ public void voteTallyUpdatedWithAddVote() {
+ when(serialiser.extractVoteFromHeader(any()))
+ .thenReturn(Optional.of(new CastVote(ADD, proposerAddress, subject)));
+
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
- headerBuilder.number(1);
- headerBuilder.nonce(VoteType.ADD.getNonceValue());
- headerBuilder.coinbase(subject);
- addProposer(headerBuilder);
+ headerBuilder.number(EPOCH_LENGTH - 1);
final BlockHeader header = headerBuilder.buildHeader();
updater.updateForBlock(header, voteTally);
- verify(voteTally).addVote(proposerAddress, subject, VoteType.ADD);
+ verify(voteTally).addVote(new CastVote(ADD, proposerAddress, subject));
}
@Test
public void voteTallyNotUpdatedWhenBlockHasNoVoteSubject() {
+ when(serialiser.extractVoteFromHeader(any())).thenReturn(Optional.empty());
+
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
- headerBuilder.number(1);
- headerBuilder.nonce(VoteType.ADD.getNonceValue());
- headerBuilder.coinbase(Address.fromHexString("0000000000000000000000000000000000000000"));
- addProposer(headerBuilder);
+ headerBuilder.number(EPOCH_LENGTH - 1);
final BlockHeader header = headerBuilder.buildHeader();
updater.updateForBlock(header, voteTally);
@@ -87,11 +79,10 @@ public class CliqueVoteTallyUpdaterTest {
@Test
public void outstandingVotesDiscardedWhenEpochReached() {
+ when(serialiser.extractVoteFromHeader(any())).thenReturn(Optional.empty());
+
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);
@@ -102,11 +93,9 @@ public class CliqueVoteTallyUpdaterTest {
@Test
public void buildVoteTallyByExtractingValidatorsFromGenesisBlock() {
+ when(serialiser.validatorsInBlock(any())).thenReturn(asList(subject, validator1));
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);
@@ -118,11 +107,8 @@ public class CliqueVoteTallyUpdaterTest {
@Test
public void buildVoteTallyByExtractingValidatorsFromEpochBlock() {
+ when(serialiser.validatorsInBlock(any())).thenReturn(asList(subject, validator1));
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);
@@ -134,15 +120,15 @@ public class CliqueVoteTallyUpdaterTest {
@Test
public void addVotesFromBlocksAfterMostRecentEpoch() {
+ when(serialiser.validatorsInBlock(any())).thenReturn(asList(validator1));
+ when(serialiser.extractVoteFromHeader(any()))
+ .thenReturn(Optional.of(new CastVote(ADD, proposerAddress, subject)));
+
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);
@@ -152,32 +138,4 @@ public class CliqueVoteTallyUpdaterTest {
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 validators) {
-
- final CliqueExtraData initialIbftExtraData =
- new CliqueExtraData(
- BytesValue.wrap(new byte[CliqueExtraData.EXTRA_VANITY_LENGTH]),
- INVALID_SEAL,
- validators);
-
- builder.extraData(initialIbftExtraData.encode());
- final BlockHeader header = builder.buildHeader();
- final Hash proposerSealHash =
- CliqueBlockHashing.calculateDataHashForProposerSeal(header, initialIbftExtraData);
-
- final Signature proposerSignature = SECP256K1.sign(proposerSealHash, proposerKeyPair);
-
- final CliqueExtraData sealedData =
- new CliqueExtraData(
- BytesValue.wrap(new byte[CliqueExtraData.EXTRA_VANITY_LENGTH]),
- proposerSignature,
- validators);
-
- builder.extraData(sealedData.encode());
- }
}
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockImporter.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockImporter.java
index d535c09349..125d721d30 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockImporter.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockImporter.java
@@ -12,6 +12,7 @@
*/
package tech.pegasys.pantheon.consensus.ibft;
+import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/VoteTallyUpdater.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/VoteTallyUpdater.java
deleted file mode 100644
index 3c1b38485a..0000000000
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/VoteTallyUpdater.java
+++ /dev/null
@@ -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);
-}
diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockImporterTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockImporterTest.java
index 9eefe49f97..ccb5354ead 100644
--- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockImporterTest.java
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockImporterTest.java
@@ -20,6 +20,7 @@ import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.consensus.common.VoteProposer;
import tech.pegasys.pantheon.consensus.common.VoteTally;
+import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Block;
diff --git a/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftLegacyVotingBlockInterface.java b/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftLegacyVotingBlockInterface.java
new file mode 100644
index 0000000000..e771bf65eb
--- /dev/null
+++ b/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftLegacyVotingBlockInterface.java
@@ -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 voteToValue =
+ ImmutableBiMap.of(
+ ValidatorVotePolarity.ADD, 0xFFFFFFFFFFFFFFFFL,
+ ValidatorVotePolarity.DROP, 0x0L);
+
+ @Override
+ public Optional 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 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 validatorsInBlock(final BlockHeader header) {
+ return IbftExtraData.decode(header.getExtraData()).getValidators();
+ }
+}
diff --git a/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftProtocolSchedule.java b/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftProtocolSchedule.java
index f828a4b1d9..4e06499425 100644
--- a/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftProtocolSchedule.java
+++ b/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftProtocolSchedule.java
@@ -17,6 +17,7 @@ import static tech.pegasys.pantheon.consensus.ibftlegacy.IbftBlockHeaderValidati
import tech.pegasys.pantheon.config.GenesisConfigOptions;
import tech.pegasys.pantheon.config.IbftConfigOptions;
import tech.pegasys.pantheon.consensus.common.EpochManager;
+import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockImporter;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.ethereum.core.Wei;
@@ -59,7 +60,7 @@ public class IbftProtocolSchedule {
new IbftBlockImporter(
new MainnetBlockImporter<>(
blockHeaderValidator, blockBodyValidator, blockProcessor),
- new IbftVoteTallyUpdater(epochManager)),
+ new VoteTallyUpdater(epochManager, new IbftLegacyVotingBlockInterface())),
(time, parent, protocolContext) -> BigInteger.ONE)
.blockReward(Wei.ZERO)
.blockHashFunction(IbftBlockHashing::calculateHashOfIbftBlockOnChain);
diff --git a/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftLegacyVotingBlockInterfaceTest.java b/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftLegacyVotingBlockInterfaceTest.java
new file mode 100644
index 0000000000..779e7bfa33
--- /dev/null
+++ b/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftLegacyVotingBlockInterfaceTest.java
@@ -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 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 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 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 extractedValidators = serDeser.validatorsInBlock(header);
+
+ assertThat(extractedValidators).isEqualTo(validatorList);
+ }
+}
diff --git a/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftVoteTallyUpdaterTest.java b/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftVoteTallyUpdaterTest.java
deleted file mode 100644
index ab05a7d7c3..0000000000
--- a/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/IbftVoteTallyUpdaterTest.java
+++ /dev/null
@@ -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 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());
- }
-}
diff --git a/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/TestHelpers.java b/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/TestHelpers.java
new file mode 100644
index 0000000000..266c502aaa
--- /dev/null
+++ b/consensus/ibftlegacy/src/test/java/tech/pegasys/pantheon/consensus/ibftlegacy/TestHelpers.java
@@ -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 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();
+ }
+}
diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java
index 63debc2eb1..40fd3e4294 100644
--- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java
+++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java
@@ -19,13 +19,14 @@ import tech.pegasys.pantheon.config.GenesisConfigFile;
import tech.pegasys.pantheon.config.GenesisConfigOptions;
import tech.pegasys.pantheon.consensus.clique.CliqueContext;
import tech.pegasys.pantheon.consensus.clique.CliqueProtocolSchedule;
-import tech.pegasys.pantheon.consensus.clique.CliqueVoteTallyUpdater;
+import tech.pegasys.pantheon.consensus.clique.CliqueVotingBlockInterface;
import tech.pegasys.pantheon.consensus.clique.VoteTallyCache;
import tech.pegasys.pantheon.consensus.clique.blockcreation.CliqueBlockScheduler;
import tech.pegasys.pantheon.consensus.clique.blockcreation.CliqueMinerExecutor;
import tech.pegasys.pantheon.consensus.clique.blockcreation.CliqueMiningCoordinator;
import tech.pegasys.pantheon.consensus.common.EpochManager;
import tech.pegasys.pantheon.consensus.common.VoteProposer;
+import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.blockcreation.MiningCoordinator;
@@ -138,7 +139,9 @@ public class CliquePantheonController implements PantheonController {
final EpochManager epochManager = new EpochManager(ibftConfig.getEpochLength());
final VoteTally voteTally =
- new IbftVoteTallyUpdater(epochManager).buildVoteTallyFromBlockchain(blockchain);
+ new VoteTallyUpdater(epochManager, new IbftLegacyVotingBlockInterface())
+ .buildVoteTallyFromBlockchain(blockchain);
final VoteProposer voteProposer = new VoteProposer();