veriblock-pop-cpp
C++11 Libraries for leveraging VeriBlock Proof-Of-Proof blockchain technology.
block_index.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_BLOCK_INDEX_HPP_
7#define ALT_INTEGRATION_INCLUDE_VERIBLOCK_BLOCKCHAIN_BLOCK_INDEX_HPP_
8
9#include <memory>
10#include <set>
11#include <vector>
12#include <veriblock/pop/algorithm.hpp>
13#include <veriblock/pop/entities/endorsements.hpp>
14#include <veriblock/pop/entities/vbkblock.hpp>
15#include <veriblock/pop/logger.hpp>
16#include <veriblock/pop/storage/stored_block_index.hpp>
17#include <veriblock/pop/validation_state.hpp>
18#include <veriblock/pop/write_stream.hpp>
19
20#include "block_status.hpp"
21#include "command.hpp"
22#include "command_group.hpp"
23
24namespace altintegration {
25
30template <typename Block>
31struct BlockIndex : public Block::addon_t {
32 using block_t = Block;
33 using addon_t = typename Block::addon_t;
34 using hash_t = typename block_t::hash_t;
35 using prev_hash_t = typename block_t::prev_hash_t;
36 using height_t = typename block_t::height_t;
37
39 BlockIndex* pprev = nullptr;
40
42 std::set<BlockIndex*> pnext{};
43
45 bool finalized = false;
46
48 BlockIndex(BlockIndex* prev) : pprev(prev) {
49 // FIXME: prev should be a reference
50 VBK_ASSERT(prev != nullptr);
51
52 pprev->pnext.insert(this);
53
54 if (pprev->isFailed()) {
55 setFlag(BLOCK_FAILED_CHILD);
56 }
57
58 height = prev->getHeight() + 1;
59
60 // TODO: what dirtiness should it have here?
61 }
62
64 explicit BlockIndex(height_t _height) : pprev(nullptr), height(_height) {}
65
66 ~BlockIndex() {
67 // before block is deallocated it must be disconnected and deleted.
68 VBK_ASSERT(isTip());
69 VBK_ASSERT(isDeleted());
70 VBK_ASSERT_MSG(pprev == nullptr, pprev->toPrettyString());
71 }
72
73 void disconnectFromPrev() {
74 if (pprev == nullptr) {
75 // this block is already root
76 return;
77 }
78
79 VBK_ASSERT_MSG(pprev->pnext.count(this) > 0, this->toPrettyString());
80 this->pprev->pnext.erase(this);
81 this->pprev = nullptr;
82 }
83
84 bool isTip() const { return pnext.empty(); }
85
86 // BlockIndex is not copyable
87 // BlockIndex is movable
88 // NOLINTNEXTLINE(performance-noexcept-move-constructor)
89 BlockIndex(BlockIndex&& other) = default;
90 // NOLINTNEXTLINE(performance-noexcept-move-constructor)
91 BlockIndex& operator=(BlockIndex&& other) = default;
92
93 bool isDeleted() const { return hasFlags(BLOCK_DELETED); };
94 bool isRoot() const { return pprev == nullptr; };
95
96 void restore() {
97 VBK_ASSERT_MSG(isDeleted(),
98 "tried to restore block %s that is not deleted",
99 toPrettyString());
100
101 // unsetFlag runs setDirty() for us
102 unsetFlag(BLOCK_DELETED);
103 }
104
105 void deleteTemporarily() {
106 VBK_ASSERT_MSG(!isDeleted(),
107 "tried to delete block %s that is already deleted",
108 toPrettyString());
109
110 // FIXME: this is a hack that might eventually bite us
111 addon_t::setNull();
112
113 // preserve FAILED_* flags
116
117 // make it dirty by default
118 setDirty();
119 }
120
121 // loads on-disk fields from 'other' block index.
122 // works like (explicit) copy constructor
123 void mergeFrom(const StoredBlockIndex<Block>& other) {
124 setHeight(other.height);
125 setHeader(other.header);
126 setStatus(other.status);
127
128 other.addon.toInmem(*this);
129 }
130
131 StoredBlockIndex<Block> toStoredBlockIndex() const {
132 StoredBlockIndex<Block> ret;
133 ret.height = height;
134 ret.status = status;
135 ret.header = header;
136 ret.addon = *this;
137 return ret;
138 }
139
145 bool isConnected() const noexcept {
146 return this->isValidUpTo(BLOCK_CONNECTED);
147 }
148
149 uint32_t getStatus() const { return status; }
150 void setStatus(uint32_t _status) {
151 if (_status == this->status) return;
152 this->status = _status;
153 setDirty();
154 }
155
156 uint32_t getValidityLevel() const {
157 auto level = status & BLOCK_VALID_MASK;
158 VBK_ASSERT_MSG(level <= BLOCK_CAN_BE_APPLIED, "unknown validity level");
159 return level;
160 }
161
162 bool isFailed() const { return (status & BLOCK_FAILED_MASK) != 0u; }
163
164 bool isValid(enum BlockStateStatus upTo = BLOCK_VALID_TREE) const {
165 return !isFailed() && isValidUpTo(upTo);
166 }
167
168 bool isValidUpTo(enum BlockStateStatus upTo) const {
169 auto validityLevel = getValidityLevel();
170 VBK_ASSERT_MSG(
171 validityLevel != BLOCK_CAN_BE_APPLIED || !hasFlags(BLOCK_FAILED_POP),
172 "block %s is both BLOCK_CAN_BE_APPLIED and BLOCK_FAILED_POP which are "
173 "mutually exclusive",
174 toPrettyString());
175
176 return getValidityLevel() >= upTo;
177 }
178
179 void setNull() {
180 addon_t::setNull();
181 this->pprev = nullptr;
182 this->pnext.clear();
183 this->height = 0;
185 // make it dirty by default
186 setDirty();
187 }
188
189 void setNullInmemFields() {
190 addon_t::setNullInmemFields();
191 this->pprev = nullptr;
192 this->pnext.clear();
193 }
194
195 bool raiseValidity(enum BlockStateStatus upTo) {
196 // we can't raise the validity of a block that's known to be invalid due to
197 // PoP it's ok to raise the validity of a block invalidated by altchain
198 if ((status & BLOCK_FAILED_POP) != 0u) {
199 return false;
200 }
201 if ((status & BLOCK_VALID_MASK) < upTo) {
202 VBK_ASSERT_MSG(isRoot() || pprev->getValidityLevel() >= upTo,
203 "attempted to raise the validity level of block %s beyond "
204 "the validity level of its ancestor %s",
205 toPrettyString(),
206 pprev->toPrettyString());
207
208 status = (status & ~BLOCK_VALID_MASK) | upTo;
209 return true;
210 }
211 return false;
212 }
213
214 bool lowerValidity(enum BlockStateStatus upTo) {
215 // we can't lower the validity of a block that's known to be invalid due to
216 // PoP, as that would incorrectly label another validity level as failed.
217 // BLOCK_FAILED_POP has to be cleared first via revalidateSubtree it's ok to
218 // lower the validity of a block invalidated by altchain
219 if ((status & BLOCK_FAILED_POP) != 0u) {
220 return false;
221 }
222 if ((status & BLOCK_VALID_MASK) > upTo) {
223 status = (status & ~BLOCK_VALID_MASK) | upTo;
224 return true;
225 }
226 return false;
227 }
228
229 void setDirty() { this->dirty = true; }
230 void unsetDirty() { this->dirty = false; }
231 bool isDirty() const { return this->dirty; }
232
233 void setFlag(enum BlockValidityStatus s) {
234 auto newStatus = this->status | s;
235 if (newStatus == this->status) return;
236 this->status = newStatus;
237 setDirty();
238 }
239 void unsetFlag(enum BlockValidityStatus s) {
240 auto newStatus = this->status & ~s;
241 if (newStatus == this->status) return;
242 this->status = newStatus;
243 setDirty();
244 }
245
246 bool hasFlags(enum BlockValidityStatus s) const { return this->status & s; }
247
248 const hash_t& getHash() const { return header->getHash(); }
249 uint32_t getTimestamp() const { return header->getTimestamp(); }
250 uint32_t getDifficulty() const { return header->getDifficulty(); }
251
252 height_t getHeight() const { return height; }
253 void setHeight(const height_t newHeight) {
254 height = newHeight;
255 setDirty();
256 }
257
258 const block_t& getHeader() const { return *header; }
259 void setHeader(const block_t& newHeader) {
260 header = std::make_shared<block_t>(newHeader);
261 setDirty();
262 }
263 void setHeader(std::shared_ptr<block_t> newHeader) {
264 header = std::move(newHeader);
265 setDirty();
266 }
267
268 bool canBeATip() const {
269 return !isDeleted() && isValid(addon_t::validTipLevel);
270 }
275 bool isValidTip() const {
276 return canBeATip() &&
277 (isTip() ||
278 std::none_of(pnext.begin(), pnext.end(), [](BlockIndex* index) {
279 return index->canBeATip();
280 }));
281 }
282
287 return isTip() ||
288 std::none_of(pnext.begin(), pnext.end(), [](BlockIndex* index) {
289 return index->hasFlags(BLOCK_ACTIVE);
290 });
291 }
292
297 return std::count_if(
298 pnext.begin(), pnext.end(), [](const BlockIndex* index) {
299 return !index->isDeleted();
300 });
301 }
302
307 return isTip() ||
308 std::none_of(pnext.begin(), pnext.end(), [](BlockIndex* index) {
309 return index->isConnected();
310 });
311 }
312
313 const BlockIndex* getAncestorBlocksBehind(height_t steps) const {
314 if (steps < 0 || steps > this->height) {
315 return nullptr;
316 }
317
318 return this->getAncestor(this->height - steps);
319 }
320
321 const BlockIndex* getPrev() const {
322 VBK_ASSERT_MSG(isRoot() || getHeight() == pprev->getHeight() + 1,
323 "state corruption: unexpected height of the previous block "
324 "%s of block %s",
325 pprev->toPrettyString(),
326 toPrettyString());
327
328 return pprev;
329 }
330
331 BlockIndex* getPrev() {
332 const auto& t = as_const(*this);
333 return const_cast<BlockIndex*>(t.getPrev());
334 }
335
336 bool isDescendantOf(const BlockIndex& ancestor) const {
337 return getAncestor(ancestor.getHeight()) == &ancestor;
338 }
339
340 bool isAncestorOf(const BlockIndex& descendant) const {
341 return descendant.getAncestor(getHeight()) == this;
342 }
343
347 const BlockIndex* getAncestor(height_t _height) const {
348 VBK_ASSERT(_height >= 0);
349 if (_height > this->height) {
350 return nullptr;
351 }
352
353 // TODO: this algorithm is not optimal. for O(n) seek backwards until we hit
354 // valid height. also it assumes whole blockchain is in memory (pprev is
355 // valid until given height)
356 const BlockIndex* index = this;
357 while (index != nullptr && index->height > _height) {
358 index = index->getPrev();
359 }
360
361 VBK_ASSERT(index == nullptr || index->height == _height);
362 return index;
363 }
364
366 BlockIndex* getAncestor(height_t _height) {
367 const auto& t = as_const(*this);
368 return const_cast<BlockIndex*>(t.getAncestor(_height));
369 }
370
371 std::string toPrettyString(size_t level = 0) const {
372 return format(
373 "{}{}BlockIndex(height={} hash={} prev={} next={} final={} status={} "
374 "{})",
375 std::string(level, ' '),
376 Block::name(),
377 height,
378 HexStr(getHash()),
379 (pprev == nullptr
380 ? "nullptr"
381 : format("{}:{}", pprev->getHeight(), HexStr(pprev->getHash()))),
383 finalized,
384 status,
385 addon_t::toPrettyString());
386 }
387
388 std::string toShortPrettyString() const {
389 return format("{}:{}:{}", Block::name(), height, HexStr(getHash()));
390 }
391
392 friend bool operator==(const BlockIndex& a, const BlockIndex& b) {
393 return a.getStatus() == b.getStatus() && a.getHeight() == b.getHeight() &&
394 a.getHeader() == b.getHeader();
395 }
396
397 friend bool operator!=(const BlockIndex& a, const BlockIndex& b) {
398 return a.getStatus() != b.getStatus() && a.getHeight() != b.getHeight() &&
399 a.getHeader() != b.getHeader();
400 }
401
402 protected:
404 height_t height = 0;
405
407 std::shared_ptr<block_t> header = std::make_shared<block_t>();
408
411
413 bool dirty = false;
414
415 private:
416 // make it non-copyable
417 BlockIndex(const BlockIndex& other) = default;
418 BlockIndex& operator=(const BlockIndex& other) = default;
419};
420
427template <typename Block>
429 const BlockIndex<Block>& b) {
430 const auto initialHeight = std::min(a.getHeight(), b.getHeight());
431
432 for (auto cursorA = a.getAncestor(initialHeight),
433 cursorB = b.getAncestor(initialHeight);
434 cursorA != nullptr && cursorB != nullptr;
435 cursorA = cursorA->getPrev(), cursorB = cursorB->getPrev()) {
436 if (cursorA == cursorB) {
437 return cursorA;
438 }
439 }
440
441 // chain `b` is not connected to `a`
442 return nullptr;
443}
444
447template <typename Block>
448bool isBlockOutdated(const BlockIndex<Block>& finalBlock,
449 const BlockIndex<Block>& candidate) {
450 // finalBlock is ancestor of candidate
451 if (candidate.getAncestor(finalBlock.getHeight()) == &finalBlock) {
452 return false;
453 }
454
455 // all candidates behind final block are outdated
456 if (candidate.getHeight() < finalBlock.getHeight()) {
457 return true;
458 }
459
460 // all parallel blocks (on same height as final, but not final) are outdated
461 if (candidate.getHeight() == finalBlock.getHeight() &&
462 &finalBlock != &candidate) {
463 return true;
464 }
465
466 // candidate is ancestor of finalBlock and within window
467 if (finalBlock.getAncestor(candidate.getHeight()) == &candidate) {
468 return false;
469 }
470
471 // candidate is on a fork
472 auto* fork = getForkBlock(finalBlock, candidate);
473
474 // candidate does not connect to finalBlock
475 if (fork == nullptr) {
476 return true;
477 }
478
479 // if fork block is outdated, then candidate is also outdated
480 return isBlockOutdated(finalBlock, *fork);
481}
482
484template <typename Block>
485void PrintTo(const BlockIndex<Block>& b, ::std::ostream* os) {
486 *os << b.toPrettyString();
487}
488
489} // namespace altintegration
490#endif // ALT_INTEGRATION_INCLUDE_VERIBLOCK_BLOCKCHAIN_BLOCK_INDEX_HPP_
Defines logging helpers.
Definition: block.hpp:14
BlockStateStatus
Flags that describe block status.
@ BLOCK_VALID_MASK
all stateful validity levels
@ BLOCK_VALID_TREE
acceptBlockHeader succeded.
@ BLOCK_CONNECTED
the block is connected via connectBlock, which means that this block and all its ancestors are at lea...
@ 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_VALID_UNKNOWN
default state for validity - validity state is unknown
@ BLOCK_FAILED_CHILD
block is state{lessly,fully} valid and the altchain did not report it as invalid, but some of the anc...
@ BLOCK_DELETED
the block is temporarily deleted
@ BLOCK_FAILED_MASK
all invalidity flags
@ BLOCK_FAILED_POP
block failed state{less,ful} validation due to its payloads
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
void PrintTo(const ArithUint256 &uint, ::std::ostream *os)
custom gtest printer, which prints Blob of any size as hexstring @ private
const BlockIndex< Block > * getForkBlock(const BlockIndex< Block > &a, const BlockIndex< Block > &b)
getForkBlock assumes that: the block tree is not malformed the fork block(worst case: genesis/bootstr...
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
BlockIndex(height_t _height)
create a root block in a deleted state
Definition: block_index.hpp:64
bool finalized
(memory only) if true, this block can not be reorganized
Definition: block_index.hpp:45
BlockIndex(BlockIndex *prev)
create a regular block in a deleted state
Definition: block_index.hpp:48
BlockIndex * getAncestor(height_t _height)
This is an overloaded member function, provided for convenience. It differs from the above function o...
bool allDescendantsUnconnected() const
Check if all immediate descendants of the block are not connected.
const BlockIndex * getAncestor(height_t _height) const
bool dirty
(memory only) if true, this block should be written on disk
bool allDescendantsUnapplied() const
Check if all immediate descendants of the block are unapplied.
std::shared_ptr< block_t > header
block header
height_t height
height of the entry in the chain
bool isConnected() const noexcept
Block is connected if it contains block body (PopData), and all its ancestors are connected.
uint32_t status
contains status flags
bool isValidTip() const
The block is a valid tip if it can be a tip and either there are no descendant blocks or none of the ...