veriblock-pop-cpp
C++11 Libraries for leveraging VeriBlock Proof-Of-Proof blockchain technology.
blocktree.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_BLOCKTREE_HPP_
7#define ALT_INTEGRATION_INCLUDE_VERIBLOCK_BLOCKCHAIN_BLOCKTREE_HPP_
8
9#include <algorithm>
10#include <memory>
11#include <unordered_map>
12
13#include "base_block_tree.hpp"
14#include "block_index.hpp"
15#include "blockchain_util.hpp"
16#include "chain.hpp"
17#include "tree_algo.hpp"
18#include "veriblock/pop/fmt.hpp"
19#include "veriblock/pop/stateless_validation.hpp"
20#include "veriblock/pop/storage/block_reader.hpp"
21#include "veriblock/pop/validation_state.hpp"
22
23namespace altintegration {
24
29template <typename Block, typename ChainParams>
30struct BlockTree : public BaseBlockTree<Block> {
32 using block_t = Block;
33 using params_t = ChainParams;
34 using index_t = typename base::index_t;
35 using stored_index_t = typename base::stored_index_t;
36 using hash_t = typename Block::hash_t;
37 using prev_block_hash_t = typename Block::prev_hash_t;
38 using height_t = typename Block::height_t;
39
40 ~BlockTree() override = default;
41
42 BlockTree(const ChainParams& param, const BlockReader& blockProvider)
43 : base(blockProvider), param_(&param) {}
44
45 const ChainParams& getParams() const { return *param_; }
46
50 virtual void bootstrapWithGenesis(const block_t& block) {
51 VBK_ASSERT(!base::isBootstrapped() && "already bootstrapped");
52 VBK_ASSERT(!this->isLoaded_);
53 VBK_ASSERT(!this->isLoadingBlocks_);
54 ValidationState state;
55 this->bootstrap(0, block, state);
56 this->isLoadingBlocks_ = false;
57 }
58
68 virtual void bootstrapWithChain(height_t startHeight,
69 const std::vector<block_t>& chain) {
70 VBK_ASSERT(!base::isBootstrapped() && "already bootstrapped");
71 VBK_ASSERT(!this->isLoadingBlocks_);
72 VBK_ASSERT(!this->isLoaded_);
73 VBK_ASSERT_MSG(!chain.empty(), "bootstrap chain must not be empty");
74 VBK_ASSERT_MSG(
75 chain.size() >= param_->numBlocksForBootstrap(),
76 "bootstrap chain is too small (%d) expected at least %d blocks",
77 chain.size(),
78 param_->numBlocksForBootstrap());
79
80 ValidationState state;
81
82 // pick first block from the chain, bootstrap with a single block
83 auto genesis = chain[0];
84 this->bootstrap(startHeight, genesis, state);
85
86 // apply the rest of the blocks from the chain on top of our bootstrap
87 // block. disable difficulty checks, because we have not enough blocks in
88 // our store (yet) to check it correctly
89 for (size_t i = 1, size = chain.size(); i < size; i++) {
90 auto& block = chain[i];
91 bool ok = this->acceptBlockHeaderImpl(
92 std::make_shared<block_t>(block), state, false);
93 VBK_ASSERT_MSG(ok,
94 "found statelssly invalid bootstrap block: %d, %s",
95 i,
96 state.toString());
97 auto* index = base::getBlockIndex(block.getHash());
98 VBK_ASSERT(index);
99 index->setIsBootstrap(true);
100 }
101 }
102
103 bool acceptBlockHeader(const block_t& block, ValidationState& state) {
104 return acceptBlockHeaderImpl(std::make_shared<block_t>(block), state, true);
105 }
106
107 bool acceptBlockHeader(const std::shared_ptr<block_t>& block,
108 ValidationState& state) {
109 return acceptBlockHeaderImpl(block, state, true);
110 }
111
112 std::string toPrettyString(size_t level = 0) const {
113 std::string pad(level, ' ');
114 return format("{}{}BlockTree{{blocks={}\n{}\n{}}}",
115 pad,
116 Block::name(),
117 base::getBlocks().size(),
118 base::toPrettyString(level + 2),
119 pad);
120 }
121
124 bool fast_load,
125 ValidationState& state) override {
126 if (!fast_load && !checkBlock(*index.header, state, *param_)) {
127 return state.Invalid("bad-header");
128 }
129
130 const auto hash = index.header->getHash();
131 if (!base::loadBlockForward(index, fast_load, state)) {
132 return false;
133 }
134
135 auto* current = base::getBlockIndex(hash);
136 VBK_ASSERT(current);
137 VBK_ASSERT(!this->isLoaded_);
138 VBK_ASSERT(this->isLoadingBlocks_);
139
140 // we only check blocks contextually if they are not bootstrap blocks, and
141 // previous block exists
142 if (!fast_load && !current->isRoot() &&
143 !current->hasFlags(BLOCK_BOOTSTRAP) &&
144 !contextuallyCheckBlock(
145 *current->pprev, current->getHeader(), state, *param_)) {
146 return state.Invalid("bad-block-contextually");
147 }
148
149 // recover chainwork
150 current->chainWork = getBlockProof(current->getHeader());
151 if (!current->isRoot()) {
152 current->chainWork += current->pprev->chainWork;
153 }
154
155 // clear blockOfProofEndorsements inmem field
156 current->clearBlockOfProofEndorsement();
157
158 current->raiseValidity(BLOCK_VALID_TREE);
159 return true;
160 }
161
164 bool isBlockOld(height_t height) const {
165 auto* tip = base::getBestChain().tip();
166 VBK_ASSERT(tip);
167
168 return tip->getHeight() - height > getParams().getOldBlocksWindow();
169 }
170
172 bool isBlockOld(const hash_t& hash) const {
173 auto* index = base::getBlockIndex(hash);
174 if (index == nullptr) {
175 // block is unknown, so not "old"
176 return false;
177 }
178
179 return isBlockOld(index->getHeight());
180 }
181
182 void finalizeBlocks() {
183 VBK_ASSERT(!this->isLoadingBlocks_);
184 base::finalizeBlocks(this->getParams().getMaxReorgBlocks(),
185 this->getParams().preserveBlocksBehindFinal());
186 }
187
188 protected:
189 const ChainParams* param_ = nullptr;
190
191 bool acceptBlockHeaderImpl(const std::shared_ptr<block_t>& block,
192 ValidationState& state,
193 bool shouldContextuallyCheck) {
194 index_t* index = nullptr;
195 if (!validateAndAddBlock(block, state, shouldContextuallyCheck, &index)) {
196 return false;
197 }
198
199 VBK_ASSERT(index);
200 if (!shouldContextuallyCheck) {
201 // this is a bootstrap block
202 index->setFlag(BLOCK_BOOTSTRAP);
203 }
204
205 base::tryAddTip(index);
206
207 // don't defer fork resolution in the acceptBlockHeader+addPayloads flow
208 // until the validation hole is plugged
209 determineBestChain(*index, state);
210
211 return true;
212 }
213
214 void bootstrap(height_t height,
215 const block_t& block,
216 ValidationState& state) {
217 bootstrap(height, std::make_shared<block_t>(block), state);
218 }
219
220 void bootstrap(height_t height,
221 std::shared_ptr<block_t> block,
222 ValidationState& state) {
223 bool ok = checkBlock(*block, state, *param_);
224 VBK_ASSERT_MSG(ok,
225 "found statelessly invalid bootstrap block in %s tree: %s",
226 block_t::name(),
227 state.toString());
228
229 auto* index = base::insertBlockHeader(block, height);
230 VBK_ASSERT(index != nullptr &&
231 "insertBlockHeader should have never returned nullptr");
232
233 index->setHeight(height);
234 base::activeChain_ = Chain<index_t>(height, index);
235 index->setIsBootstrap(true);
236
237 VBK_ASSERT(index->finalized);
238 VBK_ASSERT(base::isBootstrapped());
239 VBK_ASSERT(base::getBlockIndex(index->getHash()) != nullptr &&
240 "getBlockIndex must be able to find the block added by "
241 "insertBlockHeader");
242
243 base::tryAddTip(index);
244 index->setFlag(BLOCK_ACTIVE);
245 bool success = index->raiseValidity(BLOCK_CAN_BE_APPLIED);
246 VBK_ASSERT(success);
247 index->setFlag(BLOCK_BOOTSTRAP);
248 base::appliedBlockCount = 1;
249 }
250
251 bool validateAndAddBlock(const std::shared_ptr<block_t>& block,
252 ValidationState& state,
253 bool shouldContextuallyCheck,
254 index_t** ret) {
255 if (!checkBlock(*block, state, *param_)) {
256 return state.Invalid(block_t::name() + "-check-block");
257 }
258
259 // we must know previous block
260 auto* prev = base::getBlockIndex(block->getPreviousBlock());
261 if (prev == nullptr) {
262 return state.Invalid(
263 block_t::name() + "-bad-prev-block",
264 "can not find previous block: " + HexStr(block->getPreviousBlock()));
265 }
266
267 if (shouldContextuallyCheck &&
268 !contextuallyCheckBlock(*prev, *block, state, *param_)) {
269 return state.Invalid(block_t::name() + "-contextually-check-block");
270 }
271
272 auto index = base::insertBlockHeader(block);
273 VBK_ASSERT(index != nullptr &&
274 "insertBlockHeader should have never returned nullptr");
275
276 if (ret) {
277 *ret = index;
278 }
279
280 index->raiseValidity(BLOCK_CONNECTED);
281
282 // if prev block is invalid, mark this block as invalid
283 if (!prev->isValid()) {
284 index->setFlag(BLOCK_FAILED_CHILD);
285 return state.Invalid(
286 block_t::name() + "-bad-chain",
287 format("Previous block is invalid={}", prev->toPrettyString()));
288 }
289
290 return true;
291 }
292
293 void determineBestChain(index_t& candidate, ValidationState& state) override {
294 auto bestTip = base::getBestChain().tip();
295 VBK_ASSERT(bestTip != nullptr && "must be bootstrapped");
296
297 if (bestTip == &candidate) {
298 return;
299 }
300
301 // do not even consider invalid candidates
302 if (!candidate.isValid()) {
303 return;
304 }
305
306 if (bestTip->chainWork < candidate.chainWork) {
308 this->setState(candidate, state);
309 }
310 }
311
313 void onBlockInserted(index_t* newIndex) final {
314 newIndex->chainWork = getBlockProof(newIndex->getHeader());
315 if (!newIndex->isRoot()) {
316 newIndex->chainWork += newIndex->pprev->chainWork;
317 }
318 }
319};
320
322template <typename Block, typename ChainParams>
323void PrintTo(const BlockTree<Block, ChainParams>& tree, std::ostream* os) {
324 *os << tree.toPrettyString();
325}
326
327} // namespace altintegration
328
329#endif // ALT_INTEGRATION_INCLUDE_VERIBLOCK_BLOCKCHAIN_BLOCKTREE_HPP_
Class that is used for storing validation state.
Defines logging helpers.
Definition: block.hpp:14
@ 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,...
@ BLOCK_FAILED_CHILD
block is state{lessly,fully} valid and the altchain did not report it as invalid, but some of the anc...
@ BLOCK_ACTIVE
the block is currently applied via SetState.
@ BLOCK_BOOTSTRAP
this is a bootstrap block
std::string HexStr(const T itbegin, const T itend)
Convert bytes to hex.
Definition: strutil.hpp:44
bool checkBlock(const BtcBlock &block, ValidationState &state, const BtcChainParams &params)
Stateless validation for a single block.
void PrintTo(const ArithUint256 &uint, ::std::ostream *os)
custom gtest printer, which prints Blob of any size as hexstring @ private
Base block tree that stores all blocks, maintains tree tips, maintains active chain.
const Chain< index_t > & getBestChain() const
Getter for currently Active Chain.
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.
bool isLoadingBlocks_
if true, we're in "loading blocks" state
bool isLoaded_
if true, we can no longer execute loadBlock/loadTip on this tree.
bool isBootstrapped() const
Check if the blockchain is bootstrapped.
index_t * getBlockIndex(const T &hash)
Get BlockIndex by block hash.
A node in a block tree.
Definition: block_index.hpp:31
An abstraction over on-disk storage iterator.
BlockTree is a tree of blocks with single "bootstrap" block as root.
Definition: blocktree.hpp:30
void determineBestChain(index_t &candidate, ValidationState &state) override
Definition: blocktree.hpp:293
bool loadBlockForward(const stored_index_t &index, bool fast_load, ValidationState &state) override
Definition: blocktree.hpp:123
void onBlockInserted(index_t *newIndex) final
whenever new block is inserted, BlockTree has to update its ChainWork
Definition: blocktree.hpp:313
virtual void bootstrapWithGenesis(const block_t &block)
Bootstrap blockchain with a single genesis block.
Definition: blocktree.hpp:50
virtual void bootstrapWithChain(height_t startHeight, const std::vector< block_t > &chain)
Bootstrap network with a chain that starts at 'startHeight'.
Definition: blocktree.hpp:68
bool isBlockOld(const hash_t &hash) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: blocktree.hpp:172
bool isBlockOld(height_t height) const
block is considered old if it is behind current tip further than 'old blocks window' blocks
Definition: blocktree.hpp:164
std::shared_ptr< block_t > header
block header