Kagome
Polkadot Runtime Engine in C++17
block_storage_impl.cpp
Go to the documentation of this file.
1 
7 
10 #include "scale/scale.hpp"
11 
12 namespace kagome::blockchain {
13  using primitives::Block;
14  using primitives::BlockId;
15  using storage::face::MapCursor;
16  using storage::face::WriteBatch;
19 
21  std::shared_ptr<storage::BufferStorage> storage,
22  std::shared_ptr<crypto::Hasher> hasher)
23  : storage_{std::move(storage)},
24  hasher_{std::move(hasher)},
25  logger_{log::createLogger("BlockStorage", "blockchain")} {
26  BOOST_ASSERT(storage_ != nullptr);
27  BOOST_ASSERT(hasher_ != nullptr);
28  }
29 
30  outcome::result<std::shared_ptr<BlockStorageImpl>> BlockStorageImpl::create(
31  storage::trie::RootHash state_root,
32  const std::shared_ptr<storage::BufferStorage> &storage,
33  const std::shared_ptr<crypto::Hasher> &hasher) {
34  auto block_storage = std::shared_ptr<BlockStorageImpl>(
35  new BlockStorageImpl(storage, hasher));
36 
37  auto res = block_storage->hasBlockHeader(primitives::BlockNumber{0});
38  if (res.has_error()) {
39  return res.as_failure();
40  }
41 
42  if (not res.value()) {
43  auto extrinsics_root = trieRoot({});
44 
45  // genesis block initialization
46  primitives::Block genesis_block;
47  genesis_block.header.number = 0;
48  genesis_block.header.extrinsics_root = extrinsics_root;
49  genesis_block.header.state_root = state_root;
50  // the rest of the fields have default value
51 
52  OUTCOME_TRY(genesis_block_hash, block_storage->putBlock(genesis_block));
53  OUTCOME_TRY(block_storage->putNumberToIndexKey({0, genesis_block_hash}));
54 
55  OUTCOME_TRY(block_storage->setBlockTreeLeaves({genesis_block_hash}));
56  }
57 
58  return block_storage;
59  }
60 
61  outcome::result<bool> BlockStorageImpl::hasBlockHeader(
62  const primitives::BlockId &id) const {
64  }
65 
66  outcome::result<std::optional<primitives::BlockHeader>>
68  OUTCOME_TRY(encoded_header_opt,
70  if (encoded_header_opt.has_value()) {
71  OUTCOME_TRY(
72  header,
73  scale::decode<primitives::BlockHeader>(encoded_header_opt.value()));
74  return std::move(header);
75  }
76  return std::nullopt;
77  }
78 
79  outcome::result<std::optional<primitives::BlockBody>>
81  OUTCOME_TRY(block_data, getBlockData(id));
82  if (block_data.has_value() && block_data.value().body.has_value()) {
83  return block_data.value().body.value();
84  }
85  return std::nullopt;
86  }
87 
88  outcome::result<std::optional<primitives::BlockData>>
90  OUTCOME_TRY(encoded_block_data_opt,
92  if (encoded_block_data_opt.has_value()) {
93  OUTCOME_TRY(
94  block_data,
95  scale::decode<primitives::BlockData>(encoded_block_data_opt.value()));
96  return std::move(block_data);
97  }
98  return std::nullopt;
99  }
100 
101  outcome::result<std::optional<primitives::Justification>>
103  OUTCOME_TRY(block_data, getBlockData(block));
104  if (block_data.has_value()
105  && block_data.value().justification.has_value()) {
106  return block_data.value().justification.value();
107  }
108  return std::nullopt;
109  }
110 
112  const primitives::BlockInfo &block) {
113  SL_DEBUG(logger_, "Save num-to-idx for {}", block);
115  }
116 
117  outcome::result<primitives::BlockHash> BlockStorageImpl::putBlockHeader(
118  const primitives::BlockHeader &header) {
119  OUTCOME_TRY(encoded_header, scale::encode(header));
120  auto block_hash = hasher_->blake2b_256(encoded_header);
121  OUTCOME_TRY(putWithPrefix(*storage_,
123  header.number,
124  block_hash,
125  Buffer{std::move(encoded_header)}));
126  return block_hash;
127  }
128 
129  outcome::result<void> BlockStorageImpl::putBlockData(
130  primitives::BlockNumber block_number,
131  const primitives::BlockData &block_data) {
132  primitives::BlockData to_insert;
133 
134  // if block data does not exist, put a new one. Otherwise, get the old one
135  // and merge with the new one. During the merge new block data fields have
136  // higher priority over the old ones (old ones should be rewritten)
137  OUTCOME_TRY(existing_block_data_opt, getBlockData(block_data.hash));
138  if (not existing_block_data_opt.has_value()) {
139  to_insert = block_data;
140  } else {
141  auto &existing_data = existing_block_data_opt.value();
142 
143  // add all the fields from the new block_data
144  to_insert.header =
145  block_data.header ? block_data.header : existing_data.header;
146  to_insert.body = block_data.body ? block_data.body : existing_data.body;
147  to_insert.justification = block_data.justification
148  ? block_data.justification
149  : existing_data.justification;
150  to_insert.message_queue = block_data.message_queue
151  ? block_data.message_queue
152  : existing_data.message_queue;
153  to_insert.receipt =
154  block_data.receipt ? block_data.receipt : existing_data.receipt;
155  }
156 
157  OUTCOME_TRY(encoded_block_data, scale::encode(to_insert));
158  OUTCOME_TRY(putWithPrefix(*storage_,
160  block_number,
161  block_data.hash,
162  Buffer{encoded_block_data}));
163  return outcome::success();
164  }
165 
166  outcome::result<void> BlockStorageImpl::removeBlockData(
167  primitives::BlockNumber block_number,
168  const primitives::BlockDataFlags &remove_flags) {
169  primitives::BlockData to_insert;
170 
171  OUTCOME_TRY(existing_block_data_opt, getBlockData(remove_flags.hash));
172  if (not existing_block_data_opt.has_value()) {
173  return outcome::success();
174  }
175  auto &existing_data = existing_block_data_opt.value();
176 
177  auto move_if_flag = [](bool flag, auto &&value) {
178  if (flag) {
179  return std::move(value);
180  }
181  return std::optional<typename std::decay_t<decltype(value)>::value_type>(
182  std::nullopt);
183  };
184 
185  // add all the fields from the new block_data
186  to_insert.header =
187  move_if_flag(!remove_flags.header, std::move(existing_data.header));
188  to_insert.body =
189  move_if_flag(!remove_flags.body, std::move(existing_data.body));
190  to_insert.justification = move_if_flag(
191  !remove_flags.justification, std::move(existing_data.justification));
192  to_insert.message_queue = move_if_flag(
193  !remove_flags.message_queue, std::move(existing_data.message_queue));
194  to_insert.receipt =
195  move_if_flag(!remove_flags.receipt, std::move(existing_data.receipt));
196 
197  OUTCOME_TRY(encoded_block_data, scale::encode(to_insert));
198  OUTCOME_TRY(putWithPrefix(*storage_,
200  block_number,
201  remove_flags.hash,
202  Buffer{encoded_block_data}));
203  return outcome::success();
204  }
205 
206  outcome::result<primitives::BlockHash> BlockStorageImpl::putBlock(
207  const primitives::Block &block) {
208  // insert our block's parts into the database-
209  OUTCOME_TRY(block_hash, putBlockHeader(block.header));
210 
211  primitives::BlockData block_data;
212  block_data.hash = block_hash;
213  block_data.header = block.header;
214  block_data.body = block.body;
215 
216  OUTCOME_TRY(putBlockData(block.header.number, block_data));
217  logger_->info("Added block {} as child of {}",
218  primitives::BlockInfo(block.header.number, block_hash),
220  block.header.parent_hash));
221  return std::move(block_hash);
222  }
223 
224  outcome::result<void> BlockStorageImpl::putJustification(
225  const primitives::Justification &j,
226  const primitives::BlockHash &hash,
227  primitives::BlockNumber block_number) {
228  // insert justification into the database as a part of BlockData
229  primitives::BlockData block_data{.hash = hash, .justification = j};
230  OUTCOME_TRY(putBlockData(block_number, block_data));
231  return outcome::success();
232  }
233 
235  const primitives::BlockHash &hash, primitives::BlockNumber number) {
238  flags.justification = true;
239  OUTCOME_TRY(removeBlockData(number, flags));
240  return outcome::success();
241  }
242 
243  outcome::result<void> BlockStorageImpl::removeBlock(
244  const primitives::BlockInfo &block) {
245  auto block_lookup_key = numberAndHashToLookupKey(block.number, block.hash);
246 
247  SL_TRACE(logger_, "Removing block {}...", block);
248 
249  auto hash_to_idx_key =
251  if (auto res = storage_->remove(hash_to_idx_key); res.has_error()) {
252  logger_->error("could not remove hash-to-idx from the storage: {}",
253  res.error().message());
254  return res;
255  }
256 
257  auto num_to_idx_key =
259  OUTCOME_TRY(num_to_idx_val_opt, storage_->tryLoad(num_to_idx_key.view()));
260  if (num_to_idx_val_opt == block_lookup_key) {
261  if (auto res = storage_->remove(num_to_idx_key); res.has_error()) {
262  SL_ERROR(logger_,
263  "could not remove num-to-idx from the storage: {}",
264  block,
265  res.error().message());
266  return res;
267  }
268  SL_DEBUG(logger_, "Removed num-to-idx of {}", block);
269  }
270 
271  // TODO(xDimon): needed to clean up trie storage if block deleted
272  // issue: https://github.com/soramitsu/kagome/issues/1128
273 
274  auto body_key = prependPrefix(block_lookup_key, Prefix::BLOCK_DATA);
275  if (auto res = storage_->remove(body_key); res.has_error()) {
276  SL_ERROR(logger_,
277  "could not remove body of block {} from the storage: {}",
278  block,
279  res.error().message());
280  return res;
281  }
282 
283  auto header_key = prependPrefix(block_lookup_key, Prefix::HEADER);
284  if (auto res = storage_->remove(header_key); res.has_error()) {
285  SL_ERROR(logger_,
286  "could not remove header of block {} from the storage: {}",
287  block,
288  res.error().message());
289  return res;
290  }
291 
292  logger_->info("Removed block {}", block);
293 
294  return outcome::success();
295  }
296 
297  outcome::result<std::vector<primitives::BlockHash>>
299  if (block_tree_leaves_.has_value()) {
300  return block_tree_leaves_.value();
301  }
302 
303  OUTCOME_TRY(leaves_opt,
305  if (not leaves_opt.has_value()) {
307  }
308 
309  OUTCOME_TRY(
310  leaves,
311  scale::decode<std::vector<primitives::BlockHash>>(leaves_opt.value()));
312 
313  block_tree_leaves_.emplace(std::move(leaves));
314 
315  return block_tree_leaves_.value();
316  }
317 
319  std::vector<primitives::BlockHash> leaves) {
320  if (block_tree_leaves_.has_value()
321  and block_tree_leaves_.value() == leaves) {
322  return outcome::success();
323  }
324 
325  OUTCOME_TRY(encoded_leaves, scale::encode(leaves));
327  Buffer{std::move(encoded_leaves)}));
328 
329  block_tree_leaves_.emplace(std::move(leaves));
330 
331  return outcome::success();
332  }
333 
334  outcome::result<primitives::BlockInfo> BlockStorageImpl::getLastFinalized()
335  const {
336  OUTCOME_TRY(leaves, getBlockTreeLeaves());
337  auto current_hash = leaves[0];
338  for (;;) {
339  OUTCOME_TRY(j_opt, getJustification(current_hash));
340  if (j_opt.has_value()) {
341  break;
342  }
343  OUTCOME_TRY(header_opt, getBlockHeader(current_hash));
344  if (header_opt.has_value()) {
345  auto header = header_opt.value();
346  if (header.number == 0) {
347  SL_TRACE(logger_,
348  "Not found block with justification. "
349  "Genesis block will be used as last finalized ({})",
350  current_hash);
351  return {0, current_hash}; // genesis
352  }
353  current_hash = header.parent_hash;
354  } else {
355  SL_ERROR(
356  logger_, "Failed to fetch header for block ({})", current_hash);
358  }
359  }
360 
361  OUTCOME_TRY(header, getBlockHeader(current_hash));
362  primitives::BlockInfo found_block{header.value().number, current_hash};
363  SL_TRACE(logger_,
364  "Justification is found in block {}. "
365  "This block will be used as last finalized",
366  found_block);
367  return found_block;
368  }
369 
370 } // namespace kagome::blockchain
const common::Buffer kBlockTreeLeavesLookupKey
Class represents arbitrary (including empty) byte buffer.
Definition: buffer.hpp:29
outcome::result< void > putJustification(const primitives::Justification &j, const primitives::BlockHash &hash, primitives::BlockNumber number) override
std::optional< primitives::BlockHeader > header
Definition: block_data.hpp:25
Block class represents polkadot block primitive.
Definition: block.hpp:19
common::Buffer numberAndHashToLookupKey(primitives::BlockNumber number, const common::Hash256 &hash)
outcome::result< void > removeBlockData(primitives::BlockNumber block_number, const primitives::BlockDataFlags &remove_flags) override
outcome::result< void > setBlockTreeLeaves(std::vector< primitives::BlockHash > leaves) override
std::optional< common::Buffer > message_queue
Definition: block_data.hpp:28
outcome::result< void > removeJustification(const primitives::BlockHash &hash, primitives::BlockNumber number) override
static BlockDataFlags allUnset(primitives::BlockHash hash)
Definition: block_data.hpp:37
outcome::result< void > putBlockData(primitives::BlockNumber block_number, const primitives::BlockData &block_data) override
BlockStorageImpl(std::shared_ptr< storage::BufferStorage > storage, std::shared_ptr< crypto::Hasher > hasher)
common::Buffer numberToIndexKey(primitives::BlockNumber n)
outcome::result< void > putWithPrefix(storage::BufferStorage &map, prefix::Prefix prefix, BlockNumber num, Hash256 block_hash, const common::Buffer &value)
outcome::result< void > putNumberToIndexKey(storage::BufferStorage &map, const primitives::BlockInfo &block)
outcome::result< std::optional< primitives::Justification > > getJustification(const primitives::BlockId &block) const override
uint32_t BlockNumber
Definition: common.hpp:18
std::optional< common::Buffer > receipt
Definition: block_data.hpp:27
SLBuffer< std::numeric_limits< size_t >::max()> Buffer
Definition: buffer.hpp:244
outcome::result< void > putNumberToIndexKey(const primitives::BlockInfo &block) override
std::shared_ptr< storage::BufferStorage > storage_
outcome::result< std::optional< primitives::BlockData > > getBlockData(const primitives::BlockId &id) const override
static outcome::result< std::shared_ptr< BlockStorageImpl > > create(storage::trie::RootHash state_root, const std::shared_ptr< storage::BufferStorage > &storage, const std::shared_ptr< crypto::Hasher > &hasher)
std::optional< std::vector< primitives::BlockHash > > block_tree_leaves_
outcome::result< primitives::BlockInfo > getLastFinalized() const override
outcome::result< std::optional< common::Buffer > > getWithPrefix(const storage::BufferStorage &map, prefix::Prefix prefix, const primitives::BlockId &block_id)
outcome::result< bool > hasBlockHeader(const primitives::BlockId &id) const override
Check if header existing by provided block {.
outcome::result< std::vector< primitives::BlockHash > > getBlockTreeLeaves() const override
outcome::result< std::optional< primitives::BlockHeader > > getBlockHeader(const primitives::BlockId &id) const override
BlockNumber number
index of the block in the chain
boost::variant< BlockHash, BlockNumber > BlockId
Block id is the variant over BlockHash and BlockNumber.
Definition: block_id.hpp:18
outcome::result< void > removeBlock(const primitives::BlockInfo &block) override
outcome::result< bool > hasWithPrefix(const storage::BufferStorage &map, prefix::Prefix prefix, const primitives::BlockId &block_id)
outcome::result< std::optional< primitives::BlockBody > > getBlockBody(const primitives::BlockId &id) const override
BlockHeader header
block header
Definition: block.hpp:22
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
primitives::BlockHash hash
Definition: block_data.hpp:41
outcome::result< primitives::BlockHash > putBlockHeader(const primitives::BlockHeader &header) override
outcome::result< primitives::BlockHash > putBlock(const primitives::Block &block) override
BlockHash parent_hash
32-byte Blake2s hash of parent header
storage::trie::RootHash trieRoot(const std::vector< std::pair< common::Buffer, common::Buffer >> &key_vals)
Definition: common.cpp:32
common::Buffer prependPrefix(common::BufferView key, prefix::Prefix key_column)
primitives::BlockHash hash
Definition: block_data.hpp:24
BlockBody body
extrinsics collection
Definition: block.hpp:23
std::optional< primitives::BlockBody > body
Definition: block_data.hpp:26
std::shared_ptr< crypto::Hasher > hasher_
std::optional< primitives::Justification > justification
Definition: block_data.hpp:29