VoteTallyCache.java
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.common.validator.blockbased;
import static com.google.common.base.Preconditions.checkNotNull;
import org.hyperledger.besu.consensus.common.BlockInterface;
import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutionException;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
class VoteTallyCache {
private final Blockchain blockchain;
private final EpochManager epochManager;
private final VoteTallyUpdater voteTallyUpdater;
private final Cache<Hash, VoteTally> voteTallyCache =
CacheBuilder.newBuilder().maximumSize(100).build();
private final BlockInterface blockInterface;
VoteTallyCache(
final Blockchain blockchain,
final VoteTallyUpdater voteTallyUpdater,
final EpochManager epochManager,
final BlockInterface blockInterface) {
checkNotNull(blockchain);
checkNotNull(voteTallyUpdater);
checkNotNull(epochManager);
checkNotNull(blockInterface);
this.blockchain = blockchain;
this.voteTallyUpdater = voteTallyUpdater;
this.epochManager = epochManager;
this.blockInterface = blockInterface;
}
VoteTally getVoteTallyAtHead() {
return getVoteTallyAfterBlock(blockchain.getChainHeadHeader());
}
/**
* Determines the VoteTally for a given block header, by back-tracing the blockchain to a
* previously cached value or epoch block. Then appyling votes in each intermediate header such
* that representative state can be provided. This function assumes the vote cast in {@code
* header} is applied, thus the voteTally returned contains the group of validators who are
* permitted to partake in the next block's creation.
*
* @param header the header of the block after which the VoteTally is to be returned
* @return The Vote Tally (and therefore validators) following the application of all votes upto
* and including the requested header.
*/
VoteTally getVoteTallyAfterBlock(final BlockHeader header) {
try {
return voteTallyCache.get(header.getHash(), () -> populateCacheUptoAndIncluding(header));
} catch (final ExecutionException ex) {
throw new RuntimeException("Unable to determine a VoteTally object for the requested block.");
}
}
private VoteTally populateCacheUptoAndIncluding(final BlockHeader start) {
BlockHeader header = start;
final Deque<BlockHeader> intermediateBlocks = new ArrayDeque<>();
VoteTally voteTally = null;
while (true) { // Will run into an epoch block (and thus a VoteTally) to break loop.
intermediateBlocks.push(header);
voteTally = getValidatorsAfter(header);
if (voteTally != null) {
break;
}
header =
blockchain
.getBlockHeader(header.getParentHash())
.orElseThrow(
() ->
new NoSuchElementException(
"Supplied block was on a orphaned chain, unable to generate VoteTally."));
}
return constructMissingCacheEntries(intermediateBlocks, voteTally);
}
protected VoteTally getValidatorsAfter(final BlockHeader header) {
if (epochManager.isEpochBlock(header.getNumber())) {
return new VoteTally(blockInterface.validatorsInBlock(header));
}
return voteTallyCache.getIfPresent(header.getParentHash());
}
private VoteTally constructMissingCacheEntries(
final Deque<BlockHeader> headers, final VoteTally tally) {
final VoteTally mutableVoteTally = tally.copy();
while (!headers.isEmpty()) {
final BlockHeader h = headers.pop();
voteTallyUpdater.updateForBlock(h, mutableVoteTally);
voteTallyCache.put(h.getHash(), mutableVoteTally.copy());
}
return mutableVoteTally;
}
}