[NC-1401] Votes in clique headers (#35)

Chris Mckay 6 years ago committed by GitHub
parent 61432b5a4b
commit e5a17733cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      consensus/clique/src/main/java/net/consensys/pantheon/consensus/clique/blockcreation/CliqueBlockCreator.java
  2. 56
      consensus/clique/src/test/java/net/consensys/pantheon/consensus/clique/blockcreation/CliqueBlockCreatorTest.java
  3. 4
      consensus/clique/src/test/java/net/consensys/pantheon/consensus/clique/jsonrpc/methods/DiscardTest.java
  4. 14
      consensus/clique/src/test/java/net/consensys/pantheon/consensus/clique/jsonrpc/methods/ProposeTest.java
  5. 26
      consensus/common/src/main/java/net/consensys/pantheon/consensus/common/VoteProposer.java
  6. 37
      consensus/common/src/test/java/net/consensys/pantheon/consensus/common/VoteProposerTest.java
  7. 2
      consensus/ibft/src/main/java/net/consensys/pantheon/consensus/ibft/blockcreation/IbftBlockCreator.java
  8. 6
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/blockcreation/AbstractBlockCreator.java

@ -3,6 +3,8 @@ package net.consensys.pantheon.consensus.clique.blockcreation;
import net.consensys.pantheon.consensus.clique.CliqueBlockHashing;
import net.consensys.pantheon.consensus.clique.CliqueContext;
import net.consensys.pantheon.consensus.clique.CliqueExtraData;
import net.consensys.pantheon.consensus.common.VoteTally;
import net.consensys.pantheon.consensus.common.VoteType;
import net.consensys.pantheon.crypto.SECP256K1;
import net.consensys.pantheon.crypto.SECP256K1.KeyPair;
import net.consensys.pantheon.ethereum.ProtocolContext;
@ -18,13 +20,16 @@ import net.consensys.pantheon.ethereum.core.Util;
import net.consensys.pantheon.ethereum.core.Wei;
import net.consensys.pantheon.ethereum.mainnet.ProtocolSchedule;
import net.consensys.pantheon.ethereum.mainnet.ScheduleBasedBlockHashFunction;
import net.consensys.pantheon.util.bytes.BytesValue;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Function;
public class CliqueBlockCreator extends AbstractBlockCreator<CliqueContext> {
private final KeyPair nodeKeys;
private final ProtocolSchedule<CliqueContext> protocolSchedule;
public CliqueBlockCreator(
final Address coinbase,
@ -47,7 +52,6 @@ public class CliqueBlockCreator extends AbstractBlockCreator<CliqueContext> {
Util.publicKeyToAddress(nodeKeys.getPublicKey()),
parentHeader);
this.nodeKeys = nodeKeys;
this.protocolSchedule = protocolSchedule;
}
/**
@ -64,11 +68,26 @@ public class CliqueBlockCreator extends AbstractBlockCreator<CliqueContext> {
final BlockHashFunction blockHashFunction =
ScheduleBasedBlockHashFunction.create(protocolSchedule);
final Optional<BlockHeader> optionalParentHeader =
protocolContext.getBlockchain().getBlockHeader(sealableBlockHeader.getParentHash());
final CliqueContext cliqueContext = protocolContext.getConsensusState();
final VoteTally voteTally = cliqueContext.getVoteTallyCache().getVoteTallyAtBlock(parentHeader);
final Optional<Map.Entry<Address, VoteType>> vote =
cliqueContext
.getVoteProposer()
.getVote(Util.publicKeyToAddress(nodeKeys.getPublicKey()), voteTally);
final long nonce = vote.map(Entry::getValue).map(VoteType::getNonceValue).orElse(0L);
final Address coinbase =
vote.map(Entry::getKey).orElse(Address.wrap(BytesValue.wrap(new byte[20])));
final BlockHeaderBuilder builder =
BlockHeaderBuilder.create()
.populateFrom(sealableBlockHeader)
.mixHash(Hash.ZERO)
.nonce(0)
.nonce(nonce)
.coinbase(coinbase)
.blockHashFunction(blockHashFunction);
final CliqueExtraData sealedExtraData = constructSignedExtraData(builder.buildBlockHeader());

@ -14,6 +14,7 @@ import net.consensys.pantheon.consensus.clique.TestHelpers;
import net.consensys.pantheon.consensus.clique.VoteTallyCache;
import net.consensys.pantheon.consensus.common.VoteProposer;
import net.consensys.pantheon.consensus.common.VoteTally;
import net.consensys.pantheon.consensus.common.VoteType;
import net.consensys.pantheon.crypto.SECP256K1.KeyPair;
import net.consensys.pantheon.ethereum.ProtocolContext;
import net.consensys.pantheon.ethereum.chain.GenesisConfig;
@ -58,6 +59,7 @@ public class CliqueBlockCreatorTest {
private ProtocolContext<CliqueContext> protocolContext;
private final MutableProtocolSchedule<CliqueContext> protocolSchedule =
new CliqueProtocolSchedule();
private VoteProposer voteProposer;
@Before
public void setup() {
@ -76,7 +78,9 @@ public class CliqueBlockCreatorTest {
final VoteTallyCache voteTallyCache = mock(VoteTallyCache.class);
when(voteTallyCache.getVoteTallyAtBlock(any())).thenReturn(new VoteTally(validatorList));
final CliqueContext cliqueContext = new CliqueContext(voteTallyCache, new VoteProposer());
voteProposer = new VoteProposer();
final CliqueContext cliqueContext = new CliqueContext(voteTallyCache, voteProposer);
protocolContext = new ProtocolContext<>(blockchain, stateArchive, cliqueContext);
// Add a block above the genesis
@ -114,4 +118,54 @@ public class CliqueBlockCreatorTest {
assertThat(CliqueHelpers.getProposerOfBlock(createdBlock.getHeader()))
.isEqualTo(proposerAddress);
}
@Test
public void insertsValidVoteIntoConstructedBlock() {
final CliqueExtraData extraData =
new CliqueExtraData(BytesValue.wrap(new byte[32]), null, validatorList);
final Address a1 = Address.fromHexString("5");
voteProposer.auth(a1);
final Address coinbase = AddressHelpers.ofValue(1);
final CliqueBlockCreator blockCreator =
new CliqueBlockCreator(
coinbase,
parent -> extraData.encode(),
new PendingTransactions(5),
protocolContext,
protocolSchedule,
gasLimit -> gasLimit,
proposerKeyPair,
Wei.ZERO,
blockchain.getChainHeadHeader());
final Block createdBlock = blockCreator.createBlock(0L);
assertThat(createdBlock.getHeader().getNonce()).isEqualTo(VoteType.ADD.getNonceValue());
assertThat(createdBlock.getHeader().getCoinbase()).isEqualTo(a1);
}
@Test
public void insertsNoVoteWhenAuthInValidators() {
final CliqueExtraData extraData =
new CliqueExtraData(BytesValue.wrap(new byte[32]), null, validatorList);
final Address a1 = Util.publicKeyToAddress(otherKeyPair.getPublicKey());
voteProposer.auth(a1);
final Address coinbase = AddressHelpers.ofValue(1);
final CliqueBlockCreator blockCreator =
new CliqueBlockCreator(
coinbase,
parent -> extraData.encode(),
new PendingTransactions(5),
protocolContext,
protocolSchedule,
gasLimit -> gasLimit,
proposerKeyPair,
Wei.ZERO,
blockchain.getChainHeadHeader());
final Block createdBlock = blockCreator.createBlock(0L);
assertThat(createdBlock.getHeader().getNonce()).isEqualTo(VoteType.DROP.getNonceValue());
assertThat(createdBlock.getHeader().getCoinbase()).isEqualTo(Address.fromHexString("0"));
}
}

@ -4,7 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import net.consensys.pantheon.consensus.common.VoteProposer;
import net.consensys.pantheon.consensus.common.VoteProposer.Vote;
import net.consensys.pantheon.consensus.common.VoteType;
import net.consensys.pantheon.ethereum.core.Address;
import net.consensys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
import net.consensys.pantheon.ethereum.jsonrpc.internal.exception.InvalidJsonRpcParameters;
@ -80,7 +80,7 @@ public class DiscardTest {
final JsonRpcResponse response = discard.response(requestWithParams(a0));
assertThat(proposer.get(a0)).isEqualTo(Optional.empty());
assertThat(proposer.get(a1)).isEqualTo(Optional.of(Vote.AUTH));
assertThat(proposer.get(a1)).isEqualTo(Optional.of(VoteType.ADD));
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS);
final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response;
assertThat(successResponse.getResult()).isEqualTo(true);

@ -3,7 +3,7 @@ package net.consensys.pantheon.consensus.clique.jsonrpc.methods;
import static org.assertj.core.api.Assertions.assertThat;
import net.consensys.pantheon.consensus.common.VoteProposer;
import net.consensys.pantheon.consensus.common.VoteProposer.Vote;
import net.consensys.pantheon.consensus.common.VoteType;
import net.consensys.pantheon.ethereum.core.Address;
import net.consensys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
import net.consensys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter;
@ -27,7 +27,7 @@ public class ProposeTest {
final JsonRpcResponse response = propose.response(requestWithParams(a0, true));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(Vote.AUTH));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(VoteType.ADD));
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS);
final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response;
assertThat(successResponse.getResult()).isEqualTo(true);
@ -41,7 +41,7 @@ public class ProposeTest {
final JsonRpcResponse response = propose.response(requestWithParams(a0, false));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(Vote.DROP));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(VoteType.DROP));
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS);
final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response;
assertThat(successResponse.getResult()).isEqualTo(true);
@ -56,7 +56,7 @@ public class ProposeTest {
proposer.auth(a0);
final JsonRpcResponse response = propose.response(requestWithParams(a0, true));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(Vote.AUTH));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(VoteType.ADD));
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS);
final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response;
assertThat(successResponse.getResult()).isEqualTo(true);
@ -71,7 +71,7 @@ public class ProposeTest {
proposer.drop(a0);
final JsonRpcResponse response = propose.response(requestWithParams(a0, false));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(Vote.DROP));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(VoteType.DROP));
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS);
final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response;
assertThat(successResponse.getResult()).isEqualTo(true);
@ -86,7 +86,7 @@ public class ProposeTest {
proposer.drop(a0);
final JsonRpcResponse response = propose.response(requestWithParams(a0, true));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(Vote.AUTH));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(VoteType.ADD));
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS);
final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response;
assertThat(successResponse.getResult()).isEqualTo(true);
@ -101,7 +101,7 @@ public class ProposeTest {
proposer.auth(a0);
final JsonRpcResponse response = propose.response(requestWithParams(a0, false));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(Vote.DROP));
assertThat(proposer.get(a0)).isEqualTo(Optional.of(VoteType.DROP));
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS);
final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response;
assertThat(successResponse.getResult()).isEqualTo(true);

@ -12,12 +12,8 @@ import java.util.concurrent.atomic.AtomicInteger;
/** Container for pending votes and selecting a vote for new blocks */
public class VoteProposer {
public enum Vote {
AUTH,
DROP
}
private final Map<Address, Vote> proposals = new ConcurrentHashMap<>();
private final Map<Address, VoteType> proposals = new ConcurrentHashMap<>();
private final AtomicInteger votePosition = new AtomicInteger(0);
/**
@ -26,7 +22,7 @@ public class VoteProposer {
* @param address The address to be voted in
*/
public void auth(final Address address) {
proposals.put(address, Vote.AUTH);
proposals.put(address, VoteType.ADD);
}
/**
@ -35,7 +31,7 @@ public class VoteProposer {
* @param address The address to be voted out
*/
public void drop(final Address address) {
proposals.put(address, Vote.DROP);
proposals.put(address, VoteType.DROP);
}
/**
@ -52,14 +48,14 @@ public class VoteProposer {
proposals.clear();
}
public Optional<Vote> get(final Address address) {
public Optional<VoteType> get(final Address address) {
return Optional.ofNullable(proposals.get(address));
}
private boolean voteNotYetCast(
final Address localAddress,
final Address voteAddress,
final Vote vote,
final VoteType vote,
final Collection<Address> validators,
final VoteTally tally) {
@ -69,17 +65,17 @@ public class VoteProposer {
tally.getOutstandingRemoveVotesFor(voteAddress).contains(localAddress);
// if they're a validator, we want to see them dropped, and we haven't voted to drop them yet
if (validators.contains(voteAddress) && !votedDrop && vote == Vote.DROP) {
if (validators.contains(voteAddress) && !votedDrop && vote == VoteType.DROP) {
return true;
// or if we've previously voted to auth them and we want to drop them
} else if (votedAuth && vote == Vote.DROP) {
} else if (votedAuth && vote == VoteType.DROP) {
return true;
// if they're not currently a validator and we want to see them authed and we haven't voted to
// auth them yet
} else if (!validators.contains(voteAddress) && !votedAuth && vote == Vote.AUTH) {
} else if (!validators.contains(voteAddress) && !votedAuth && vote == VoteType.ADD) {
return true;
// or if we've previously voted to drop them and we want to see them authed
} else if (votedDrop && vote == Vote.AUTH) {
} else if (votedDrop && vote == VoteType.ADD) {
return true;
}
@ -94,10 +90,10 @@ public class VoteProposer {
* @return Either an address with the vote (auth or drop) or no vote if we have no valid pending
* votes
*/
public Optional<Map.Entry<Address, Vote>> getVote(
public Optional<Map.Entry<Address, VoteType>> getVote(
final Address localAddress, final VoteTally tally) {
final Collection<Address> validators = tally.getCurrentValidators();
final List<Map.Entry<Address, Vote>> validVotes = new ArrayList<>();
final List<Map.Entry<Address, VoteType>> validVotes = new ArrayList<>();
proposals
.entrySet()

@ -4,7 +4,6 @@ import static net.consensys.pantheon.consensus.common.VoteType.ADD;
import static net.consensys.pantheon.consensus.common.VoteType.DROP;
import static org.assertj.core.api.Assertions.assertThat;
import net.consensys.pantheon.consensus.common.VoteProposer.Vote;
import net.consensys.pantheon.ethereum.core.Address;
import java.util.AbstractMap;
@ -46,9 +45,9 @@ public class VoteProposerTest {
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.empty());
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.singletonList(a1))))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.DROP)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.DROP)));
assertThat(proposer.getVote(localAddress, new VoteTally(Arrays.asList(a1, a2, a3))))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.DROP)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.DROP)));
assertThat(proposer.getVote(localAddress, new VoteTally(Arrays.asList(a2, a3))))
.isEqualTo(Optional.empty());
}
@ -63,13 +62,13 @@ public class VoteProposerTest {
proposer.auth(a1);
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.ADD)));
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.singletonList(a1))))
.isEqualTo(Optional.empty());
assertThat(proposer.getVote(localAddress, new VoteTally(Arrays.asList(a1, a2, a3))))
.isEqualTo(Optional.empty());
assertThat(proposer.getVote(localAddress, new VoteTally(Arrays.asList(a2, a3))))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.ADD)));
}
@Test
@ -84,13 +83,13 @@ public class VoteProposerTest {
proposer.discard(a2);
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.ADD)));
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.singletonList(a1))))
.isEqualTo(Optional.empty());
assertThat(proposer.getVote(localAddress, new VoteTally(Arrays.asList(a1, a2, a3))))
.isEqualTo(Optional.empty());
assertThat(proposer.getVote(localAddress, new VoteTally(Arrays.asList(a2, a3))))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.ADD)));
}
@Test
@ -105,13 +104,13 @@ public class VoteProposerTest {
proposer.auth(a3);
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a2, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a2, VoteType.ADD)));
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a3, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a3, VoteType.ADD)));
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.ADD)));
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a2, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a2, VoteType.ADD)));
}
@Test
@ -128,13 +127,13 @@ public class VoteProposerTest {
proposer.drop(a4);
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a2, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a2, VoteType.ADD)));
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a3, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a3, VoteType.ADD)));
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.ADD)));
assertThat(proposer.getVote(localAddress, new VoteTally(Collections.emptyList())))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a2, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a2, VoteType.ADD)));
}
@Test
@ -150,7 +149,7 @@ public class VoteProposerTest {
tally.addVote(localAddress, a1, ADD);
assertThat(proposer.getVote(localAddress, tally))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.DROP)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.DROP)));
}
@Test
@ -166,7 +165,7 @@ public class VoteProposerTest {
tally.addVote(localAddress, a1, DROP);
assertThat(proposer.getVote(localAddress, tally))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.ADD)));
}
@Test
@ -182,7 +181,7 @@ public class VoteProposerTest {
proposer.drop(a1);
assertThat(proposer.getVote(localAddress, tally))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.DROP)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.DROP)));
}
@Test
@ -198,6 +197,6 @@ public class VoteProposerTest {
proposer.auth(a1);
assertThat(proposer.getVote(localAddress, tally))
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, Vote.AUTH)));
.isEqualTo(Optional.of(new AbstractMap.SimpleEntry<>(a1, VoteType.ADD)));
}
}

