Kagome
Polkadot Runtime Engine in C++17
chain_spec_impl.cpp
Go to the documentation of this file.
1 
7 
8 #include <boost/property_tree/json_parser.hpp>
9 #include <charconv>
10 #include <libp2p/multi/multiaddress.hpp>
11 #include <system_error>
12 
13 #include "common/hexutil.hpp"
14 #include "common/visitor.hpp"
15 
18  switch (e) {
19  case E::MISSING_ENTRY:
20  return "A required entry is missing in the config file";
21  case E::MISSING_PEER_ID:
22  return "Peer id is missing in a multiaddress provided in the config file";
23  case E::PARSER_ERROR:
24  return "Internal parser error";
25  case E::NOT_IMPLEMENTED:
26  return "Known entry name, but parsing not implemented";
27  }
28  return "Unknown error in ChainSpecImpl";
29 }
30 
31 namespace kagome::application {
32 
33  namespace pt = boost::property_tree;
34 
35  outcome::result<std::shared_ptr<ChainSpecImpl>> ChainSpecImpl::loadFrom(
36  const std::string &path) {
37  // done so because of private constructor
38  std::shared_ptr<ChainSpecImpl> config_storage{new ChainSpecImpl};
39  config_storage->known_code_substitutes_ =
40  std::make_shared<primitives::CodeSubstituteBlockIds>();
41  OUTCOME_TRY(config_storage->loadFromJson(path));
42 
43  return config_storage;
44  }
45 
46  outcome::result<void> ChainSpecImpl::loadFromJson(
47  const std::string &file_path) {
48  config_path_ = file_path;
49  pt::ptree tree;
50  try {
51  pt::read_json(file_path, tree);
52  } catch (pt::json_parser_error &e) {
53  log_->error(
54  "Parser error: {}, line {}: {}", e.filename(), e.line(), e.message());
55  return Error::PARSER_ERROR;
56  }
57 
58  OUTCOME_TRY(loadFields(tree));
59  OUTCOME_TRY(loadGenesis(tree));
60  OUTCOME_TRY(loadBootNodes(tree));
61 
62  return outcome::success();
63  }
64 
65  outcome::result<void> ChainSpecImpl::loadFields(
66  const boost::property_tree::ptree &tree) {
67  OUTCOME_TRY(name, ensure("name", tree.get_child_optional("name")));
68  name_ = name.get<std::string>("");
69 
70  OUTCOME_TRY(id, ensure("id", tree.get_child_optional("id")));
71  id_ = id.get<std::string>("");
72 
73  // acquiring "chainType" value
74  if (auto entry = tree.get_child_optional("chainType"); entry.has_value()) {
75  chain_type_ = entry.value().get<std::string>("");
76  } else {
77  log_->warn(
78  "Field 'chainType' was not specified in the chain spec. 'Live' by "
79  "default.");
80  chain_type_ = std::string("Live");
81  }
82 
83  auto telemetry_endpoints_opt =
84  tree.get_child_optional("telemetryEndpoints");
85  if (telemetry_endpoints_opt.has_value()
86  && telemetry_endpoints_opt.value().get<std::string>("") != "null") {
87  for (auto &[_, endpoint] : telemetry_endpoints_opt.value()) {
88  if (auto it = endpoint.begin(); endpoint.size() >= 2) {
89  auto &uri = it->second;
90  auto &priority = (++it)->second;
91  telemetry_endpoints_.emplace_back(uri.get<std::string>(""),
92  priority.get<size_t>(""));
93  }
94  }
95  }
96 
97  auto protocol_id_opt = tree.get_child_optional("protocolId");
98  if (protocol_id_opt.has_value()) {
99  auto protocol_id = protocol_id_opt.value().get<std::string>("");
100  if (protocol_id != "null") {
101  protocol_id_ = std::move(protocol_id);
102  }
103  }
104 
105  auto properties_opt = tree.get_child_optional("properties");
106  if (properties_opt.has_value()
107  && properties_opt.value().get<std::string>("") != "null") {
108  for (auto &[propertyName, propertyValue] : properties_opt.value()) {
109  properties_.emplace(propertyName, propertyValue.get<std::string>(""));
110  }
111  }
112 
113  auto fork_blocks_opt = tree.get_child_optional("forkBlocks");
114  if (fork_blocks_opt.has_value()
115  && fork_blocks_opt.value().get<std::string>("") != "null") {
116  /*
117  * Currently there is no special handling implemented for forkBlocks.
118  * We read them, but do it in terms of legacy chain specs' support.
119  * We assume we are safe even if there are some values specified.
120  *
121  * The kagome node currently is going to sync with the main fork only.
122  * The full implementation of forkBlocks support is not worth it now but
123  * can be added later on.
124  */
125  log_->warn(
126  "A non-empty set of 'forkBlocks' encountered! They might not be "
127  "taken into account!");
128  for (auto &[_, fork_block] : fork_blocks_opt.value()) {
129  OUTCOME_TRY(hash,
131  fork_block.get<std::string>("")));
132  fork_blocks_.emplace(hash);
133  }
134  }
135 
136  auto bad_blocks_opt = tree.get_child_optional("badBlocks");
137  if (bad_blocks_opt.has_value()
138  && bad_blocks_opt.value().get<std::string>("") != "null") {
139  /*
140  * Currently there is no special handling implemented for badBlocks.
141  * We read them, but do it in terms of legacy chain specs' support.
142  * We assume we are safe even if there are some values specified.
143  *
144  * The kagome node currently is going to sync with the main fork only.
145  * The full implementation of badBlocks support is not worth it now but
146  * can be added later on.
147  */
148  log_->warn(
149  "A non-empty set of 'badBlocks' encountered! They might not be "
150  "taken into account!");
151  for (auto &[_, bad_block] : bad_blocks_opt.value()) {
152  OUTCOME_TRY(hash,
154  bad_block.get<std::string>("")));
155  bad_blocks_.emplace(hash);
156  }
157  }
158 
159  auto consensus_engine_opt = tree.get_child_optional("consensusEngine");
160  if (consensus_engine_opt.has_value()) {
161  auto consensus_engine = consensus_engine_opt.value().get<std::string>("");
162  if (consensus_engine != "null") {
163  consensus_engine_.emplace(std::move(consensus_engine));
164  }
165  }
166 
167  auto code_substitutes_opt = tree.get_child_optional("codeSubstitutes");
168  if (code_substitutes_opt.has_value()) {
169  for (const auto &[block_id, code] : code_substitutes_opt.value()) {
170  OUTCOME_TRY(block_id_parsed, parseBlockId(block_id));
171  known_code_substitutes_->emplace(block_id_parsed);
172  }
173  }
174 
175  return outcome::success();
176  }
177 
178  outcome::result<primitives::BlockId> ChainSpecImpl::parseBlockId(
179  const std::string_view block_id_str) const {
180  primitives::BlockId block_id;
181  if (block_id_str.rfind("0x", 0) != std::string::npos) { // Is it hash?
182  OUTCOME_TRY(block_hash, common::Hash256::fromHexWithPrefix(block_id_str));
183  block_id = block_hash;
184  } else {
185  primitives::BlockNumber block_num;
186  auto res = std::from_chars(block_id_str.data(),
187  block_id_str.data() + block_id_str.size(),
188  block_num);
189  if (res.ec != std::errc()) {
190  return Error::PARSER_ERROR;
191  }
192  block_id = block_num;
193  }
194  return block_id;
195  }
196 
197  outcome::result<common::Buffer> ChainSpecImpl::fetchCodeSubstituteByBlockInfo(
198  const primitives::BlockInfo &block_info) const {
199  if (!known_code_substitutes_->contains(block_info)) {
200  return Error::MISSING_ENTRY;
201  }
202 
203  pt::ptree tree;
204  try {
205  pt::read_json(config_path_, tree);
206  } catch (pt::json_parser_error &e) {
207  log_->error(
208  "Parser error: {}, line {}: {}", e.filename(), e.line(), e.message());
209  return Error::PARSER_ERROR;
210  }
211 
212  auto code_substitutes_opt = tree.get_child_optional("codeSubstitutes");
213  if (code_substitutes_opt.has_value()) {
214  for (const auto &[_block_id, _code] : code_substitutes_opt.value()) {
215  OUTCOME_TRY(block_id_parsed, parseBlockId(_block_id));
216  if (visit_in_place(
217  block_id_parsed,
218  [&block_info](primitives::BlockNumber &arg) {
219  return arg == block_info.number;
220  },
221  [&block_info](primitives::BlockHash &arg) {
222  return arg == block_info.hash;
223  })) {
224  OUTCOME_TRY(code_processed, common::unhexWith0x(_code.data()));
225  return outcome::success(code_processed);
226  }
227  }
228  }
229  return Error::MISSING_ENTRY;
230  }
231 
232  outcome::result<void> ChainSpecImpl::loadGenesis(
233  const boost::property_tree::ptree &tree) {
234  OUTCOME_TRY(genesis_tree,
235  ensure("genesis", tree.get_child_optional("genesis")));
236  OUTCOME_TRY(genesis_raw_tree,
237  ensure("genesis/raw", genesis_tree.get_child_optional("raw")));
238  boost::property_tree::ptree top_tree;
239  // v0.7+ format
240  if (auto top_tree_opt = genesis_raw_tree.get_child_optional("top");
241  top_tree_opt.has_value()) {
242  top_tree = top_tree_opt.value();
243  } else {
244  // Try to fall back to v0.6
245  top_tree = genesis_raw_tree.begin()->second;
246  }
247 
248  auto read_key_block = [](const auto &tree,
249  GenesisRawData &data) -> outcome::result<void> {
250  for (const auto &[child_key, child_value] : tree) {
251  // get rid of leading 0x for key and value and unhex
252  OUTCOME_TRY(key_processed, common::unhexWith0x(child_key));
253  OUTCOME_TRY(value_processed, common::unhexWith0x(child_value.data()));
254  data.emplace_back(std::move(key_processed), std::move(value_processed));
255  }
256  return outcome::success();
257  };
258 
259  if (auto children_default_tree_opt =
260  genesis_raw_tree.get_child_optional("childrenDefault");
261  children_default_tree_opt.has_value()) {
262  for (const auto &[key, value] : children_default_tree_opt.value()) {
263  GenesisRawData child;
264  OUTCOME_TRY(read_key_block(value, child));
265  OUTCOME_TRY(key_processed, common::unhexWith0x(key));
266  children_default_.emplace(std::move(key_processed), std::move(child));
267  log_->trace("Child address {} added", key);
268  }
269  }
270 
271  OUTCOME_TRY(read_key_block(top_tree, genesis_));
272 
273  return outcome::success();
274  }
275 
276  outcome::result<void> ChainSpecImpl::loadBootNodes(
277  const boost::property_tree::ptree &tree) {
278  OUTCOME_TRY(boot_nodes,
279  ensure("bootNodes", tree.get_child_optional("bootNodes")));
280  for (auto &v : boot_nodes) {
281  if (auto ma_res = libp2p::multi::Multiaddress::create(v.second.data())) {
282  auto &&multiaddr = ma_res.value();
283  if (auto peer_id_base58 = multiaddr.getPeerId();
284  peer_id_base58.has_value()) {
285  OUTCOME_TRY(libp2p::peer::PeerId::fromBase58(peer_id_base58.value()));
286  boot_nodes_.emplace_back(std::move(multiaddr));
287  } else {
288  return Error::MISSING_PEER_ID;
289  }
290  } else {
291  log_->warn("Unsupported multiaddress '{}'. Ignoring that boot node",
292  v.second.data());
293  }
294  }
295  return outcome::success();
296  }
297 
298 } // namespace kagome::application
std::shared_ptr< primitives::CodeSubstituteBlockIds > known_code_substitutes_
std::vector< libp2p::multi::Multiaddress > boot_nodes_
std::map< std::string, std::string > properties_
outcome::result< void > loadFromJson(const std::string &file_path)
outcome::result< primitives::BlockId > parseBlockId(const std::string_view block_id_str) const
outcome::result< std::vector< uint8_t > > unhexWith0x(std::string_view hex_with_prefix)
Unhex hex-string with 0x in the begining.
Definition: hexutil.cpp:89
outcome::result< common::Buffer > fetchCodeSubstituteByBlockInfo(const primitives::BlockInfo &block_info) const override
std::vector< std::pair< std::string, size_t > > telemetry_endpoints_
OUTCOME_CPP_DEFINE_CATEGORY(kagome::application, ChainSpecImpl::Error, e)
ChildrenDefaultRawData children_default_
static outcome::result< std::shared_ptr< ChainSpecImpl > > loadFrom(const std::string &config_path)
outcome::result< void > loadFields(const boost::property_tree::ptree &tree)
std::set< primitives::BlockHash > fork_blocks_
std::vector< std::pair< common::Buffer, common::Buffer >> GenesisRawData
Definition: chain_spec.hpp:18
static outcome::result< Blob< size_ > > fromHexWithPrefix(std::string_view hex)
Definition: blob.hpp:197
uint32_t BlockNumber
Definition: common.hpp:18
std::optional< std::string > consensus_engine_
std::set< primitives::BlockHash > bad_blocks_
outcome::result< void > loadGenesis(const boost::property_tree::ptree &tree)
boost::variant< BlockHash, BlockNumber > BlockId
Block id is the variant over BlockHash and BlockNumber.
Definition: block_id.hpp:18
outcome::result< void > loadBootNodes(const boost::property_tree::ptree &tree)
const std::string & name() const override
outcome::result< std::decay_t< T > > ensure(std::string_view entry_name, boost::optional< T > opt_entry)