Kagome
Polkadot Runtime Engine in C++17
runtime_upgrade_tracker_impl.cpp
Go to the documentation of this file.
1 
7 
11 #include "log/profiling_logger.hpp"
14 
15 namespace kagome::runtime {
16  outcome::result<std::unique_ptr<RuntimeUpgradeTrackerImpl>>
18  std::shared_ptr<const blockchain::BlockHeaderRepository> header_repo,
19  std::shared_ptr<storage::BufferStorage> storage,
20  std::shared_ptr<const primitives::CodeSubstituteBlockIds>
21  code_substitutes,
22  std::shared_ptr<blockchain::BlockStorage> block_storage) {
23  BOOST_ASSERT(header_repo);
24  BOOST_ASSERT(storage);
25  BOOST_ASSERT(code_substitutes);
26  BOOST_ASSERT(block_storage);
27 
28  OUTCOME_TRY(encoded_opt,
29  storage->tryLoad(storage::kRuntimeHashesLookupKey));
30 
31  std::vector<RuntimeUpgradeData> saved_data{};
32  if (encoded_opt.has_value()) {
33  OUTCOME_TRY(
34  decoded,
35  scale::decode<std::vector<RuntimeUpgradeData>>(encoded_opt.value()));
36  saved_data = std::move(decoded);
37  }
38  return std::unique_ptr<RuntimeUpgradeTrackerImpl>{
39  new RuntimeUpgradeTrackerImpl(std::move(header_repo),
40  std::move(storage),
41  std::move(code_substitutes),
42  std::move(saved_data),
43  std::move(block_storage))};
44  }
45 
47  std::shared_ptr<const blockchain::BlockHeaderRepository> header_repo,
48  std::shared_ptr<storage::BufferStorage> storage,
49  std::shared_ptr<const primitives::CodeSubstituteBlockIds>
50  code_substitutes,
51  std::vector<RuntimeUpgradeData> &&saved_data,
52  std::shared_ptr<blockchain::BlockStorage> block_storage)
53  : runtime_upgrades_{std::move(saved_data)},
54  header_repo_{std::move(header_repo)},
55  storage_{std::move(storage)},
56  known_code_substitutes_{std::move(code_substitutes)},
57  block_storage_{std::move(block_storage)},
58  logger_{log::createLogger("StorageCodeProvider", "runtime")} {}
59 
61  const kagome::primitives::BlockInfo &block_info) const {
62  return known_code_substitutes_->contains(block_info);
63  }
64 
66  const primitives::BlockInfo &state,
67  const primitives::BlockInfo &chain_end) const noexcept {
68  // if the found state is finalized, it is guaranteed to not belong to a
69  // different fork
70  primitives::BlockInfo last_finalized;
71  if (block_tree_) {
72  last_finalized = block_tree_->getLastFinalized(); // less expensive
73  } else {
74  OUTCOME_TRY(block_info, block_storage_->getLastFinalized());
75  last_finalized = block_info;
76  }
77  if (last_finalized.number >= state.number) {
78  return true;
79  }
80  // a non-finalized state may belong to a different fork, need to check
81  // explicitly (can be expensive if blocks are far apart)
82  KAGOME_PROFILE_START(has_direct_chain)
83  bool has_direct_chain =
84  block_tree_->hasDirectChain(state.hash, chain_end.hash);
85  KAGOME_PROFILE_END(has_direct_chain)
86  return has_direct_chain;
87  }
88 
89  outcome::result<std::optional<storage::trie::RootHash>>
91  const primitives::BlockInfo &block,
92  std::vector<RuntimeUpgradeData>::const_reverse_iterator latest_upgrade_it)
93  const {
94  for (; latest_upgrade_it != runtime_upgrades_.rend(); latest_upgrade_it++) {
95  OUTCOME_TRY(in_chain, isStateInChain(latest_upgrade_it->block, block));
96  if (in_chain) {
98  logger_, latest_upgrade_it->state, block.hash, block.number);
99  SL_DEBUG(logger_,
100  "Pick runtime state at block {} for block {}",
101  latest_upgrade_it->block,
102  block);
103 
104  return latest_upgrade_it->state;
105  }
106  }
107  return std::nullopt;
108  }
109 
110  outcome::result<storage::trie::RootHash>
112  const primitives::BlockInfo &block) {
113  if (hasCodeSubstitute(block)) {
114  OUTCOME_TRY(push(block.hash));
115  }
116 
117  // if there are no known blocks with runtime upgrades, we just fall back to
118  // returning the state of the current block
119  if (runtime_upgrades_.empty()) {
120  // even if it doesn't actually upgrade runtime, still a solid source of
121  // runtime code
122  OUTCOME_TRY(state, push(block.hash));
123  SL_DEBUG(
124  logger_, "Pick runtime state at block {} for the same block", block);
125  return std::move(state);
126  }
127 
128  KAGOME_PROFILE_START(blocks_with_runtime_upgrade_search)
129  auto block_number = block.number;
130  auto latest_upgrade =
131  std::upper_bound(runtime_upgrades_.begin(),
132  runtime_upgrades_.end(),
133  block_number,
134  [](auto block_number, auto const &upgrade_data) {
135  return block_number < upgrade_data.block.number;
136  });
137  KAGOME_PROFILE_END(blocks_with_runtime_upgrade_search)
138 
139  if (latest_upgrade == runtime_upgrades_.begin()) {
140  // if we have no info on updates before this block, we just return its
141  // state
142  OUTCOME_TRY(block_header, header_repo_->getBlockHeader(block.hash));
143  SL_DEBUG(
144  logger_, "Pick runtime state at block {} for the same block", block);
145  return block_header.state_root;
146  }
147 
148  // this conversion also steps back on one element
149  auto reverse_latest_upgrade = std::make_reverse_iterator(latest_upgrade);
150 
151  // we are now at the last element in block_with_runtime_upgrade which is
152  // less or equal to our \arg block number
153  // we may have several entries with the same block number, we have to pick
154  // one which is the predecessor of our block
155  KAGOME_PROFILE_START(search_for_proper_fork)
156  OUTCOME_TRY(proper_fork, findProperFork(block, reverse_latest_upgrade));
157  KAGOME_PROFILE_END(search_for_proper_fork)
158  if (proper_fork.has_value()) {
159  return proper_fork.value();
160  }
161  // if this is an orphan block for some reason, just return its state_root
162  // (there is no other choice)
163  OUTCOME_TRY(block_header, header_repo_->getBlockHeader(block.hash));
164  logger_->warn("Block {}, a child of block {} is orphan",
165  block,
166  primitives::BlockInfo(block_header.number - 1,
167  block_header.parent_hash));
168  return block_header.state_root;
169  }
170 
171  outcome::result<primitives::BlockInfo>
173  const storage::trie::RootHash &state) const {
174  auto it = std::find_if(
175  runtime_upgrades_.begin(),
176  runtime_upgrades_.end(),
177  [&state](const auto &item) { return state == item.state; });
178  if (it != runtime_upgrades_.end()) {
179  return it->block;
180  }
181  return outcome::failure(RuntimeUpgradeTrackerError::NOT_FOUND);
182  }
183 
185  std::shared_ptr<primitives::events::ChainSubscriptionEngine>
186  chain_sub_engine,
187  std::shared_ptr<const blockchain::BlockTree> block_tree) {
188  block_tree_ = std::move(block_tree);
189  BOOST_ASSERT(block_tree_ != nullptr);
190 
192  std::make_shared<primitives::events::ChainEventSubscriber>(
193  chain_sub_engine);
194  BOOST_ASSERT(chain_subscription_ != nullptr);
195 
196  auto chain_subscription_set_id =
197  chain_subscription_->generateSubscriptionSetId();
198  chain_subscription_->subscribe(
199  chain_subscription_set_id,
201  chain_subscription_->setCallback(
202  [this](auto set_id,
203  auto &receiver,
205  const primitives::events::ChainEventParams &event_params) {
207  return;
208  }
209  const auto &block_hash =
210  boost::get<primitives::events::NewRuntimeEventParams>(
211  event_params)
212  .get();
213  SL_INFO(logger_, "Runtime upgrade at block {}", block_hash.toHex());
214  (void)push(block_hash);
215  });
216  }
217 
218  outcome::result<storage::trie::RootHash> RuntimeUpgradeTrackerImpl::push(
219  const primitives::BlockHash &hash) {
220  OUTCOME_TRY(header, header_repo_->getBlockHeader(hash));
221  runtime_upgrades_.emplace_back(primitives::BlockInfo{header.number, hash},
222  std::move(header.state_root));
223  std::sort(runtime_upgrades_.begin(),
224  runtime_upgrades_.end(),
225  [](const auto &lhs, const auto &rhs) {
226  return lhs.block.number < rhs.block.number;
227  });
228  save();
229  return header.state_root;
230  }
231 
233  auto encoded_res = scale::encode(runtime_upgrades_);
234  if (encoded_res.has_value()) {
235  auto put_res = storage_->put(storage::kRuntimeHashesLookupKey,
236  common::Buffer(encoded_res.value()));
237  if (not put_res.has_value()) {
238  SL_ERROR(logger_,
239  "Could not store hashes of blocks changing runtime: {}",
240  put_res.error().message());
241  }
242  } else {
243  SL_ERROR(logger_,
244  "Could not store hashes of blocks changing runtime: {}",
245  encoded_res.error().message());
246  }
247  }
248 } // namespace kagome::runtime
249 
252  switch (e) {
253  case E::NOT_FOUND:
254  return "Block hash for the given state not found among runtime upgrades.";
255  }
256  return "unknown error";
257 }
#define KAGOME_PROFILE_END(scope)
Class represents arbitrary (including empty) byte buffer.
Definition: buffer.hpp:29
void subscribeToBlockchainEvents(std::shared_ptr< primitives::events::ChainSubscriptionEngine > chain_sub_engine, std::shared_ptr< const blockchain::BlockTree > block_tree)
outcome::result< std::optional< storage::trie::RootHash > > findProperFork(const primitives::BlockInfo &block, std::vector< RuntimeUpgradeData >::const_reverse_iterator latest_upgrade_it) const
#define SL_TRACE_FUNC_CALL(logger, ret,...)
Definition: logger.hpp:142
std::shared_ptr< primitives::events::ChainEventSubscriber > chain_subscription_
boost::variant< std::nullopt_t, HeadsEventParams, RuntimeVersionEventParams, NewRuntimeEventParams > ChainEventParams
Definition: event_types.hpp:51
static outcome::result< std::unique_ptr< RuntimeUpgradeTrackerImpl > > create(std::shared_ptr< const blockchain::BlockHeaderRepository > header_repo, std::shared_ptr< storage::BufferStorage > storage, std::shared_ptr< const primitives::CodeSubstituteBlockIds > code_substitutes, std::shared_ptr< blockchain::BlockStorage > block_storage)
std::shared_ptr< storage::BufferStorage > storage_
outcome::result< primitives::BlockInfo > getLastCodeUpdateBlockInfo(const storage::trie::RootHash &state) const override
std::shared_ptr< const blockchain::BlockHeaderRepository > header_repo_
std::shared_ptr< const primitives::CodeSubstituteBlockIds > known_code_substitutes_
std::shared_ptr< blockchain::BlockStorage > block_storage_
bool hasCodeSubstitute(const kagome::primitives::BlockInfo &block_info) const
outcome::result< storage::trie::RootHash > getLastCodeUpdateState(const primitives::BlockInfo &block) override
OUTCOME_CPP_DEFINE_CATEGORY(kagome::runtime, RuntimeUpgradeTrackerError, e)
#define KAGOME_PROFILE_START(scope)
outcome::result< bool > isStateInChain(const primitives::BlockInfo &state, const primitives::BlockInfo &chain_end) const noexcept
std::shared_ptr< const blockchain::BlockTree > block_tree_
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
outcome::result< storage::trie::RootHash > push(const primitives::BlockHash &hash)
RuntimeUpgradeTrackerImpl(std::shared_ptr< const blockchain::BlockHeaderRepository > header_repo, std::shared_ptr< storage::BufferStorage > storage, std::shared_ptr< const primitives::CodeSubstituteBlockIds > code_substitutes, std::vector< RuntimeUpgradeData > &&saved_data, std::shared_ptr< blockchain::BlockStorage > block_storage)
const common::Buffer kRuntimeHashesLookupKey