@ -35,7 +35,6 @@ public class IbftBlockCreator extends AbstractBlockCreator<IbftContext> {
private static final Logger LOG = LogManager.getLogger();
private final KeyPair nodeKeys;
private final ProtocolSchedule<IbftContext> protocolSchedule;
public IbftBlockCreator(
final Address coinbase,
@ -58,7 +57,6 @@ public class IbftBlockCreator extends AbstractBlockCreator<IbftContext> {
Util.publicKeyToAddress(nodeKeys.getPublicKey()),
parentHeader);
this.nodeKeys = nodeKeys;
this.protocolSchedule = protocolSchedule;
}
/**

@ -47,11 +47,11 @@ public abstract class AbstractBlockCreator<C> implements AsyncBlockCreator {
private final ExtraDataCalculator extraDataCalculator;
private final PendingTransactions pendingTransactions;
private final ProtocolContext<C> protocolContext;
private final ProtocolSchedule<C> protocolSchedule;
protected final ProtocolContext<C> protocolContext;
protected final ProtocolSchedule<C> protocolSchedule;
private final Wei minTransactionGasPrice;
private final Address miningBeneficiary;
private final BlockHeader parentHeader;
protected final BlockHeader parentHeader;
private final AtomicBoolean isCancelled = new AtomicBoolean(false);

Loading…
Cancel
Save