veriblock-pop-cpp
C++11 Libraries for leveraging VeriBlock Proof-Of-Proof blockchain technology.
fork_resolution.hpp
1// Copyright (c) 2019-2022 Xenios SEZC
2// https://www.veriblock.org
3// Distributed under the MIT software license, see the accompanying
4// file LICENSE or http://www.opensource.org/licenses/mit-license.php.
5
6#ifndef ALT_INTEGRATION_INCLUDE_VERIBLOCK_BLOCKCHAIN_POP_FORK_RESOLUTION_HPP_
7#define ALT_INTEGRATION_INCLUDE_VERIBLOCK_BLOCKCHAIN_POP_FORK_RESOLUTION_HPP_
8
9#include <algorithm>
10#include <cstddef>
11#include <cstdint>
12#include <functional>
13#include <limits>
14#include <memory>
15#include <set>
16#include <string>
17#include <utility>
18#include <vector>
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>
27
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"
32
33namespace altintegration {
34struct PayloadsStorage;
35
36namespace internal {
37
39template <typename ProtectingBlockT>
40struct ProtoKeystoneContext {
41 int blockHeight;
42 uint32_t timestampOfEndorsedBlock;
43
44 ProtoKeystoneContext(int height, int time)
45 : blockHeight(height), timestampOfEndorsedBlock(time) {}
46
47 // A map of the Endorsement blocks which reference (endorse a block header
48 // which is the represented block or references the represented block) to the
49 // index of the earliest
50 std::set<const BlockIndex<ProtectingBlockT>*> referencedByBlocks;
51};
52
54// FIXME: we don't use block height
55// FIXME: this is really asking to be converted to an optional(but it isn't
56// available in C++14) or a pseudo-optional where firstBlockPublicationHeight ==
57// NO_ENDORSEMENT is the check for the missing value
58struct KeystoneContext {
59 int blockHeight;
60 int firstBlockPublicationHeight;
61};
62
63template <typename ConfigType>
64bool publicationViolatesFinality(int pubToCheck,
65 int base,
66 const ConfigType& config) {
67 int64_t diff = pubToCheck - base;
68 return diff > config.getFinalityDelay();
69}
70
71template <typename ConfigType>
72int getConsensusScoreFromRelativeBlockStartingAtZero(int64_t relativeBlock,
73 const ConfigType& config) {
74 if (relativeBlock < 0 ||
75 (size_t)relativeBlock >= config.getForkResolutionLookUpTable().size()) {
76 return 0;
77 }
78
79 return config.getForkResolutionLookUpTable()[relativeBlock];
80}
81
82constexpr int NO_ENDORSEMENT = (std::numeric_limits<int32_t>::max)();
83
84template <typename ProtectingBlockT, typename ProtectingChainParams>
85KeystoneContext getKeystoneContext(
86 const ProtoKeystoneContext<ProtectingBlockT>& pkc,
87 const BlockTree<ProtectingBlockT, ProtectingChainParams>& tree) {
88 VBK_TRACE_ZONE_SCOPED;
89
90 int earliestEndorsementIndex = NO_ENDORSEMENT;
91 for (const auto* btcIndex : pkc.referencedByBlocks) {
92 if (btcIndex == nullptr) {
93 continue;
94 }
95
96 auto endorsementIndex = btcIndex->getHeight();
97 if (endorsementIndex >= earliestEndorsementIndex) {
98 continue;
99 }
100
101 bool EnableTimeAdjustment = tree.getParams().EnableTimeAdjustment();
102 if (!EnableTimeAdjustment ||
103 pkc.timestampOfEndorsedBlock < btcIndex->getTimestamp()) {
104 earliestEndorsementIndex = endorsementIndex;
105 continue;
106 }
107
108 // look at the future BTC blocks and set the
109 // earliestEndorsementIndex to a future Bitcoin block
110 auto best = tree.getBestChain();
111 for (int adjustedEndorsementIndex = endorsementIndex + 1;
112 adjustedEndorsementIndex <= best.chainHeight();
113 adjustedEndorsementIndex++) {
114 // Ensure that the keystone's block time isn't later than the
115 // block time of the Bitcoin block it's endorsed in
116 auto* index = best[adjustedEndorsementIndex];
117 VBK_ASSERT(index != nullptr);
118 if (pkc.timestampOfEndorsedBlock < index->getTimestamp()) {
119 // Timestamp of VeriBlock block is lower than Bitcoin block,
120 // set this as the adjusted index if another lower index has
121 // not already been set
122 if (adjustedEndorsementIndex < earliestEndorsementIndex) {
123 earliestEndorsementIndex = adjustedEndorsementIndex;
124 }
125
126 // Always break; we found a valid Bitcoin index and any
127 // future adjustedEndorsementIndex is going to be higher
128 break;
129 } // end if
130 } // end for
131 } // end for
132
133 return {pkc.blockHeight, earliestEndorsementIndex};
134}
135
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");
151
152 auto highestConnectingBlock =
153 highestBlockWhichConnectsKeystoneToPrevious(keystoneToConsider, ki);
154 auto highestPossibleEndorsedBlockHeaderHeight = tip->getHeight();
155 auto highestEndorsedBlock = std::min(
156 highestConnectingBlock, highestPossibleEndorsedBlockHeaderHeight);
157
158 ProtoKeystoneContext<ProtectingBlockT> pkc(
159 keystoneToConsider, chain[keystoneToConsider]->getTimestamp());
160
161 // Find the endorsements of the keystone block and other blocks which
162 // reference it, and look at the earliest Bitcoin block that any of those
163 // endorsements are contained within.
164 for (auto relevantEndorsedBlock = keystoneToConsider;
165 relevantEndorsedBlock <= highestEndorsedBlock;
166 relevantEndorsedBlock++) {
167 auto* index = chain[relevantEndorsedBlock];
168
169 // chain must contain relevantEndorsedBlock
170 VBK_ASSERT(index != nullptr);
171
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");
178
179 if (!chain.contains(containingBlock)) {
180 // do not count endorsement whose containingHash is not on the same
181 // chain as 'endorsedHash'
182 continue;
183 }
184
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)) {
190 continue;
191 }
192
193 // include only endorsements that are on best chain of protecting chain,
194 // and whose 'containingHash' is on the same chain as 'endorsedHash'
195 pkc.referencedByBlocks.insert(ind);
196
197 } // end for
198 } // end for
199
200 return pkc;
201}
202
204// AKA KeystoneContextList
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>;
219
220 const protected_chain_params_t& config;
221 const int keystoneInterval;
222 const protected_chain_t chain;
223
224 const protected_tree_t& protectedTree;
225 const protecting_tree_t& protectingTree;
226
227 const int firstKeystoneHeight;
228 const int lastKeystoneHeight;
229
230 KeystoneContext currentKeystoneContext; // FIXME: UGLY
231
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)
236 : config(_config),
237 keystoneInterval(config.getKeystoneInterval()),
238 chain(_chain),
239 protectedTree(_ed),
240 protectingTree(_ing),
241 firstKeystoneHeight(firstKeystoneAfterBlockIndex(chain.first())),
242 lastKeystoneHeight(highestKeystoneAtOrBeforeBlockIndex(chain.tip())) {
243 VBK_ASSERT(keystoneInterval > 0);
244 }
245
246 const protected_chain_params_t& getConfig() const { return config; }
247
248 size_t size() const {
249 return blockHeightToKeystoneNumber(lastKeystone(), keystoneInterval) -
250 blockHeightToKeystoneNumber(firstKeystone(), keystoneInterval) + 1;
251 }
252
253 bool empty() const { return size() == 0; }
254
255 int firstKeystone() const { return firstKeystoneHeight; }
256
257 int lastKeystone() const { return lastKeystoneHeight; }
258
259 int nextKeystoneAfter(int keystoneHeight) const {
260 VBK_ASSERT(isKeystone(keystoneHeight, keystoneInterval));
261 return keystoneHeight + keystoneInterval;
262 }
263
264 const KeystoneContext* operator[](int blockHeight) {
265 return getKeystone(blockHeight);
266 }
267
268 const KeystoneContext* getKeystone(int blockHeight) {
269 VBK_ASSERT_MSG(
270 isKeystone(blockHeight, keystoneInterval),
271 "getKeystone can not be called with a non-keystone block height");
272
273 if (blockHeight < firstKeystone() || blockHeight > lastKeystone()) {
274 return nullptr;
275 }
276
277 // FIXME: there's no need to store the intermediate list of blocks and thus
278 // no need for ProtoKeystoneContext entity
279 auto pkc = getProtoKeystoneContext(
280 blockHeight, chain, protectedTree, protectingTree, config);
281
282 currentKeystoneContext = getKeystoneContext(pkc, protectingTree);
283 return &currentKeystoneContext;
284 }
285
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);
290 }
291
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);
296 }
297};
298
299template <typename PublicationView>
300int comparePopScoreImpl(PublicationView& a, PublicationView& b) {
301 VBK_TRACE_ZONE_SCOPED;
302
303 if (a.empty() && b.empty()) {
304 return 0;
305 }
306
307 if (a.empty()) {
308 // a is empty, b is not
309 return -1;
310 }
311
312 if (b.empty()) {
313 // b is empty, a is not
314 return 1;
315 }
316
317 VBK_LOG_DEBUG(
318 "Comparing POP scores of chains A(first=%d, tip=%d) "
319 "and B(first=%d, tip=%d)",
320 a.firstKeystone(),
321 a.lastKeystone(),
322 b.firstKeystone(),
323 b.lastKeystone());
324
325 int earliestKeystone = a.firstKeystone();
326 VBK_ASSERT(earliestKeystone == b.firstKeystone());
327 int latestKeystone = (std::max)(a.lastKeystone(), b.lastKeystone());
328
329 auto& config = a.getConfig();
330 VBK_ASSERT(&config == &b.getConfig()); // FIXME: comparing pointers is UGLY
331 // clang-format off
332 // If either chain has a keystone the other chain is missing, the other chain
333 // goes outside finality.
334 //
335 // Example:
336 // Chain A has keystones VBK20:BTC100, VBK40:BTC101, VBK60:BTC103, VBK100:BTC104
337 // Chain B has keystones VBK20:BTC100, VBK40:BTC101, VBK100:BTC102
338 //
339 // Chain A has keystone 60 but chain B does not, so Chain B is
340 // chopped to VBK20:BTC100, VBK40:BTC101.
341
342 // Also, the chain goes out of finality if it contains a keystone which violates
343 // the Bitcoin finality delay based on the previous keystone in the chain.
344 // clang-format on
345
346 bool aOutsideFinality = false;
347 bool bOutsideFinality = false;
348 int chainAscore = 0;
349 int chainBscore = 0;
350 int previousPublicationA = NO_ENDORSEMENT;
351 int previousPublicationB = NO_ENDORSEMENT;
352
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);
358
359 int earliestPublicationA =
360 actx == nullptr ? NO_ENDORSEMENT : actx->firstBlockPublicationHeight;
361 int earliestPublicationB =
362 bctx == nullptr ? NO_ENDORSEMENT : bctx->firstBlockPublicationHeight;
363
364 // if keystoneToCompare is right after a gap, publicationViolatesFinality
365 // will return false, thus we only compare the finality delay violations
366 // between endorsements that exist
367 if (actx != nullptr &&
368 publicationViolatesFinality(
369 earliestPublicationA, previousPublicationA, config)) {
370 aOutsideFinality = true;
371 actx = nullptr;
372 VBK_LOG_DEBUG(
373 "Chain A is outside finality: the gap between adjacent keystone "
374 "endorsements is too large");
375 }
376 previousPublicationA = earliestPublicationA;
377
378 if (bctx != nullptr &&
379 publicationViolatesFinality(
380 earliestPublicationB, previousPublicationB, config)) {
381 bOutsideFinality = true;
382 bctx = nullptr;
383 VBK_LOG_DEBUG(
384 "Chain B is outside finality: the gap between adjacent keystone "
385 "endorsements is too large");
386 }
387 previousPublicationB = earliestPublicationB;
388
389 // if both chains are missing keystone at this height, ignore
390 // if both are outside finality, break
391 if (actx == nullptr && bctx == nullptr) {
392 if (aOutsideFinality && bOutsideFinality) {
393 break;
394 }
395 continue;
396 }
397
398 if (actx == nullptr) {
399 chainBscore += config.getForkResolutionLookUpTable()[0];
400 // Nothing added to chainA; it doesn't have an endorsed keystone at
401 // this height (or any additional height)
402
403 // chain A is missing the keystone that chain B isn't
404 aOutsideFinality = true;
405
406 if (chainBscore > chainAscore) {
407 // exit early as the score of A will stay the same
408 break;
409 }
410
411 continue;
412 }
413
414 if (bctx == nullptr) {
415 chainAscore += config.getForkResolutionLookUpTable()[0];
416
417 // chain B is missing the keystone that chain A isn't
418 bOutsideFinality = true;
419
420 if (chainAscore > chainBscore) {
421 // exit early as the score of chain B will stay the same
422 break;
423 }
424
425 continue;
426 }
427
428 // both chains have a keystone at this height
429
430 int earliestPublicationOfEither =
431 (std::min)(earliestPublicationA, earliestPublicationB);
432
433 chainAscore += getConsensusScoreFromRelativeBlockStartingAtZero(
434 earliestPublicationA - earliestPublicationOfEither, config);
435 chainBscore += getConsensusScoreFromRelativeBlockStartingAtZero(
436 earliestPublicationB - earliestPublicationOfEither, config);
437
438 if (publicationViolatesFinality(
439 earliestPublicationA, earliestPublicationB, config)) {
440 VBK_LOG_DEBUG("Chain A is outside finality: way behind chain B");
441 aOutsideFinality = true;
442 }
443
444 if (publicationViolatesFinality(
445 earliestPublicationB, earliestPublicationA, config)) {
446 VBK_LOG_DEBUG("Chain B is outside finality: way behind chain A");
447 bOutsideFinality = true;
448 }
449 }
450
451 VBK_LOG_DEBUG("ChainA score=%d, ChainB score=%d", chainAscore, chainBscore);
452 return chainAscore - chainBscore;
453}
454
455} // namespace internal
456
457enum class PopFrOutcome {
458 UNKNOWN,
459 CANDIDATE_IS_TIP,
460 CANDIDATE_INVALID_CHAIN,
461 CANDIDATE_INVALID_PAYLOADS,
462 CANDIDATE_PART_OF_ACTIVE_CHAIN,
463 CANDIDATE_IS_TIP_SUCCESSOR,
464 TIP_IS_FINAL,
465 BOTH_DONT_CROSS_KEYSTONE_BOUNDARY,
466 CANDIDATE_INVALID_INDEPENDENTLY,
467 HIGHER_POP_SCORE,
468};
469
470std::string popFrOutcomeToString(PopFrOutcome value,
471 const ValidationState& state);
472
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,
488 ProtectedBlockTree,
489 BlockIndex<protected_block_t>,
490 protected_params_t>;
491
492 using reduced_publication_view_t =
493 internal::ReducedPublicationView<protected_block_t,
494 protecting_block_t,
495 protecting_params_t,
496 protected_params_t,
497 protected_block_tree_t>;
498
499 PopAwareForkResolutionComparator(ProtectedBlockTree& ed,
500 std::shared_ptr<ProtectingBlockTree> ing,
501 const protected_params_t& protectedParams,
502 PayloadsStorage& payloadsProvider)
503 : ed_(ed),
504 ing_(std::move(ing)),
505 protectedParams_(protectedParams),
506 payloadsProvider_(payloadsProvider),
507 sm_(ed_, *ing_) {
508 VBK_ASSERT(protectedParams.getKeystoneInterval() > 0);
509 }
510
511 ProtectingBlockTree& getProtectingBlockTree() { return *ing_; }
512 const ProtectingBlockTree& getProtectingBlockTree() const { return *ing_; }
513
518 bool setState(protected_index_t& to, ValidationState& state) {
519 VBK_TRACE_ZONE_SCOPED;
520
521 auto* currentActive = ed_.getBestChain().tip();
522 VBK_ASSERT(currentActive != nullptr && "should be bootstrapped");
523
524 VBK_ASSERT_MSG(
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");
529
530 if (currentActive == &to) {
531 // already at this state
532 return true;
533 }
534
535 auto guard = ing_->deferForkResolutionGuard();
536 auto* originalTip = ing_->getBestChain().tip();
537
538 if (sm_.setState(*currentActive, to, state)) {
539 return true;
540 }
541
542 guard.overrideDeferredForkResolution(originalTip);
543 return false;
544 }
545
553 std::pair<int, PopFrOutcome> comparePopScore(protected_index_t& candidate,
554 ValidationState& state) {
555 VBK_TRACE_ZONE_SCOPED;
556
557 if (!candidate.isValid()) {
558 // if the new block is known to be invalid, we always return "A is better"
559 VBK_LOG_DEBUG("Candidate %s is invalid, the current chain wins",
560 candidate.toShortPrettyString());
561 return {1, PopFrOutcome::CANDIDATE_INVALID_CHAIN};
562 }
563 auto currentBest = ed_.getBestChain();
564 auto bestTip = currentBest.tip();
565 VBK_ASSERT(bestTip && "must be bootstrapped");
566
567 if (bestTip == &candidate) {
568 // we are comparing the best chain to itself
569 return {1, PopFrOutcome::CANDIDATE_IS_TIP};
570 }
571
572 if (bestTip->finalized && candidate.getHeight() <= bestTip->getHeight()) {
573 // finalized blocks can not be reorganized
574 return {1, PopFrOutcome::TIP_IS_FINAL};
575 }
576
577 if (currentBest.contains(&candidate)) {
578 VBK_LOG_DEBUG(
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};
582 }
583
584 auto originalProtectingTip = ing_->getBestChain().tip();
585
586 // candidate is on top of our best tip
587 if (candidate.getAncestor(bestTip->getHeight()) == bestTip) {
588 VBK_LOG_DEBUG("Candidate is %d blocks ahead",
589 candidate.getHeight() - bestTip->getHeight());
590
591 auto guard = ing_->deferForkResolutionGuard();
592
593 if (!sm_.apply(*bestTip, candidate, state)) {
594 // new chain is invalid. our current chain is definitely better.
595 VBK_LOG_DEBUG("Candidate contains INVALID command(s): %s",
596 state.toString());
597 guard.overrideDeferredForkResolution(originalProtectingTip);
598 return {1, PopFrOutcome::CANDIDATE_INVALID_PAYLOADS};
599 }
600
601 VBK_LOG_DEBUG("Candidate contains VALID commands, chain B wins");
602 return {-1, PopFrOutcome::CANDIDATE_IS_TIP_SUCCESSOR};
603 }
604
605 VBK_LOG_DEBUG("Doing %s POP fork resolution. Best=%s, Candidate=%s",
606 protected_block_t::name(),
607 bestTip->toShortPrettyString(),
608 candidate.toShortPrettyString());
609
610 auto ki = ed_.getParams().getKeystoneInterval();
611 const auto* fork = findFork(currentBest, &candidate);
612 VBK_ASSERT_MSG(
613 fork != nullptr,
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());
618
619 // if block next to fork (on active chain) is finalized, we don't do FR,
620 // since it can not be reorganized.
621 auto* nextToFork = currentBest[fork->getHeight() + 1];
622 if (nextToFork != nullptr && nextToFork->finalized) {
623 // block `fork+1` is on active chain, and ancestor of currentBest.
624 // which means that `fork+1` can not be reorganized and candidate will
625 // never win.
626 return {1, PopFrOutcome::TIP_IS_FINAL};
627 }
628
629 // perform multi-keystone PoP fork resolution
630 bool AcrossedKeystoneBoundary =
631 isCrossedKeystoneBoundary(fork->getHeight(), bestTip->getHeight(), ki);
632 bool BcrossedKeystoneBoundary =
633 isCrossedKeystoneBoundary(fork->getHeight(), candidate.getHeight(), ki);
634 if (!AcrossedKeystoneBoundary && !BcrossedKeystoneBoundary) {
635 // chains are equal in terms of POP
636 VBK_LOG_DEBUG(
637 "Neither chain crossed a keystone boundary: chains are equal");
638 return {0, PopFrOutcome::BOTH_DONT_CROSS_KEYSTONE_BOUNDARY};
639 }
640
641 // [vbk fork point ... current tip]
642 ChainSlice<protected_index_t> chainA(currentBest, fork->getHeight());
643 // [vbk fork point ... new block]
644 Chain<protected_index_t> chainB(fork->getHeight(), &candidate);
645
646 // chains are not empty and chains start at the same block
647 VBK_ASSERT(chainA.first() != nullptr && chainA.first() == chainB.first());
648
649 // we ALWAYS compare the currently applied chain (chainA) and the candidate
650 // (chainB)
651 VBK_ASSERT(chainA.tip() == bestTip);
652
653 // we are at chainA.
654 // apply all payloads from chain B (both chains have same first block - the
655 // fork point, so exclude it during 'apply')
656 {
657 auto guard = ing_->deferForkResolutionGuard();
658
659 if (!sm_.apply(chainB, state)) {
660 // chain B has been unapplied and invalidated already
661 VBK_LOG_DEBUG("Chain B contains INVALID payloads, Chain A wins (%s)",
662 state.toString());
663 guard.overrideDeferredForkResolution(originalProtectingTip);
664 return {1, PopFrOutcome::CANDIDATE_INVALID_PAYLOADS};
665 }
666 }
667
668 // now the tree contains payloads from both chains
669 auto reducedPublicationViewA =
670 reduced_publication_view_t(chainA, protectedParams_, ed_, *ing_);
671 auto reducedPublicationViewB =
672 reduced_publication_view_t(chainB, protectedParams_, ed_, *ing_);
673
674 int result = internal::comparePopScoreImpl(reducedPublicationViewA,
675 reducedPublicationViewB);
676 if (result >= 0) {
677 // chain A remains the best one. unapply B and leave A applied
678 auto guard = ing_->deferForkResolutionGuard();
679 sm_.unapply(chainB);
680 guard.overrideDeferredForkResolution(originalProtectingTip);
681 VBK_LOG_DEBUG("Chain A remains the best chain");
682 } else {
683 // chain B is better. unapply A and leave B applied
684 auto guard = ing_->deferForkResolutionGuard();
685
686 // if part of chainB has uncertain validity(never been applied before with
687 // setState()), we have to unapply this part before unapplying chainA and
688 // try to apply the unvalidated part of chainB without any payloads from
689 // chainA to make sure it is fully valid
690 auto& chainBValidFrom =
691 sm_.unapplyWhile(chainB, [](protected_index_t& index) -> bool {
692 return !index.isValid(BLOCK_CAN_BE_APPLIED);
693 });
694
695 // unapply losing chain
696 sm_.unapply(chainA);
697
698 // validate the unvalidated part of chainB
699 if (!sm_.apply(chainBValidFrom, *chainB.tip(), state)) {
700 sm_.unapply(chainBValidFrom, *chainB.first());
701 bool success = sm_.apply(*chainA.first(), *chainA.tip(), state);
702 VBK_ASSERT_MSG(
703 success,
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};
708 }
709
710 VBK_LOG_DEBUG("Chain B wins");
711 }
712
713 return {result, PopFrOutcome::HIGHER_POP_SCORE};
714 }
715
716 std::string toPrettyString(size_t level = 0) const {
717 std::string pad(level, ' ');
718 return format("{}Comparator{{\n{}{{tree=\n{}}}}}",
719 pad,
720 pad,
721 ing_->toPrettyString(level + 2));
722 }
723
724 private:
725 ProtectedBlockTree& ed_;
726 std::shared_ptr<ProtectingBlockTree> ing_;
727
728 const protected_params_t& protectedParams_;
729 PayloadsStorage& payloadsProvider_;
730 sm_t sm_;
731};
732
733} // namespace altintegration
734
735#endif // ALT_INTEGRATION_INCLUDE_VERIBLOCK_BLOCKCHAIN_POP_FORK_RESOLUTION_HPP_
Defines logging helpers.
Definition: block.hpp:14
@ 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.
Definition: chain.hpp:144
bool isKeystone(int32_t blockNumber, uint32_t keystoneInterval)
Calculates if block with height = blockNumber is keystone.