6#ifndef ALT_INTEGRATION_INCLUDE_VERIBLOCK_BLOCKCHAIN_POP_FORK_RESOLUTION_HPP_
7#define ALT_INTEGRATION_INCLUDE_VERIBLOCK_BLOCKCHAIN_POP_FORK_RESOLUTION_HPP_
19#include <veriblock/pop/blockchain/block_index.hpp>
20#include <veriblock/pop/blockchain/blocktree.hpp>
21#include <veriblock/pop/blockchain/chain_slice.hpp>
22#include <veriblock/pop/blockchain/pop/pop_state_machine.hpp>
23#include <veriblock/pop/finalizer.hpp>
24#include <veriblock/pop/keystone_util.hpp>
25#include <veriblock/pop/logger.hpp>
26#include <veriblock/pop/trace.hpp>
28#include "veriblock/pop/assert.hpp"
29#include "veriblock/pop/blockchain/block_status.hpp"
30#include "veriblock/pop/blockchain/chain.hpp"
31#include "veriblock/pop/validation_state.hpp"
34struct PayloadsStorage;
39template <
typename ProtectingBlockT>
40struct ProtoKeystoneContext {
42 uint32_t timestampOfEndorsedBlock;
44 ProtoKeystoneContext(
int height,
int time)
45 : blockHeight(height), timestampOfEndorsedBlock(time) {}
50 std::set<const BlockIndex<ProtectingBlockT>*> referencedByBlocks;
58struct KeystoneContext {
60 int firstBlockPublicationHeight;
63template <
typename ConfigType>
64bool publicationViolatesFinality(
int pubToCheck,
66 const ConfigType& config) {
67 int64_t diff = pubToCheck - base;
68 return diff > config.getFinalityDelay();
71template <
typename ConfigType>
72int getConsensusScoreFromRelativeBlockStartingAtZero(int64_t relativeBlock,
73 const ConfigType& config) {
74 if (relativeBlock < 0 ||
75 (
size_t)relativeBlock >= config.getForkResolutionLookUpTable().size()) {
79 return config.getForkResolutionLookUpTable()[relativeBlock];
82constexpr int NO_ENDORSEMENT = (std::numeric_limits<int32_t>::max)();
84template <
typename ProtectingBlockT,
typename ProtectingChainParams>
85KeystoneContext getKeystoneContext(
86 const ProtoKeystoneContext<ProtectingBlockT>& pkc,
87 const BlockTree<ProtectingBlockT, ProtectingChainParams>& tree) {
88 VBK_TRACE_ZONE_SCOPED;
90 int earliestEndorsementIndex = NO_ENDORSEMENT;
91 for (
const auto* btcIndex : pkc.referencedByBlocks) {
92 if (btcIndex ==
nullptr) {
96 auto endorsementIndex = btcIndex->getHeight();
97 if (endorsementIndex >= earliestEndorsementIndex) {
101 bool EnableTimeAdjustment = tree.getParams().EnableTimeAdjustment();
102 if (!EnableTimeAdjustment ||
103 pkc.timestampOfEndorsedBlock < btcIndex->getTimestamp()) {
104 earliestEndorsementIndex = endorsementIndex;
110 auto best = tree.getBestChain();
111 for (
int adjustedEndorsementIndex = endorsementIndex + 1;
112 adjustedEndorsementIndex <= best.chainHeight();
113 adjustedEndorsementIndex++) {
116 auto* index = best[adjustedEndorsementIndex];
117 VBK_ASSERT(index !=
nullptr);
118 if (pkc.timestampOfEndorsedBlock < index->getTimestamp()) {
122 if (adjustedEndorsementIndex < earliestEndorsementIndex) {
123 earliestEndorsementIndex = adjustedEndorsementIndex;
133 return {pkc.blockHeight, earliestEndorsementIndex};
136template <
typename ProtectedBlockT,
137 typename ProtectingBlockT,
138 typename ProtectingChainParams,
139 typename ProtectedChainParams,
140 typename ProtectedBlockTree>
141ProtoKeystoneContext<ProtectingBlockT> getProtoKeystoneContext(
142 int keystoneToConsider,
143 const ChainSlice<BlockIndex<ProtectedBlockT>>& chain,
144 const ProtectedBlockTree& ed,
145 const BlockTree<ProtectingBlockT, ProtectingChainParams>& ing,
146 const ProtectedChainParams& config) {
147 VBK_TRACE_ZONE_SCOPED;
148 auto ki = config.getKeystoneInterval();
149 auto* tip = chain.tip();
150 VBK_ASSERT(tip !=
nullptr &&
"chain must not be empty");
152 auto highestConnectingBlock =
153 highestBlockWhichConnectsKeystoneToPrevious(keystoneToConsider, ki);
154 auto highestPossibleEndorsedBlockHeaderHeight = tip->getHeight();
155 auto highestEndorsedBlock = std::min(
156 highestConnectingBlock, highestPossibleEndorsedBlockHeaderHeight);
158 ProtoKeystoneContext<ProtectingBlockT> pkc(
159 keystoneToConsider, chain[keystoneToConsider]->getTimestamp());
164 for (
auto relevantEndorsedBlock = keystoneToConsider;
165 relevantEndorsedBlock <= highestEndorsedBlock;
166 relevantEndorsedBlock++) {
167 auto* index = chain[relevantEndorsedBlock];
170 VBK_ASSERT(index !=
nullptr);
172 for (
const auto* e : index->getEndorsedBy()) {
173 VBK_ASSERT(e !=
nullptr);
174 auto* containingBlock = ed.getBlockIndex(e->containingHash);
175 VBK_ASSERT_MSG(containingBlock !=
nullptr,
176 "state corruption: could not find the containing block of "
177 "an applied endorsement");
179 if (!chain.contains(containingBlock)) {
185 auto* ind = ing.getBlockIndex(e->blockOfProof);
186 VBK_ASSERT(ind !=
nullptr &&
187 "state corruption: could not find the block of proof of "
188 "an applied endorsement");
189 if (!ing.getBestChain().contains(ind)) {
195 pkc.referencedByBlocks.insert(ind);
205template <
typename ProtectedBlockT,
206 typename ProtectingBlockT,
207 typename ProtectingChainParams,
208 typename ProtectedChainParams,
209 typename ProtectedBlockTree>
210struct ReducedPublicationView {
211 using protecting_block_t = ProtectingBlockT;
212 using protected_block_t = ProtectedBlockT;
213 using protecting_chain_params_t = ProtectingChainParams;
214 using protected_chain_params_t = ProtectedChainParams;
215 using protected_chain_t = ChainSlice<BlockIndex<protected_block_t>>;
216 using protected_tree_t = ProtectedBlockTree;
217 using protecting_tree_t =
218 BlockTree<protecting_block_t, protecting_chain_params_t>;
220 const protected_chain_params_t& config;
221 const int keystoneInterval;
222 const protected_chain_t chain;
224 const protected_tree_t& protectedTree;
225 const protecting_tree_t& protectingTree;
227 const int firstKeystoneHeight;
228 const int lastKeystoneHeight;
230 KeystoneContext currentKeystoneContext;
232 ReducedPublicationView(
const protected_chain_t _chain,
233 const protected_chain_params_t& _config,
234 const protected_tree_t& _ed,
235 const protecting_tree_t& _ing)
237 keystoneInterval(config.getKeystoneInterval()),
240 protectingTree(_ing),
241 firstKeystoneHeight(firstKeystoneAfterBlockIndex(chain.first())),
242 lastKeystoneHeight(highestKeystoneAtOrBeforeBlockIndex(chain.tip())) {
243 VBK_ASSERT(keystoneInterval > 0);
246 const protected_chain_params_t& getConfig()
const {
return config; }
248 size_t size()
const {
249 return blockHeightToKeystoneNumber(lastKeystone(), keystoneInterval) -
250 blockHeightToKeystoneNumber(firstKeystone(), keystoneInterval) + 1;
253 bool empty()
const {
return size() == 0; }
255 int firstKeystone()
const {
return firstKeystoneHeight; }
257 int lastKeystone()
const {
return lastKeystoneHeight; }
259 int nextKeystoneAfter(
int keystoneHeight)
const {
260 VBK_ASSERT(
isKeystone(keystoneHeight, keystoneInterval));
261 return keystoneHeight + keystoneInterval;
264 const KeystoneContext* operator[](
int blockHeight) {
265 return getKeystone(blockHeight);
268 const KeystoneContext* getKeystone(
int blockHeight) {
271 "getKeystone can not be called with a non-keystone block height");
273 if (blockHeight < firstKeystone() || blockHeight > lastKeystone()) {
279 auto pkc = getProtoKeystoneContext(
280 blockHeight, chain, protectedTree, protectingTree, config);
282 currentKeystoneContext = getKeystoneContext(pkc, protectingTree);
283 return ¤tKeystoneContext;
286 int32_t firstKeystoneAfterBlockIndex(
287 typename protected_chain_t::index_t* blockIndex) {
288 VBK_ASSERT_MSG(blockIndex,
"Protected tree should be bootstrapped");
289 return firstKeystoneAfter(blockIndex->getHeight(), keystoneInterval);
292 int32_t highestKeystoneAtOrBeforeBlockIndex(
293 typename protected_chain_t::index_t* blockIndex) {
294 VBK_ASSERT_MSG(blockIndex,
"Protected tree should be bootstrapped");
295 return highestKeystoneAtOrBefore(blockIndex->getHeight(), keystoneInterval);
299template <
typename PublicationView>
300int comparePopScoreImpl(PublicationView& a, PublicationView& b) {
301 VBK_TRACE_ZONE_SCOPED;
303 if (a.empty() && b.empty()) {
318 "Comparing POP scores of chains A(first=%d, tip=%d) "
319 "and B(first=%d, tip=%d)",
325 int earliestKeystone = a.firstKeystone();
326 VBK_ASSERT(earliestKeystone == b.firstKeystone());
327 int latestKeystone = (std::max)(a.lastKeystone(), b.lastKeystone());
329 auto& config = a.getConfig();
330 VBK_ASSERT(&config == &b.getConfig());
346 bool aOutsideFinality =
false;
347 bool bOutsideFinality =
false;
350 int previousPublicationA = NO_ENDORSEMENT;
351 int previousPublicationB = NO_ENDORSEMENT;
353 for (
int keystoneToCompare = earliestKeystone;
354 keystoneToCompare <= latestKeystone;
355 keystoneToCompare = a.nextKeystoneAfter(keystoneToCompare)) {
356 auto* actx = aOutsideFinality ? nullptr : a.getKeystone(keystoneToCompare);
357 auto* bctx = bOutsideFinality ? nullptr : b.getKeystone(keystoneToCompare);
359 int earliestPublicationA =
360 actx ==
nullptr ? NO_ENDORSEMENT : actx->firstBlockPublicationHeight;
361 int earliestPublicationB =
362 bctx ==
nullptr ? NO_ENDORSEMENT : bctx->firstBlockPublicationHeight;
367 if (actx !=
nullptr &&
368 publicationViolatesFinality(
369 earliestPublicationA, previousPublicationA, config)) {
370 aOutsideFinality =
true;
373 "Chain A is outside finality: the gap between adjacent keystone "
374 "endorsements is too large");
376 previousPublicationA = earliestPublicationA;
378 if (bctx !=
nullptr &&
379 publicationViolatesFinality(
380 earliestPublicationB, previousPublicationB, config)) {
381 bOutsideFinality =
true;
384 "Chain B is outside finality: the gap between adjacent keystone "
385 "endorsements is too large");
387 previousPublicationB = earliestPublicationB;
391 if (actx ==
nullptr && bctx ==
nullptr) {
392 if (aOutsideFinality && bOutsideFinality) {
398 if (actx ==
nullptr) {
399 chainBscore += config.getForkResolutionLookUpTable()[0];
404 aOutsideFinality =
true;
406 if (chainBscore > chainAscore) {
414 if (bctx ==
nullptr) {
415 chainAscore += config.getForkResolutionLookUpTable()[0];
418 bOutsideFinality =
true;
420 if (chainAscore > chainBscore) {
430 int earliestPublicationOfEither =
431 (std::min)(earliestPublicationA, earliestPublicationB);
433 chainAscore += getConsensusScoreFromRelativeBlockStartingAtZero(
434 earliestPublicationA - earliestPublicationOfEither, config);
435 chainBscore += getConsensusScoreFromRelativeBlockStartingAtZero(
436 earliestPublicationB - earliestPublicationOfEither, config);
438 if (publicationViolatesFinality(
439 earliestPublicationA, earliestPublicationB, config)) {
440 VBK_LOG_DEBUG(
"Chain A is outside finality: way behind chain B");
441 aOutsideFinality =
true;
444 if (publicationViolatesFinality(
445 earliestPublicationB, earliestPublicationA, config)) {
446 VBK_LOG_DEBUG(
"Chain B is outside finality: way behind chain A");
447 bOutsideFinality =
true;
451 VBK_LOG_DEBUG(
"ChainA score=%d, ChainB score=%d", chainAscore, chainBscore);
452 return chainAscore - chainBscore;
457enum class PopFrOutcome {
460 CANDIDATE_INVALID_CHAIN,
461 CANDIDATE_INVALID_PAYLOADS,
462 CANDIDATE_PART_OF_ACTIVE_CHAIN,
463 CANDIDATE_IS_TIP_SUCCESSOR,
465 BOTH_DONT_CROSS_KEYSTONE_BOUNDARY,
466 CANDIDATE_INVALID_INDEPENDENTLY,
470std::string popFrOutcomeToString(PopFrOutcome value,
471 const ValidationState& state);
474template <
typename ProtectedBlock,
475 typename ProtectedParams,
476 typename ProtectingBlockTree,
477 typename ProtectedBlockTree>
478struct PopAwareForkResolutionComparator {
479 using protected_block_t = ProtectedBlock;
480 using protected_params_t = ProtectedParams;
481 using protected_block_tree_t = ProtectedBlockTree;
482 using protecting_params_t =
typename ProtectingBlockTree::params_t;
483 using protected_index_t = BlockIndex<protected_block_t>;
484 using protecting_index_t =
typename ProtectingBlockTree::index_t;
485 using protecting_block_t =
typename protecting_index_t::block_t;
486 using endorsement_t =
typename protected_index_t::endorsement_t;
487 using sm_t = PopStateMachine<ProtectingBlockTree,
489 BlockIndex<protected_block_t>,
492 using reduced_publication_view_t =
493 internal::ReducedPublicationView<protected_block_t,
497 protected_block_tree_t>;
499 PopAwareForkResolutionComparator(ProtectedBlockTree& ed,
500 std::shared_ptr<ProtectingBlockTree> ing,
501 const protected_params_t& protectedParams,
502 PayloadsStorage& payloadsProvider)
504 ing_(std::move(ing)),
505 protectedParams_(protectedParams),
506 payloadsProvider_(payloadsProvider),
508 VBK_ASSERT(protectedParams.getKeystoneInterval() > 0);
511 ProtectingBlockTree& getProtectingBlockTree() {
return *ing_; }
512 const ProtectingBlockTree& getProtectingBlockTree()
const {
return *ing_; }
518 bool setState(protected_index_t& to, ValidationState& state) {
519 VBK_TRACE_ZONE_SCOPED;
521 auto* currentActive = ed_.getBestChain().tip();
522 VBK_ASSERT(currentActive !=
nullptr &&
"should be bootstrapped");
525 currentActive->getHeight() + 1 ==
526 ed_.getRoot().getHeight() +
527 (
typename protected_block_t::height_t)ed_.appliedBlockCount,
528 "the tree must have the best chain applied");
530 if (currentActive == &to) {
535 auto guard = ing_->deferForkResolutionGuard();
536 auto* originalTip = ing_->getBestChain().tip();
538 if (sm_.setState(*currentActive, to, state)) {
542 guard.overrideDeferredForkResolution(originalTip);
553 std::pair<int, PopFrOutcome> comparePopScore(protected_index_t& candidate,
554 ValidationState& state) {
555 VBK_TRACE_ZONE_SCOPED;
557 if (!candidate.isValid()) {
559 VBK_LOG_DEBUG(
"Candidate %s is invalid, the current chain wins",
560 candidate.toShortPrettyString());
561 return {1, PopFrOutcome::CANDIDATE_INVALID_CHAIN};
563 auto currentBest = ed_.getBestChain();
564 auto bestTip = currentBest.tip();
565 VBK_ASSERT(bestTip &&
"must be bootstrapped");
567 if (bestTip == &candidate) {
569 return {1, PopFrOutcome::CANDIDATE_IS_TIP};
572 if (bestTip->finalized && candidate.getHeight() <= bestTip->getHeight()) {
574 return {1, PopFrOutcome::TIP_IS_FINAL};
577 if (currentBest.contains(&candidate)) {
579 "Candidate %s is part of the active chain, the current chain wins",
580 candidate.toShortPrettyString());
581 return {1, PopFrOutcome::CANDIDATE_PART_OF_ACTIVE_CHAIN};
584 auto originalProtectingTip = ing_->getBestChain().tip();
587 if (candidate.getAncestor(bestTip->getHeight()) == bestTip) {
588 VBK_LOG_DEBUG(
"Candidate is %d blocks ahead",
589 candidate.getHeight() - bestTip->getHeight());
591 auto guard = ing_->deferForkResolutionGuard();
593 if (!sm_.apply(*bestTip, candidate, state)) {
595 VBK_LOG_DEBUG(
"Candidate contains INVALID command(s): %s",
597 guard.overrideDeferredForkResolution(originalProtectingTip);
598 return {1, PopFrOutcome::CANDIDATE_INVALID_PAYLOADS};
601 VBK_LOG_DEBUG(
"Candidate contains VALID commands, chain B wins");
602 return {-1, PopFrOutcome::CANDIDATE_IS_TIP_SUCCESSOR};
605 VBK_LOG_DEBUG(
"Doing %s POP fork resolution. Best=%s, Candidate=%s",
606 protected_block_t::name(),
607 bestTip->toShortPrettyString(),
608 candidate.toShortPrettyString());
610 auto ki = ed_.getParams().getKeystoneInterval();
611 const auto* fork =
findFork(currentBest, &candidate);
614 "state corruption: all blocks in a blocktree must form a tree, "
615 "thus all pairs of chains must have a fork point: chainA=%s, chainB=%s",
616 bestTip->toPrettyString(),
617 candidate.toPrettyString());
621 auto* nextToFork = currentBest[fork->getHeight() + 1];
622 if (nextToFork !=
nullptr && nextToFork->finalized) {
626 return {1, PopFrOutcome::TIP_IS_FINAL};
630 bool AcrossedKeystoneBoundary =
631 isCrossedKeystoneBoundary(fork->getHeight(), bestTip->getHeight(), ki);
632 bool BcrossedKeystoneBoundary =
633 isCrossedKeystoneBoundary(fork->getHeight(), candidate.getHeight(), ki);
634 if (!AcrossedKeystoneBoundary && !BcrossedKeystoneBoundary) {
637 "Neither chain crossed a keystone boundary: chains are equal");
638 return {0, PopFrOutcome::BOTH_DONT_CROSS_KEYSTONE_BOUNDARY};
642 ChainSlice<protected_index_t> chainA(currentBest, fork->getHeight());
644 Chain<protected_index_t> chainB(fork->getHeight(), &candidate);
647 VBK_ASSERT(chainA.first() !=
nullptr && chainA.first() == chainB.first());
651 VBK_ASSERT(chainA.tip() == bestTip);
657 auto guard = ing_->deferForkResolutionGuard();
659 if (!sm_.apply(chainB, state)) {
661 VBK_LOG_DEBUG(
"Chain B contains INVALID payloads, Chain A wins (%s)",
663 guard.overrideDeferredForkResolution(originalProtectingTip);
664 return {1, PopFrOutcome::CANDIDATE_INVALID_PAYLOADS};
669 auto reducedPublicationViewA =
670 reduced_publication_view_t(chainA, protectedParams_, ed_, *ing_);
671 auto reducedPublicationViewB =
672 reduced_publication_view_t(chainB, protectedParams_, ed_, *ing_);
674 int result = internal::comparePopScoreImpl(reducedPublicationViewA,
675 reducedPublicationViewB);
678 auto guard = ing_->deferForkResolutionGuard();
680 guard.overrideDeferredForkResolution(originalProtectingTip);
681 VBK_LOG_DEBUG(
"Chain A remains the best chain");
684 auto guard = ing_->deferForkResolutionGuard();
690 auto& chainBValidFrom =
691 sm_.unapplyWhile(chainB, [](protected_index_t& index) ->
bool {
699 if (!sm_.apply(chainBValidFrom, *chainB.tip(), state)) {
700 sm_.unapply(chainBValidFrom, *chainB.first());
701 bool success = sm_.apply(*chainA.first(), *chainA.tip(), state);
704 "state corruption: chainA as a former best chain should be valid");
705 VBK_LOG_DEBUG(
"Chain B is invalid when applied alone. Chain A wins");
706 guard.overrideDeferredForkResolution(originalProtectingTip);
707 return {1, PopFrOutcome::CANDIDATE_INVALID_INDEPENDENTLY};
710 VBK_LOG_DEBUG(
"Chain B wins");
713 return {result, PopFrOutcome::HIGHER_POP_SCORE};
716 std::string toPrettyString(
size_t level = 0)
const {
717 std::string pad(level,
' ');
718 return format(
"{}Comparator{{\n{}{{tree=\n{}}}}}",
721 ing_->toPrettyString(level + 2));
725 ProtectedBlockTree& ed_;
726 std::shared_ptr<ProtectingBlockTree> ing_;
728 const protected_params_t& protectedParams_;
729 PayloadsStorage& payloadsProvider_;
@ BLOCK_CAN_BE_APPLIED
the chain with the block at its tip is fully valid, so if we do SetState on this block,...
C::index_t * findFork(const C &chain, const typename C::index_t *pindex)
Find fork between chain and pindex.
bool isKeystone(int32_t blockNumber, uint32_t keystoneInterval)
Calculates if block with height = blockNumber is keystone.