veriblock-pop-cpp
C++11 Libraries for leveraging VeriBlock Proof-Of-Proof blockchain technology.
base_block_tree.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 ALTINTEGRATION_BASE_BLOCK_TREE_HPP
7#define ALTINTEGRATION_BASE_BLOCK_TREE_HPP
8
9#include <limits>
10#include <unordered_map>
11#include <unordered_set>
12
13#include "block_index.hpp"
14#include "blockchain_util.hpp"
15#include "chain.hpp"
16#include "payloads_index.hpp"
17#include "tree_algo.hpp"
18#include "veriblock/pop/algorithm.hpp"
19#include "veriblock/pop/assert.hpp"
20#include "veriblock/pop/fmt.hpp"
21#include "veriblock/pop/logger.hpp"
22#include "veriblock/pop/signals.hpp"
23#include "veriblock/pop/storage/block_reader.hpp"
24#include "veriblock/pop/storage/stored_block_index.hpp"
25#include "veriblock/pop/trace.hpp"
26
27namespace altintegration {
28
34template <typename Block>
36 using block_t = Block;
37 using block_height_t = typename block_t::height_t;
38 using hash_t = typename Block::hash_t;
39 using prev_block_hash_t = typename Block::prev_hash_t;
42 using on_invalidate_t = void(const index_t&);
43 using block_index_t =
44 std::unordered_map<prev_block_hash_t, std::unique_ptr<index_t>>;
45
46 const std::unordered_set<index_t*>& getTips() const { return tips_; }
47 std::vector<index_t*> getBlocks() const {
48 std::vector<index_t*> blocks;
49 blocks.reserve(blocks_.size());
50 for (const auto& el : blocks_) {
51 if (!el.second->isDeleted()) {
52 blocks.push_back(el.second.get());
53 }
54 }
55 return blocks;
56 }
57
58 std::vector<index_t*> getAllBlocks() const {
59 std::vector<index_t*> blocks;
60 blocks.reserve(blocks_.size());
61 for (const auto& el : blocks_) {
62 blocks.push_back(el.second.get());
63 }
64 return blocks;
65 }
66
67 const BlockReader& getBlockProvider() const { return blockProvider_; }
68
69 virtual ~BaseBlockTree() {
70 if (!isBootstrapped()) {
71 return;
72 }
73
74 deallocateTree(getRoot());
75 }
76
77 BaseBlockTree(const BlockReader& blockProvider)
78 : blockProvider_(blockProvider) {}
79
80 // non-copyable
81 BaseBlockTree(const BaseBlockTree&) = delete;
82 BaseBlockTree& operator=(const BaseBlockTree&) = delete;
83
84 // movable
85 // NOLINTNEXTLINE(performance-noexcept-move-constructor)
86 BaseBlockTree(BaseBlockTree&&) = default;
87 // NOLINTNEXTLINE(performance-noexcept-move-constructor)
88 BaseBlockTree& operator=(BaseBlockTree&&) = default;
89
94 const Chain<index_t>& getBestChain() const { return this->activeChain_; }
95
96 // HACK: see big comment in VBK tree.
97 template <typename T>
98 inline prev_block_hash_t makePrevHash(const T& h) const {
99 // given any type T, just return an implicit cast to prev_block_hash_t
100 return h;
101 }
102
109 template <typename T,
110 typename = typename std::enable_if<
111 std::is_same<T, hash_t>::value ||
112 std::is_same<T, prev_block_hash_t>::value>::type>
113 index_t* getBlockIndex(const T& hash) {
114 const auto& t = as_const(*this);
115 return const_cast<index_t*>(t.getBlockIndex(hash));
116 }
117
119 template <typename T,
120 typename = typename std::enable_if<
121 std::is_same<T, hash_t>::value ||
122 std::is_same<T, prev_block_hash_t>::value>::type>
123 const index_t* getBlockIndex(const T& hash) const {
124 auto blockIndex = findBlockIndex(hash);
125 return blockIndex != nullptr && blockIndex->isDeleted() ? nullptr
126 : blockIndex;
127 }
128
135 template <typename T,
136 typename = typename std::enable_if<
137 std::is_same<T, hash_t>::value ||
138 std::is_same<T, prev_block_hash_t>::value>::type>
139 index_t* findBlockIndex(const T& hash) {
140 const auto& t = as_const(*this);
141 return const_cast<index_t*>(t.findBlockIndex(hash));
142 }
143
145 template <typename T,
146 typename = typename std::enable_if<
147 std::is_same<T, hash_t>::value ||
148 std::is_same<T, prev_block_hash_t>::value>::type>
149 const index_t* findBlockIndex(const T& hash) const {
150 VBK_TRACE_ZONE_SCOPED;
151 auto shortHash = makePrevHash(hash);
152 auto it = blocks_.find(shortHash);
153 return it == blocks_.end() ? nullptr : it->second.get();
154 }
155
156 virtual bool loadTip(const hash_t& hash, ValidationState& state) {
157 VBK_TRACE_ZONE_SCOPED;
158 VBK_ASSERT_MSG(this->isLoadingBlocks_,
159 "%s tree must be in LoadingBlocks state!",
160 block_t::name());
161 VBK_ASSERT_MSG(
162 !this->isLoaded_, "%s tree is already loaded!", block_t::name());
163
164 auto* tip = getBlockIndex(hash);
165 if (tip == nullptr) {
166 return state.Invalid(
167 block_t::name() + "-no-tip",
168 format("tip {} doesn't exist in block tree", HexStr(hash)));
169 }
170
171 this->overrideTip(*tip);
172
173 // we no longer can execute loadTip/loadBlocks
174 this->isLoaded_ = true;
175 this->isLoadingBlocks_ = false;
176 return true;
177 }
178
189 virtual bool loadBlockForward(const stored_index_t& index,
190 bool fast_load,
191 ValidationState& state) {
192 return loadBlockInner(index, true, fast_load, state);
193 }
194
200 void removeSubtree(index_t& toRemove) {
201 VBK_TRACE_ZONE_SCOPED;
202 VBK_LOG_DEBUG("remove subtree %s", toRemove.toPrettyString());
203
204 VBK_ASSERT_MSG(!toRemove.isRoot(), "cannot remove the root block");
205 // save the pointer to the previous block
206 auto* prev = toRemove.pprev;
207
208 bool isOnMainChain = activeChain_.contains(&toRemove);
209 if (isOnMainChain) {
210 ValidationState dummy;
211 bool success = this->setState(*prev, dummy);
212 VBK_ASSERT_MSG(success, "err: %s", dummy.toString());
213 }
214
215 forEachNodePostorder<block_t>(
216 toRemove,
217 [&](index_t& next) {
218 onBeforeLeafRemoved(next);
219 tips_.erase(&next);
220 VBK_ASSERT(!next.isDeleted());
221 next.deleteTemporarily();
222 },
223 [&](index_t& next) { return !next.isDeleted(); });
224
225 // after removal, try to add tip
226 tryAddTip(prev);
227
228 if (isOnMainChain) {
229 updateTips();
230 }
231 }
232
235 void removeSubtree(const hash_t& toRemove) {
236 auto* index = getBlockIndex(toRemove);
237 VBK_ASSERT_MSG(
238 index, "cannot find the subtree to remove: %s", HexStr(toRemove));
239 return this->removeSubtree(*index);
240 }
241
243 void removeLeaf(index_t& toRemove) {
244 auto nondeletedDescendantCount = toRemove.nondeletedDescendantCount();
245
246 VBK_ASSERT_MSG(nondeletedDescendantCount == 0,
247 "not a leaf block %s, has %d non-deleted descendants",
248 toRemove.toPrettyString(),
249 nondeletedDescendantCount);
250 return this->removeSubtree(toRemove);
251 }
252
265 void invalidateSubtree(index_t& toBeInvalidated,
266 enum BlockValidityStatus reason,
267 bool shouldDetermineBestChain = true) {
268 VBK_TRACE_ZONE_SCOPED;
269 VBK_LOG_DEBUG("Invalidating %s subtree: reason=%d block=%s",
270 block_t::name(),
271 (int)reason,
272 toBeInvalidated.toShortPrettyString());
273
274 VBK_ASSERT_MSG(!toBeInvalidated.isRoot(),
275 "cannot invalidate the root block");
276
277 VBK_ASSERT(isValidInvalidationReason(reason) &&
278 "invalid invalidation reason");
279
280 if (toBeInvalidated.hasFlags(reason)) {
281 return;
282 }
283
284 // all descendants of an invalid block are already flagged as
285 // BLOCK_FAILED_CHILD
286 if (!toBeInvalidated.isValid()) {
287 doInvalidate(toBeInvalidated, reason);
288 return;
289 }
290
291 bool isOnMainChain = activeChain_.contains(&toBeInvalidated);
292 if (isOnMainChain) {
293 ValidationState dummy;
294 bool success = this->setState(*toBeInvalidated.pprev, dummy);
295 VBK_ASSERT(success);
296 }
297
298 doInvalidate(toBeInvalidated, reason);
299
300 // flag the child subtrees (excluding current block) as BLOCK_FAILED_CHILD
301 for (auto* ptr : toBeInvalidated.pnext) {
302 auto* pnext = ptr;
303 forEachNodePreorder<block_t>(*pnext, [&](index_t& index) {
304 bool failed = index.isFailed();
305 doInvalidate(index, BLOCK_FAILED_CHILD);
306 return !failed;
307 });
308 }
309
310 // after invalidation, the previous block might have become a tip
311 tryAddTip(toBeInvalidated.pprev);
312
313 if (shouldDetermineBestChain) {
314 updateTips();
315 }
316 }
317
320 void invalidateSubtree(const hash_t& toBeInvalidated,
321 enum BlockValidityStatus reason,
322 bool shouldDetermineBestChain = true) {
323 auto* index = getBlockIndex(toBeInvalidated);
324 VBK_ASSERT(index && "cannot find the subtree to invalidate");
325 return invalidateSubtree(*index, reason, shouldDetermineBestChain);
326 }
327
328 void revalidateSubtree(const hash_t& hash,
329 enum BlockValidityStatus reason,
330 bool shouldDetermineBestChain = true) {
331 auto* index = this->getBlockIndex(hash);
332 VBK_ASSERT(index && "cannot find the subtree to revalidate");
333 revalidateSubtree(*index, reason, shouldDetermineBestChain);
334 }
335
336 /*
337 * `tobeValidated` does NOT necessarily become valid after a call to this
338 * function.
339 */
340 void revalidateSubtree(index_t& toBeValidated,
341 enum BlockValidityStatus reason,
342 bool shouldDetermineBestChain = true) {
343 VBK_TRACE_ZONE_SCOPED;
344 VBK_LOG_DEBUG("Revalidating %s subtree: reason=%d block=%s",
345 block_t::name(),
346 (int)reason,
347 toBeValidated.toShortPrettyString());
348
349 VBK_ASSERT_MSG(!toBeValidated.isRoot(), "cannot revalidate the root block");
350
351 VBK_ASSERT_MSG(isValidInvalidationReason(reason),
352 "invalid revalidation reason");
353
354 if (!toBeValidated.hasFlags(reason)) {
355 return;
356 }
357
358 // if the block has any invalidity flags other than `reason`, its
359 // descendants are already flagged as BLOCK_FAILED_CHILD and should stay so
360 if (toBeValidated.hasFlags(static_cast<enum BlockValidityStatus>(
361 BLOCK_FAILED_MASK & ~reason))) {
362 doReValidate(toBeValidated, reason);
363 return;
364 }
365
366 doReValidate(toBeValidated, reason);
367
368 for (auto* pnext : toBeValidated.pnext) {
369 forEachNodePreorder<block_t>(*pnext, [&](index_t& index) -> bool {
370 doReValidate(index, BLOCK_FAILED_CHILD);
371 bool failed = index.isFailed();
372 return !failed;
373 });
374 }
375
376 if (shouldDetermineBestChain) {
377 updateTips();
378 }
379 }
380
386 bool isBootstrapped() const {
387 if (!blocks_.empty() && activeChain_.tip() != nullptr) {
388 return true;
389 }
390
391 if (blocks_.empty() && activeChain_.tip() == nullptr) {
392 return false;
393 }
394
395 VBK_ASSERT_MSG(
396 false,
397 "state corruption: the blockchain is neither bootstrapped nor empty");
398 return false;
399 }
400
401 virtual bool setState(const hash_t& block, ValidationState& state) {
402 auto* index = getBlockIndex(block);
403 if (index == nullptr) {
404 return state.Invalid(block_t::name() + "-setstate-unknown-block",
405 "could not find the block to set the state to");
406 }
407 return setState(*index, state);
408 }
409
410 virtual bool setState(index_t& index, ValidationState&) {
411 VBK_TRACE_ZONE_SCOPED;
412 overrideTip(index);
413 return true;
414 }
415
416 virtual void overrideTip(index_t& to) {
417 onBeforeOverrideTip.emit(to);
418 activeChain_.setTip(&to);
419 appliedBlockCount = activeChain_.blocksCount();
420 }
421
422 index_t& getRoot() const {
423 VBK_ASSERT_MSG(isBootstrapped(), "must be bootstrapped");
424 auto* root = getBestChain().first();
425 VBK_ASSERT_MSG(root, "must be bootstrapped");
426 return *root;
427 }
428
431 size_t appliedBlockCount = 0;
432
433 protected:
435 void finalizeBlocks(
436 int32_t maxReorgBlocks,
437 int32_t preserveBlocksBehindFinal,
438 // we should not finalize blocks
439 // above this height
440 int32_t maxFinalizeBlockHeight = std::numeric_limits<int32_t>::max()) {
441 auto* tip = this->getBestChain().tip();
442 VBK_ASSERT_MSG(tip, "%s tree must be bootstrapped", block_t::name());
443 VBK_ASSERT(!this->isLoadingBlocks_);
444
445 if (tip->getHeight() < maxReorgBlocks) {
446 // skip finalization
447 return;
448 }
449
450 int32_t firstBlockHeight = tip->getHeight() - maxReorgBlocks;
451 int32_t bootstrapBlockHeight = this->getRoot().getHeight();
452 firstBlockHeight = std::max(bootstrapBlockHeight, firstBlockHeight);
453 auto* finalizedIndex = this->getBestChain()[firstBlockHeight];
454 VBK_ASSERT(finalizedIndex != nullptr);
455 if (finalizedIndex->getHeight() >= maxFinalizeBlockHeight) {
456 // we should not finalize blocks with height higher than
457 // maxFinalizeBlockHeight
458 VBK_LOG_INFO(
459 "Skipping finalization of %s because its height >= %d (max "
460 "possible height to finalize)",
461 finalizedIndex->toShortPrettyString(),
462 maxFinalizeBlockHeight);
463 return;
464 }
465
466 this->finalizeBlockImpl(*finalizedIndex, preserveBlocksBehindFinal);
467 }
468
470 virtual void determineBestChain(index_t& candidate,
471 ValidationState& state) = 0;
472
474 void tryAddTip(index_t* index) {
475 VBK_TRACE_ZONE_SCOPED;
476 VBK_ASSERT(index);
477
478 if (!index->isValidTip()) {
479 return;
480 }
481
482 auto it = tips_.find(index->pprev);
483 if (it != tips_.end()) {
484 // remove the previous block from the valid tip set as it can no longer be
485 // a valid tip
486 tips_.erase(it);
487 }
488
489 tips_.insert(index);
490 }
491
498 VBK_TRACE_ZONE_SCOPED;
499 VBK_ASSERT_MSG(block.isDeleted(),
500 "cannot erase a block that is not deleted %s",
501 block.toPrettyString());
502 VBK_ASSERT_MSG(
503 block.isTip(),
504 "cannot erase a block that has ancestors as that would split "
505 "the tree: %s",
506 block.toPrettyString());
507
508 onBlockBeforeDeallocated.emit(block);
509 auto shortHash = makePrevHash(block.getHash());
510 auto count = blocks_.erase(shortHash);
511 VBK_ASSERT_MSG(count == 1,
512 "state corruption: block %s is not in the store",
513 block.toPrettyString());
514 }
515
518 index_t* createBlockIndex(const hash_t& hash, index_t& prev) {
519 VBK_TRACE_ZONE_SCOPED;
520 auto shortHash = makePrevHash(hash);
521 auto newIndex = make_unique<index_t>(&prev);
522 auto inserted = blocks_.emplace(shortHash, std::move(newIndex));
523 VBK_ASSERT_MSG(inserted.second,
524 "attempted to create a blockindex with duplicate hash %s",
525 HexStr(shortHash));
526 return inserted.first->second.get();
527 }
528
531 index_t* createBootstrapBlockIndex(const hash_t& hash,
532 block_height_t height) {
533 VBK_TRACE_ZONE_SCOPED;
534 auto shortHash = makePrevHash(hash);
535 auto newIndex = make_unique<index_t>(height);
536 auto inserted = blocks_.emplace(shortHash, std::move(newIndex));
537 VBK_ASSERT_MSG(inserted.second,
538 "attempted to create a blockindex with duplicate hash %s",
539 HexStr(shortHash));
540 auto* index = inserted.first->second.get();
541 // bootstrap blocks are finalized by default
542 index->finalized = true;
543 return index;
544 }
545
548 index_t* doInsertBlockHeader(const std::shared_ptr<block_t>& header,
549 block_height_t bootstrapHeight = 0) {
550 VBK_TRACE_ZONE_SCOPED;
551 VBK_ASSERT(header != nullptr);
552
553 auto* prev = getBlockIndex(header->getPreviousBlock());
554
555 VBK_ASSERT_MSG(isBootstrapped() == (prev != nullptr),
556 "already bootstrapped");
557
558 index_t* current =
559 prev == nullptr
560 ? createBootstrapBlockIndex(header->getHash(), bootstrapHeight)
561 : createBlockIndex(header->getHash(), *prev);
562 current->setHeader(std::move(header));
563
564 return current;
565 }
566
568 index_t* insertBlockHeader(const std::shared_ptr<block_t>& block,
569 block_height_t bootstrapHeight = 0) {
570 VBK_TRACE_ZONE_SCOPED;
571
572 assertBlockSanity(*block);
573
574 auto hash = block->getHash();
575 index_t* current = findBlockIndex(hash);
576
577 if (current != nullptr) {
578 // it is a duplicate
579 if (!current->isDeleted()) {
580 return current;
581 }
582
583 VBK_ASSERT_MSG(*block == current->getHeader(),
584 "hash collision detected between %s and %s",
585 block->toPrettyString(),
586 current->getHeader().toPrettyString());
587 } else {
588 current = doInsertBlockHeader(block, bootstrapHeight);
589 }
590
591 current->restore();
592 tryAddTip(current);
593
594 this->onBlockInserted(current);
595
596 // raise validity may return false if the block is invalid
597 current->raiseValidity(BLOCK_VALID_TREE);
598 return current;
599 }
600
602 bool loadBlockInner(const stored_index_t& index,
603 bool connectForward,
604 bool fast_load,
605 ValidationState& state) {
606 VBK_TRACE_ZONE_SCOPED;
607
608 VBK_ASSERT_MSG(isBootstrapped(), "should be bootstrapped");
609 VBK_ASSERT_MSG(
610 !this->isLoaded_, "%s tree must not be loaded", block_t::name());
611
612 // indicate that we're in "loadTree" state.
613 this->isLoadingBlocks_ = true;
614
615 // quick check if given block is sane
616 auto& root = getRoot();
617 if (!fast_load) {
618 if (connectForward) {
619 if (index.height < root.getHeight()) {
620 auto hash = index.header->getHash();
621 return state.Invalid(
622 "bad-height",
623 format("Blocks can be forward connected after root only. "
624 "index.height: {}, root.height: {}, index.hash: {}",
625 index.height,
626 root.getHeight(),
627 HexStr(hash.begin(), hash.end())));
628 }
629 } else {
630 if (index.height + 1 != root.getHeight()) {
631 auto hash = index.header->getHash();
632 return state.Invalid(
633 "bad-height",
634 format("Blocks can be backwards connected only to the "
635 "root, index.height: {}, root.height: {}, index.hash: {}",
636 index.height,
637 root.getHeight(),
638 HexStr(hash.begin(), hash.end())));
639 }
640 }
641 }
642
643 if (!fast_load && index.height == root.getHeight() &&
644 index.header->getHash() != root.getHash()) {
645 // root is finalized, we can't load a block on same height
646 return state.Invalid("bad-root",
647 format("Can't overwrite root block with block {}",
648 index.toPrettyString()));
649 }
650
651 auto& header = *index.header;
652 auto currentHash = header.getHash();
653 auto* current = findBlockIndex(currentHash);
654
655 // we can not load a block, which already exists on chain and is not a
656 // bootstrap block
657 if (!fast_load && current != nullptr && !current->isDeleted() &&
658 !current->hasFlags(BLOCK_BOOTSTRAP)) {
659 return state.Invalid(
660 "block-exists",
661 "Found duplicate block, which is not bootstrap block");
662 }
663
664 if (current != nullptr) {
665 if (current->isDeleted()) {
666 current->restore();
667 }
668 } else {
669 auto* prev = getBlockIndex(header.getPreviousBlock());
670 // we can not load block which is not connected to the
671 // bootstrap block
672 if (prev == nullptr) {
673 if (!fast_load) {
674 if (connectForward) {
675 return state.Invalid("bad-prev",
676 "Block does not connect to current tree");
677 }
678
679 if (root.getHeader().getPreviousBlock() !=
680 makePrevHash(currentHash)) {
681 return state.Invalid(
682 "bad-block-hash",
683 format("Can't replace root block with block {} ",
684 index.toPrettyString()));
685 }
686 }
687
688 current = createBootstrapBlockIndex(currentHash, index.height);
689 current->restore();
690 current->pnext.insert(&root);
691 VBK_ASSERT(root.isRoot());
692 root.pprev = current;
693
694 } else {
695 current = createBlockIndex(currentHash, *prev);
696 current->restore();
697 }
698 }
699
700 VBK_ASSERT(current);
701 VBK_ASSERT(!current->isDeleted());
702
703 // touchBlockIndex may return an existing block (one of bootstrap blocks),
704 // so backup its 'pnext'
705 auto next = current->pnext;
706 auto prev = current->pprev;
707 // copy all fields
708 current->mergeFrom(index);
709 // FIXME: this is an ugly HACK
710 // clear inmem fields
711 current->setNullInmemFields();
712 // recover pnext
713 current->pnext = next;
714 // recover pprev
715 current->pprev = prev;
716
717 // check for valid previous block
718 if (!fast_load && current->pprev != nullptr) {
719 // prev block found
720 auto expectedHeight = current->pprev->getHeight() + 1;
721 if (current->getHeight() != expectedHeight) {
722 return state.Invalid("bad-height");
723 }
724 }
725
726 // set a new bootstrap block which is the prev block for the current
727 // bootstrap block
728 if ((current->pprev == nullptr) && (activeChain_.first() != current) &&
729 !connectForward) {
730 activeChain_.prependRoot(current);
731 }
732
733 VBK_ASSERT(!current->isDeleted());
734 current->raiseValidity(BLOCK_VALID_TREE);
735 current->unsetDirty();
736
737 tryAddTip(current);
738
739 return true;
740 }
741
742 std::string toPrettyString(size_t level = 0) const {
743 auto tip = activeChain_.tip();
744 std::string pad(level, ' ');
745
746 std::string blocksStr{};
747 // sort blocks by height
748 std::vector<std::pair<int, index_t*>> byheight;
749 byheight.reserve(blocks_.size());
750 for (const auto& p : blocks_) {
751 // FIXME: ugly hack due to trees being compared via toPrettyString in
752 // tests
753 if (!p.second->isDeleted()) {
754 byheight.emplace_back(p.second->getHeight(), p.second.get());
755 }
756 }
757 std::sort(byheight.rbegin(), byheight.rend());
758 for (const auto& p : byheight) {
759 blocksStr += (p.second->toPrettyString(level + 2) + "\n");
760 }
761
762 std::string tipsStr{};
763 for (const auto* _tip : tips_) {
764 tipsStr += (_tip->toPrettyString(level + 2) + "\n");
765 }
766
767 return format("{}{{tip={}}}\n{}{{blocks=\n{}{}}}\n{}{{tips=\n{}{}}}",
768 pad,
769 (tip ? tip->toPrettyString() : "<empty>"),
770 pad,
771 blocksStr,
772 pad,
773 pad,
774 tipsStr,
775 pad);
776 }
777
778 public:
779 const FinalizedPayloadsIndex<index_t>& getFinalizedPayloadsIndex() const {
781 }
782
783 const PayloadsIndex<index_t>& getPayloadsIndex() const {
784 return payloadsIndex_;
785 }
786
788 class DeferForkResolutionGuard {
789 BaseBlockTree<Block>& tree_;
790
791 public:
792 DeferForkResolutionGuard(BaseBlockTree<Block>& tree) : tree_(tree) {
793 tree_.deferForkResolution();
794 }
795
796 // non-copyable
797 DeferForkResolutionGuard(const DeferForkResolutionGuard& other) = delete;
798 DeferForkResolutionGuard& operator=(const DeferForkResolutionGuard& other) =
799 delete;
800 // movable
801 DeferForkResolutionGuard(DeferForkResolutionGuard&& o) noexcept = default;
802 DeferForkResolutionGuard& operator=(DeferForkResolutionGuard&& o) noexcept =
803 default;
804
805 void overrideDeferredForkResolution(index_t* bestChain) {
806 // there's no obvious way to go back to having no best chain, so we
807 // just have to let fork resolution run its course
808 if (bestChain != nullptr) {
809 return tree_.overrideDeferredForkResolution(*bestChain);
810 }
811 }
812
813 ~DeferForkResolutionGuard() { tree_.continueForkResolution(); }
814 };
815
816 DeferForkResolutionGuard deferForkResolutionGuard() {
817 return DeferForkResolutionGuard(*this);
818 }
819
820 protected:
829 void deallocateTree(index_t& toDelete) {
830 VBK_TRACE_ZONE_SCOPED;
831 auto* index = &toDelete;
832
833 // find oldest ancestor
834 while (index != nullptr && !index->isRoot()) {
835 index = index->pprev;
836 }
837
838 // starting at oldest ancestor, remove all blocks during post-order tree
839 // traversal
840 forEachNodePostorder<block_t>(
841 *index,
842 [&](index_t& next) {
845
846 if (!next.isDeleted()) {
847 onBeforeLeafRemoved(next);
848 next.deleteTemporarily();
849 }
850
851 next.disconnectFromPrev();
852 deallocateBlock(next);
853 },
854 [&](index_t&) { return true; });
855 }
856
857 inline void decreaseAppliedBlockCount(size_t erasedBlocks) {
858 VBK_ASSERT_MSG(appliedBlockCount >= erasedBlocks,
859 "Tree: %s, Applied: %d, erased: %d",
860 block_t::name(),
861 appliedBlockCount,
862 erasedBlocks);
863 appliedBlockCount -= erasedBlocks;
864 }
865
882 virtual void finalizeBlockImpl(index_t& index,
883 // see config.preserveBlocksBehindFinal()
884 int32_t preserveBlocksBehindFinal) {
885 VBK_TRACE_ZONE_SCOPED;
886 VBK_LOG_DEBUG("Finalize %s, preserve %d blocks behind",
887 index.toShortPrettyString(),
888 preserveBlocksBehindFinal);
889 VBK_ASSERT_MSG(appliedBlockCount == activeChain_.blocksCount(),
890 "Tree=%s Applied=%d active chain=%d",
891 block_t::name(),
892 appliedBlockCount,
893 activeChain_.blocksCount());
894
895 index_t* finalizedBlock = &index;
896 if (finalizedBlock == &this->getRoot()) {
897 if (finalizedBlock->finalized) {
898 // block is already finalized
899 return;
900 }
901
902 finalizedBlock->finalized = true;
903 finalizedPayloadsIndex_.addBlock(*finalizedBlock);
904 return;
905 }
906
907 VBK_ASSERT(activeChain_.contains(finalizedBlock));
908
909 // we need to clarify which block will be final. we can not remove blocks
910 // which have not been saved into the storage
911 for (auto* walkBlock = finalizedBlock; walkBlock != nullptr;
912 walkBlock = walkBlock->pprev) {
913 if (walkBlock->isDirty()) {
914 finalizedBlock = walkBlock;
915 }
916 }
917
918 // first, erase candidates from tips_ that will never be activated
919 erase_if<decltype(tips_), index_t*>(
920 tips_, [this, &finalizedBlock](index_t* tip) -> bool {
921 VBK_ASSERT(tip);
922
923 // tip from active chain can not be outdated
924 if (!activeChain_.contains(tip) &&
925 isBlockOutdated(*finalizedBlock, *tip)) {
926 // we need to clarify which block will be final. we can not remove
927 // blocks which have not been saved into the storage
928 bool newFinalized = false;
929 auto* walkBlock = tip;
930 for (; tip != nullptr && !activeChain_.contains(walkBlock);
931 walkBlock = walkBlock->pprev) {
932 if (walkBlock->isDirty()) {
933 newFinalized = true;
934 }
935 }
936
937 if (newFinalized) {
938 finalizedBlock = walkBlock;
939 }
940
941 return true;
942 }
943
944 return false;
945 });
946
947 VBK_ASSERT(finalizedBlock != nullptr);
948
949 // second, update active chain (it should start with
950 // 'finalizedBlock' but we also need to preserve `preserveBlocksBehindFinal`
951 // blocks before it). all outdated blocks behind `finalizedBlock` block will
952 // be deallocated
953 int32_t firstBlockHeight =
954 finalizedBlock->getHeight() - preserveBlocksBehindFinal;
955 int32_t rootBlockHeight = getRoot().getHeight();
956 firstBlockHeight = std::max(rootBlockHeight, firstBlockHeight);
957
958 // before we deallocate subtree, disconnect "new root block" from previous
959 // tree
960 auto* newRoot = activeChain_[firstBlockHeight];
961 VBK_ASSERT(newRoot);
962
963 auto* rootPrev = newRoot->pprev;
964 if (newRoot->pprev != nullptr) {
965 newRoot->pprev->pnext.erase(newRoot);
966 newRoot->pprev = nullptr;
967
968 // do deallocate
969 deallocateTree(*rootPrev);
970
971 // erase "parallel" blocks - blocks that are on same height as `index`,
972 // but since `index` is final, will never be active.
973 if (finalizedBlock->pprev != nullptr) {
974 auto parallelBlocks = finalizedBlock->pprev->pnext;
975 parallelBlocks.erase(finalizedBlock);
976 for (auto* par : parallelBlocks) {
977 // disconnect `par` from prev block
978 par->disconnectFromPrev();
979 deallocateTree(*par);
980 }
981 }
982 }
983
984 // update active chain
985 VBK_ASSERT(firstBlockHeight >= rootBlockHeight);
986 size_t deallocatedBlocks = firstBlockHeight - rootBlockHeight;
987 activeChain_ = Chain<index_t>(firstBlockHeight, activeChain_.tip());
988 appliedBlockCount = activeChain_.blocksCount();
989
990 if (deallocatedBlocks > 0) {
991 VBK_LOG_WARN("Deallocated %d blocks in %s tree. Active chain %d..%d",
992 deallocatedBlocks,
993 block_t::name(),
994 activeChain_.first()->getHeight(),
995 activeChain_.tip()->getHeight());
996 }
997
998 // fourth, mark `index` and all predecessors as finalized
999 index_t* ptr = finalizedBlock;
1000 while (ptr != nullptr && !ptr->finalized) {
1001 ptr->finalized = true;
1002 finalizedPayloadsIndex_.addBlock(*ptr);
1003 ptr = ptr->pprev;
1004 }
1005 }
1006
1009 virtual void onBlockInserted(index_t* /*ignore*/) {
1010 /* do nothing in base tree */
1011 }
1012
1018 void updateAffectedTips(index_t& modifiedBlock) {
1019 VBK_TRACE_ZONE_SCOPED;
1020 if (deferForkResolutionDepth == 0) {
1021 ValidationState dummy;
1022 return doUpdateAffectedTips(modifiedBlock, dummy);
1023 }
1024
1025 if (!isUpdateTipsDeferred) {
1026 if (lastModifiedBlock == nullptr) {
1027 lastModifiedBlock = &modifiedBlock;
1028 } else {
1029 isUpdateTipsDeferred = true;
1030 lastModifiedBlock = nullptr;
1031 }
1032 }
1033 }
1034
1040 void doUpdateAffectedTips(index_t& modifiedBlock, ValidationState& state) {
1041 VBK_TRACE_ZONE_SCOPED;
1042 auto tips = findValidTips<block_t>(this->getTips(), modifiedBlock);
1043 VBK_LOG_DEBUG(
1044 "Found %d affected valid tips in %s", tips.size(), block_t::name());
1045 for (auto* tip : tips) {
1046 determineBestChain(*tip, state);
1047 }
1048 }
1049
1051 void updateTips() {
1052 if (deferForkResolutionDepth == 0) {
1053 return doUpdateTips();
1054 }
1055 isUpdateTipsDeferred = true;
1056 lastModifiedBlock = nullptr;
1057 }
1058
1059 private:
1060 int deferForkResolutionDepth = 0;
1061 bool isUpdateTipsDeferred = false;
1062 index_t* lastModifiedBlock = nullptr;
1063
1064 void doUpdateTips() {
1065 VBK_TRACE_ZONE_SCOPED;
1066 for (auto* tip : tips_) {
1067 VBK_ASSERT_MSG(tip->isValidTip(),
1068 "found block %s in tips_ which is not a valid tip",
1069 tip->toPrettyString());
1070
1071 ValidationState state;
1072 determineBestChain(*tip, state);
1073 }
1074 }
1075
1080 void overrideDeferredForkResolution(index_t& bestChain) {
1081 lastModifiedBlock = nullptr;
1082 isUpdateTipsDeferred = false;
1083
1084 ValidationState dummy;
1085 bool success = setState(bestChain, dummy);
1086 VBK_ASSERT_MSG(
1087 success,
1088 "state corruption: could not revert to the saved best chain tip: %s",
1089 dummy.toString());
1090 }
1091
1092 void deferForkResolution() { ++deferForkResolutionDepth; }
1093
1094 void continueForkResolution() {
1095 VBK_ASSERT(deferForkResolutionDepth > 0);
1096 --deferForkResolutionDepth;
1097
1098 if (deferForkResolutionDepth > 0) {
1099 return;
1100 }
1101
1102 if (lastModifiedBlock != nullptr) {
1103 VBK_ASSERT(!isUpdateTipsDeferred);
1104 ValidationState dummy;
1105 doUpdateAffectedTips(*lastModifiedBlock, dummy);
1106 lastModifiedBlock = nullptr;
1107 }
1108
1109 if (isUpdateTipsDeferred) {
1110 doUpdateTips();
1111 isUpdateTipsDeferred = false;
1112 }
1113 }
1114
1117 virtual void onBeforeLeafRemoved(const index_t&) {}
1118
1119 void doInvalidate(index_t& block, enum BlockValidityStatus reason) {
1120 VBK_TRACE_ZONE_SCOPED;
1121 VBK_ASSERT_MSG(
1122 !block.isValidUpTo(BLOCK_CAN_BE_APPLIED) ||
1123 (reason & BLOCK_FAILED_POP) == 0u,
1124 "attempted to set mutually exclusive flags BLOCK_CAN_BE_APPLIED "
1125 "and BLOCK_FAILED_POP for block %s",
1126 block.toPrettyString());
1127
1128 block.setFlag(reason);
1129 tips_.erase(&block);
1130
1131 onBlockValidityChanged.emit(block);
1132 }
1133
1134 void doReValidate(index_t& block, enum BlockValidityStatus reason) {
1135 block.unsetFlag(reason);
1136 tryAddTip(&block);
1137 onBlockValidityChanged.emit(block);
1138 }
1139
1140 public:
1142 signals::Signal<void(const index_t&)> onBlockBeforeDeallocated;
1144 signals::Signal<on_invalidate_t> onBlockValidityChanged;
1146 signals::Signal<void(const index_t& index)> onBeforeOverrideTip;
1147
1148 protected:
1150 bool isLoadingBlocks_ = false;
1152 bool isLoaded_ = false;
1154 block_index_t blocks_;
1156 std::unordered_set<index_t*> tips_;
1166
1167 const BlockReader& blockProvider_;
1168};
1169
1170} // namespace altintegration
1171#endif // ALTINTEGRATION_BASE_BLOCK_TREE_HPP
Class that is used for storing validation state.
Defines logging helpers.
Definition: block.hpp:14
@ BLOCK_VALID_TREE
acceptBlockHeader succeded.
@ BLOCK_CAN_BE_APPLIED
the chain with the block at its tip is fully valid, so if we do SetState on this block,...
BlockValidityStatus
Flags that describe block status.
@ BLOCK_FAILED_CHILD
block is state{lessly,fully} valid and the altchain did not report it as invalid, but some of the anc...
@ BLOCK_FAILED_MASK
all invalidity flags
@ BLOCK_FAILED_POP
block failed state{less,ful} validation due to its payloads
@ BLOCK_BOOTSTRAP
this is a bootstrap block
bool isBlockOutdated(const BlockIndex< Block > &finalBlock, const BlockIndex< Block > &candidate)
a candidate is considered outdated if it is behind finalBlock, or on same height and not equal to fin...
std::string HexStr(const T itbegin, const T itend)
Convert bytes to hex.
Definition: strutil.hpp:44
constexpr bool isValidInvalidationReason(const BlockValidityStatus &reason)
Check if the reason value can be used as the reason for invalidateSubtree and revalidateSubtree.
Base block tree that stores all blocks, maintains tree tips, maintains active chain.
void removeSubtree(const hash_t &toRemove)
This is an overloaded member function, provided for convenience. It differs from the above function o...
index_t * findBlockIndex(const T &hash)
Get BlockIndex by block hash.
void removeSubtree(index_t &toRemove)
Removes block and all its successors.
signals::Signal< void(const index_t &index)> onBeforeOverrideTip
chain reorg signal - the tip is being changed
signals::Signal< void(const index_t &)> onBlockBeforeDeallocated
before we deallocate any block index we emit it to this signal
void deallocateBlock(index_t &block)
Permanently erases block from a block tree.
const Chain< index_t > & getBestChain() const
Getter for currently Active Chain.
std::unordered_set< index_t * > tips_
stores ONLY VALID tips, including currently active tip
signals::Signal< on_invalidate_t > onBlockValidityChanged
signals to the end user that block have been invalidated
Chain< index_t > activeChain_
currently applied chain
virtual bool loadBlockForward(const stored_index_t &index, bool fast_load, ValidationState &state)
Efficiently connects BlockIndex to this tree as a leaf, when it is loaded from disk.
void removeLeaf(index_t &toRemove)
This is an overloaded member function, provided for convenience. It differs from the above function o...
bool isLoadingBlocks_
if true, we're in "loading blocks" state
void invalidateSubtree(const hash_t &toBeInvalidated, enum BlockValidityStatus reason, bool shouldDetermineBestChain=true)
This is an overloaded member function, provided for convenience. It differs from the above function o...
block_index_t blocks_
stores ALL blocks, including valid and invalid
bool isLoaded_
if true, we can no longer execute loadBlock/loadTip on this tree.
void invalidateSubtree(index_t &toBeInvalidated, enum BlockValidityStatus reason, bool shouldDetermineBestChain=true)
Mark given block as invalid.
bool isBootstrapped() const
Check if the blockchain is bootstrapped.
const index_t * getBlockIndex(const T &hash) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
const index_t * findBlockIndex(const T &hash) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
FinalizedPayloadsIndex< index_t > finalizedPayloadsIndex_
stores mapping of payload id -> its containing ALT/VBK block which are already finalized.
index_t * getBlockIndex(const T &hash)
Get BlockIndex by block hash.
PayloadsIndex< index_t > payloadsIndex_
stores mapping of payload id -> its containing ALT/VBK blocks which are not yet finalized.
A node in a block tree.
Definition: block_index.hpp:31
size_t nondeletedDescendantCount() const
Count the descendants that are not deleted.
BlockIndex * pprev
(memory only) pointer to the previous block
Definition: block_index.hpp:39
std::set< BlockIndex * > pnext
(memory only) a set of pointers for forward iteration
Definition: block_index.hpp:42
An abstraction over on-disk storage iterator.
Fully in-memory chain representation.
Definition: chain.hpp:25
Stores a mapping "payload id -> containing block" hash of payloads that are stored in finalized block...
Payloads index that stores mapping "payload id -> set of containing blocks" from all NON-FINALIZED bl...