Scorum
application.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015 Cryptonomex, Inc., and contributors.
3  *
4  * The MIT License
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 #include <scorum/app/api.hpp>
26 #include <scorum/app/chain_api.hpp>
31 #include <scorum/app/plugin.hpp>
39 
44 #include <scorum/egenesis/egenesis.hpp>
45 
46 #include <fc/time.hpp>
47 
48 #include <graphene/net/core_messages.hpp>
49 #include <graphene/net/exceptions.hpp>
50 
51 #include <graphene/utilities/key_conversion.hpp>
52 #include <graphene/utilities/git_revision.hpp>
53 #include <fc/git_revision.hpp>
54 
55 #include <fc/smart_ref_impl.hpp>
56 
57 #include <fc/io/fstream.hpp>
58 #include <fc/rpc/api_connection.hpp>
59 #include <fc/rpc/websocket_api.hpp>
60 #include <fc/network/resolve.hpp>
61 #include <fc/string.hpp>
62 
63 #include <boost/algorithm/string.hpp>
64 #include <boost/filesystem/path.hpp>
65 #include <boost/signals2.hpp>
66 #include <boost/range/algorithm/reverse.hpp>
67 
68 #include <iostream>
69 #include <fstream>
70 #include <set>
71 
72 #include <fc/log/file_appender.hpp>
73 #include <fc/log/logger.hpp>
74 #include <fc/log/logger_config.hpp>
75 
76 #include <boost/range/adaptor/reversed.hpp>
77 
79 
80 namespace scorum {
81 namespace app {
82 
83 using graphene::net::item_hash_t;
84 using graphene::net::item_id;
85 using graphene::net::message;
86 using graphene::net::block_message;
87 using graphene::net::trx_message;
88 
89 using protocol::block_header;
90 using protocol::signed_block_header;
91 using protocol::signed_block;
93 
94 namespace bpo = boost::program_options;
95 
96 api_context::api_context(application& _app, const std::string& _api_name, std::weak_ptr<api_session_data> _session)
97  : app(_app)
98  , api_name(_api_name)
99  , session(_session)
100 {
101 }
102 
103 namespace detail {
104 
105 using plugins_type = std::map<std::string, std::shared_ptr<abstract_plugin>>;
106 using plugin_names_type = std::set<std::string>;
107 
108 class application_impl : public graphene::net::node_delegate
109 {
110 public:
111  fc::optional<fc::temp_file> _lock_file;
112  bool _is_block_producer = false;
113  bool _force_validate = false;
114 
115  void reset_p2p_node(const fc::path& data_dir)
116  {
117  try
118  {
119  _p2p_network = std::make_shared<graphene::net::node>("Graphene Reference Implementation");
120 
121  _p2p_network->load_configuration(data_dir / "p2p");
122  _p2p_network->set_node_delegate(this);
123 
124  if (_options->count("seed-node"))
125  {
126  auto seeds = _options->at("seed-node").as<std::vector<std::string>>();
127  for (const std::string& endpoint_string : seeds)
128  {
129  try
130  {
131  std::vector<fc::ip::endpoint> endpoints = resolve_string_to_ip_endpoints(endpoint_string);
132  for (const fc::ip::endpoint& endpoint : endpoints)
133  {
134  ilog("Adding seed node ${endpoint}", ("endpoint", endpoint));
135  _p2p_network->add_node(endpoint);
136  _p2p_network->connect_to_endpoint(endpoint);
137  }
138  }
139  catch (const fc::exception& e)
140  {
141  wlog("caught exception ${e} while adding seed node ${endpoint}",
142  ("e", e.to_detail_string())("endpoint", endpoint_string));
143  }
144  }
145  }
146 
147  if (_options->count("p2p-endpoint"))
148  {
149  auto p2p_endpoint = _options->at("p2p-endpoint").as<std::string>();
150  auto endpoints = resolve_string_to_ip_endpoints(p2p_endpoint);
151  FC_ASSERT(endpoints.size(), "p2p-endpoint ${hostname} did not resolve", ("hostname", p2p_endpoint));
152  _p2p_network->listen_on_endpoint(endpoints[0], true);
153  }
154  else
155  {
156  _p2p_network->listen_on_port(0, false);
157  }
158 
159  if (_options->count("p2p-max-connections"))
160  {
161  fc::variant_object node_param = fc::variant_object(
162  "maximum_number_of_connections", fc::variant(_options->at("p2p-max-connections").as<uint32_t>()));
163  _p2p_network->set_advanced_node_parameters(node_param);
164  ilog("Setting p2p max connections to ${n}", ("n", node_param["maximum_number_of_connections"]));
165  }
166 
167  _p2p_network->listen_to_p2p_network();
168  ilog("Configured p2p node to listen on ${ip}", ("ip", _p2p_network->get_actual_listening_endpoint()));
169 
170  _p2p_network->connect_to_p2p_network();
171  block_id_type head_block_id;
172  _chain_db->with_read_lock([&]() { head_block_id = _chain_db->head_block_id(); });
173  idump((head_block_id));
174  _p2p_network->sync_from(
175  graphene::net::item_id(graphene::net::core_message_type_enum::block_message_type, head_block_id),
176  std::vector<uint32_t>());
177  }
178  FC_CAPTURE_AND_RETHROW()
179  }
180 
181  std::vector<fc::ip::endpoint> resolve_string_to_ip_endpoints(const std::string& endpoint_string)
182  {
183  try
184  {
185  std::string::size_type colon_pos = endpoint_string.find(':');
186  if (colon_pos == std::string::npos)
187  FC_THROW("Missing required port number in endpoint string \"${endpoint_string}\"",
188  ("endpoint_string", endpoint_string));
189  std::string port_string = endpoint_string.substr(colon_pos + 1);
190  try
191  {
192  uint16_t port = boost::lexical_cast<uint16_t>(port_string);
193 
194  std::string hostname = endpoint_string.substr(0, colon_pos);
195  std::vector<fc::ip::endpoint> endpoints = fc::resolve(hostname, port);
196  if (endpoints.empty())
197  FC_THROW_EXCEPTION(fc::unknown_host_exception, "The host name can not be resolved: ${hostname}",
198  ("hostname", hostname));
199  return endpoints;
200  }
201  catch (const boost::bad_lexical_cast&)
202  {
203  FC_THROW("Bad port: ${port}", ("port", port_string));
204  }
205  }
206  FC_CAPTURE_AND_RETHROW((endpoint_string))
207  }
208 
210  {
211  try
212  {
213  if (!_options->count("rpc-endpoint"))
214  {
215  return;
216  }
217 
218  _websocket_server = std::make_shared<fc::http::websocket_server>();
219 
220  _websocket_server->on_connection([&](const fc::http::websocket_connection_ptr& c) { on_connection(c); });
221  auto rpc_endpoint = _options->at("rpc-endpoint").as<std::string>();
222  ilog("Configured websocket rpc to listen on ${ip}", ("ip", rpc_endpoint));
223  auto endpoints = resolve_string_to_ip_endpoints(rpc_endpoint);
224  FC_ASSERT(endpoints.size(), "rpc-endpoint ${hostname} did not resolve", ("hostname", rpc_endpoint));
225  _websocket_server->listen(endpoints[0]);
226  _websocket_server->start_accept();
227  }
228  FC_CAPTURE_AND_RETHROW()
229  }
230 
232  {
233  try
234  {
235  if (!_options->count("rpc-tls-endpoint"))
236  {
237  return;
238  }
239  if (!_options->count("server-pem"))
240  {
241  wlog("Please specify a server-pem to use rpc-tls-endpoint");
242  return;
243  }
244 
245  std::string password
246  = _options->count("server-pem-password") ? _options->at("server-pem-password").as<std::string>() : "";
247  _websocket_tls_server = std::make_shared<fc::http::websocket_tls_server>(
248  _options->at("server-pem").as<std::string>(), password);
249 
250  _websocket_tls_server->on_connection(
251  [this](const fc::http::websocket_connection_ptr& c) { on_connection(c); });
252  auto rpc_tls_endpoint = _options->at("rpc-tls-endpoint").as<std::string>();
253  ilog("Configured websocket TLS rpc to listen on ${ip}", ("ip", rpc_tls_endpoint));
254  auto endpoints = resolve_string_to_ip_endpoints(rpc_tls_endpoint);
255  FC_ASSERT(endpoints.size(), "rpc-tls-endpoint ${hostname} did not resolve", ("hostname", rpc_tls_endpoint));
256  _websocket_tls_server->listen(endpoints[0]);
257  _websocket_tls_server->start_accept();
258  }
259  FC_CAPTURE_AND_RETHROW()
260  }
261 
262  void on_connection(const fc::http::websocket_connection_ptr& c)
263  {
264  std::shared_ptr<api_session_data> session = std::make_shared<api_session_data>();
265  session->wsc = std::make_shared<fc::rpc::websocket_api_connection>(*c);
266 
267  for (const std::string& name : _public_apis)
268  {
269  api_context ctx(*_self, name, session);
270  fc::api_ptr api = create_api_by_name(ctx);
271  if (!api)
272  {
273  elog("Couldn't create API ${name}", ("name", name));
274  continue;
275  }
276  session->api_map[name] = api;
277  api->register_api(*session->wsc);
278  }
279  c->set_session_data(session);
280  }
281 
282  application_impl(application* self, std::shared_ptr<chain::database> chain_db)
283  : _self(self)
284  , _chain_db(std::move(chain_db))
285  {
286  }
287 
289  {
290  }
291 
293  {
294  _self->register_api_factory<login_api>("login_api");
299  _self->register_api_factory<network_node_api>("network_node_api");
300  _self->register_api_factory<network_broadcast_api>("network_broadcast_api");
301  }
302 
304  {
305  std::string genesis_str;
306 
307  if (_options->count("genesis-json"))
308  {
309  fc::path genesis_json_filename = _options->at("genesis-json").as<boost::filesystem::path>();
310 
311  fc::read_file_contents(genesis_json_filename, genesis_str);
312  }
313  else
314  {
315  scorum::egenesis::compute_egenesis_json(genesis_str);
316  }
317 
318  FC_ASSERT(!genesis_str.empty());
319 
320  genesis_state = fc::json::from_string(genesis_str).as<genesis_state_type>();
321  genesis_state.initial_chain_id = fc::sha256::hash(genesis_str);
322  }
323 
324  void startup()
325  {
326  try
327  {
328  static const char* default_data_subdir = "blockchain";
329 
330  genesis_state_type genesis_state;
331  compute_genesis_state(genesis_state);
332 
333  ilog("node chain ID: ${chain_id}", ("chain_id", genesis_state.initial_chain_id));
334 
335  if (_options->count("data-dir"))
336  {
337  _data_dir = fc::path(_options->at("data-dir").as<boost::filesystem::path>());
338  // use lexically_normal for boost version >= 1.60
339  _data_dir = boost::filesystem::absolute(_data_dir);
340  }
341 
342  _shared_file_size = fc::parse_size(_options->at("shared-file-size").as<std::string>());
343  ilog("shared_file_size is ${n} bytes", ("n", _shared_file_size));
345 
346  if (_options->count("check-locks"))
347  {
348  _chain_db->set_require_locking(true);
349  }
350 
351  if (_options->count("shared-file-dir"))
352  {
353  _shared_dir = fc::path(_options->at("shared-file-dir").as<boost::filesystem::path>());
354  if (_shared_dir.is_relative())
356  }
357  else
358  {
359  _shared_dir = _data_dir / default_data_subdir;
360  }
361 
362  fc::path block_log_dir = _data_dir / default_data_subdir;
363 
364  if (!_self->is_read_only())
365  {
366  ilog("Starting Scorum node in write mode.");
367  _max_block_age = _options->at("max-block-age").as<int32_t>();
368 
369  if (_options->count("resync-blockchain"))
370  {
371  _chain_db->wipe(block_log_dir, _shared_dir, true);
372  }
373 
374  _chain_db->set_flush_interval(_options->at("flush").as<uint32_t>());
375 
376  flat_map<uint32_t, block_id_type> loaded_checkpoints;
377  if (_options->count("checkpoint"))
378  {
379  auto cps = _options->at("checkpoint").as<std::vector<std::string>>();
380  loaded_checkpoints.reserve(cps.size());
381  for (auto cp : cps)
382  {
383  auto item = fc::json::from_string(cp).as<std::pair<uint32_t, block_id_type>>();
384  loaded_checkpoints[item.first] = item.second;
385  }
386  }
387  _chain_db->add_checkpoints(loaded_checkpoints);
388 
389  if (_options->count("replay-blockchain") && !_options->count("resync-blockchain"))
390  {
391  ilog("Replaying blockchain on user request.");
392 
393  uint32_t skip_flags = _chain_db->get_reindex_skip_flags();
394  if (!_options->at("replay-skip-witness-schedule-check").as<bool>())
395  skip_flags &= ~database::skip_witness_schedule_check;
396 
397  _chain_db->reindex(block_log_dir, _shared_dir, _shared_file_size, skip_flags, genesis_state);
398  }
399  else
400  {
401  _chain_db->open(block_log_dir, _shared_dir, _shared_file_size, chainbase::database::read_write,
402  genesis_state);
403  }
404 
405  if (_options->count("force-validate"))
406  {
407  ilog("All transaction signatures will be validated");
408  _force_validate = true;
409  }
410  }
411  else
412  {
413  ilog("Starting Scorum node in read mode.");
414  _chain_db->open(block_log_dir, _shared_dir, _shared_file_size, chainbase::database::read_only,
415  genesis_state);
416 
417  if (_options->count("read-forward-rpc"))
418  {
419  try
420  {
421  _self->_remote_endpoint = _options->at("read-forward-rpc").as<std::string>();
422  }
423  catch (fc::exception& e)
424  {
425  wlog("Error connecting to remote RPC, network api forwarding disabled. ${e}",
426  ("e", e.to_detail_string()));
427  }
428  }
429  }
430  _chain_db->show_free_memory(true);
431 
432  if (_options->count("api-user"))
433  {
434  for (const std::string& api_access_str : _options->at("api-user").as<std::vector<std::string>>())
435  {
436  api_access_info info = fc::json::from_string(api_access_str).as<api_access_info>();
437  FC_ASSERT(info.username != "");
438  _apiaccess.permission_map[info.username] = info;
439  }
440  }
441  else
442  {
443  // TODO: Remove this generous default access policy
444  // when the UI logs in properly
446  api_access_info wild_access;
447  wild_access.username = "*";
448  wild_access.password_hash_b64 = "*";
449  wild_access.password_salt_b64 = "*";
450  wild_access.allowed_apis.push_back(API_DATABASE);
451  wild_access.allowed_apis.push_back(API_CHAIN);
452  wild_access.allowed_apis.push_back(ADVERTISING_API_NAME);
453  wild_access.allowed_apis.push_back("network_broadcast_api");
454  wild_access.allowed_apis.push_back("tag_api");
455  wild_access.allowed_apis.push_back(API_ACCOUNT_HISTORY);
456  wild_access.allowed_apis.push_back(API_BLOCKCHAIN_HISTORY);
457  wild_access.allowed_apis.push_back(API_ACCOUNT_STATISTICS);
458  wild_access.allowed_apis.push_back(API_BLOCKCHAIN_STATISTICS);
459  _apiaccess.permission_map["*"] = wild_access;
460  }
461 
462  for (const std::string& arg : _options->at("public-api").as<std::vector<std::string>>())
463  {
464  std::vector<std::string> names;
465  boost::split(names, arg, boost::is_any_of(" \t,"));
466  for (const std::string& name : names)
467  {
468  ilog("API ${name} enabled publicly", ("name", name));
469  _public_apis.push_back(name);
470  }
471  }
472  _running = true;
473 
474  if (!_self->is_read_only())
475  {
477  }
478 
481  }
482  FC_LOG_AND_RETHROW()
483  }
484 
485  optional<api_access_info> get_api_access_info(const std::string& username) const
486  {
487  optional<api_access_info> result;
488  auto it = _apiaccess.permission_map.find(username);
489  if (it == _apiaccess.permission_map.end())
490  {
491  it = _apiaccess.permission_map.find("*");
492  if (it == _apiaccess.permission_map.end())
493  {
494  return result;
495  }
496  }
497  return it->second;
498  }
499 
500  void set_api_access_info(const std::string& username, api_access_info&& permissions)
501  {
502  _apiaccess.permission_map.insert(std::make_pair(username, std::move(permissions)));
503  }
504 
505  void register_api_factory(const std::string& name, std::function<fc::api_ptr(const api_context&)> factory)
506  {
507  _api_factories_by_name[name] = factory;
508  }
509 
510  fc::api_ptr create_api_by_name(const api_context& ctx)
511  {
512  auto it = _api_factories_by_name.find(ctx.api_name);
513  if (it == _api_factories_by_name.end())
514  {
515  wlog("unknown api: ${api}", ("api", ctx.api_name));
516  return nullptr;
517  }
518  return it->second(ctx);
519  }
520 
524  virtual bool has_item(const graphene::net::item_id& id) override
525  {
526  // If the node is shutting down we don't care about fetching items
527  if (!_running)
528  {
529  return true;
530  }
531 
532  try
533  {
534  return _chain_db->with_read_lock([&]() {
535  if (id.item_type == graphene::net::block_message_type)
536  {
537  return _chain_db->is_known_block(id.item_hash);
538  }
539  else
540  {
541  return _chain_db->is_known_transaction(id.item_hash);
542  }
543  });
544  }
545  FC_CAPTURE_AND_RETHROW((id))
546  }
547 
556  virtual bool handle_block(const graphene::net::block_message& blk_msg,
557  bool sync_mode,
558  std::vector<fc::uint160_t>& contained_transaction_message_ids) override
559  {
560  try
561  {
562  if (_running)
563  {
564  uint32_t head_block_num;
565 
566  _chain_db->with_read_lock([&]() { head_block_num = _chain_db->head_block_num(); });
567 
568  if (sync_mode)
569  fc_ilog(fc::logger::get("sync"),
570  "chain pushing sync block #${block_num} ${block_hash}, head is ${head}",
571  ("block_num", blk_msg.block.block_num())("block_hash", blk_msg.block_id)("head",
572  head_block_num));
573  else
574  fc_ilog(fc::logger::get("sync"), "chain pushing block #${block_num} ${block_hash}, head is ${head}",
575  ("block_num", blk_msg.block.block_num())("block_hash", blk_msg.block_id)("head",
576  head_block_num));
577 
578  if (sync_mode && blk_msg.block.block_num() % 10000 == 0)
579  {
580  ilog("Syncing Blockchain --- Got block: #${n} time: ${t}",
581  ("t", blk_msg.block.timestamp)("n", blk_msg.block.block_num()));
582  }
583 
584  time_point_sec now = fc::time_point::now();
585 
586  uint64_t max_accept_time = now.sec_since_epoch();
587  max_accept_time += allow_future_time;
588  FC_ASSERT(blk_msg.block.timestamp.sec_since_epoch() <= max_accept_time);
589 
590  try
591  {
592  // TODO: in the case where this block is valid but on a fork that's too old for us to switch to,
593  // you can help the network code out by throwing a block_older_than_undo_history exception.
594  // when the net code sees that, it will stop trying to push blocks from that chain, but
595  // leave that peer connected so that they can get sync blocks from us
596  uint32_t skip_flags = (_is_block_producer | _force_validate)
599  if (sync_mode)
600  {
602  }
603  bool result = _chain_db->push_block(blk_msg.block, skip_flags);
604 
605  if (!sync_mode)
606  {
607  fc::microseconds latency = fc::time_point::now() - blk_msg.block.timestamp;
608  ilog("Got ${t} transactions on block ${b} by ${w} -- latency: ${l} ms",
609  ("t", blk_msg.block.transactions.size())("b", blk_msg.block.block_num())(
610  "w", blk_msg.block.witness)("l", latency.count() / 1000));
611  }
612 
613  return result;
614  }
615  catch (const scorum::chain::unlinkable_block_exception& e)
616  {
617  // translate to a graphene::net exception
618  fc_elog(fc::logger::get("sync"), "Error when pushing block, current head block is ${head3}:\n${e}",
619  ("e", e.to_detail_string())("head", head_block_num));
620  elog("Error when pushing block:\n${e}", ("e", e.to_detail_string()));
621  FC_THROW_EXCEPTION(graphene::net::unlinkable_block_exception, "Error when pushing block:\n${e}",
622  ("e", e.to_detail_string()));
623  }
624  catch (const fc::exception& e)
625  {
626  fc_elog(fc::logger::get("sync"), "Error when pushing block, current head block is ${head}:\n${e}",
627  ("e", e.to_detail_string())("head", head_block_num));
628  elog("Error when pushing block:\n${e}", ("e", e.to_detail_string()));
629  throw;
630  }
631  }
632  return false;
633  }
634  FC_CAPTURE_AND_RETHROW((blk_msg)(sync_mode))
635  }
636 
637  virtual void handle_transaction(const graphene::net::trx_message& transaction_message) override
638  {
639  try
640  {
641  if (_running)
642  {
643  _chain_db->push_transaction(transaction_message.trx);
644  }
645  }
646  FC_CAPTURE_AND_RETHROW((transaction_message))
647  }
648 
649  virtual void handle_message(const message& message_to_process) override
650  {
651  // not a transaction, not a block
652  FC_THROW("Invalid Message Type");
653  }
654 
655  bool is_included_block(const block_id_type& block_id)
656  {
657  return _chain_db->with_read_lock([&]() {
658  uint32_t block_num = block_header::num_from_id(block_id);
659  block_id_type block_id_in_preferred_chain = _chain_db->get_block_id_for_num(block_num);
660  return block_id == block_id_in_preferred_chain;
661  });
662  }
663 
673  virtual std::vector<item_hash_t> get_block_ids(const std::vector<item_hash_t>& blockchain_synopsis,
674  uint32_t& remaining_item_count,
675  uint32_t limit) override
676  {
677  try
678  {
679  return _chain_db->with_read_lock([&]() {
680  std::vector<block_id_type> result;
681  remaining_item_count = 0;
682  if (_chain_db->head_block_num() == 0)
683  {
684  return result;
685  }
686 
687  result.reserve(limit);
688  block_id_type last_known_block_id;
689 
690  if (blockchain_synopsis.empty()
691  || (blockchain_synopsis.size() == 1 && blockchain_synopsis[0] == block_id_type()))
692  {
693  // peer has sent us an empty synopsis meaning they have no blocks.
694  // A bug in old versions would cause them to send a synopsis containing block 000000000
695  // when they had an empty blockchain, so pretend they sent the right thing here.
696  // do nothing, leave last_known_block_id set to zero
697  }
698  else
699  {
700  bool found_a_block_in_synopsis = false;
701 
702  for (const item_hash_t& block_id_in_synopsis : boost::adaptors::reverse(blockchain_synopsis))
703  {
704  if (block_id_in_synopsis == block_id_type() || (_chain_db->is_known_block(block_id_in_synopsis)
705  && is_included_block(block_id_in_synopsis)))
706  {
707  last_known_block_id = block_id_in_synopsis;
708  found_a_block_in_synopsis = true;
709  break;
710  }
711  }
712 
713  if (!found_a_block_in_synopsis)
714  FC_THROW_EXCEPTION(
715  graphene::net::peer_is_on_an_unreachable_fork,
716  "Unable to provide a list of blocks starting at any of the blocks in peer's synopsis");
717  }
718 
719  for (uint32_t num = block_header::num_from_id(last_known_block_id);
720  num <= _chain_db->head_block_num() && result.size() < limit; ++num)
721  {
722  if (num > 0)
723  {
724  result.push_back(_chain_db->get_block_id_for_num(num));
725  }
726  }
727 
728  if (!result.empty() && block_header::num_from_id(result.back()) < _chain_db->head_block_num())
729  {
730  remaining_item_count = _chain_db->head_block_num() - block_header::num_from_id(result.back());
731  }
732 
733  return result;
734  });
735  }
736  FC_CAPTURE_AND_RETHROW((blockchain_synopsis)(remaining_item_count)(limit))
737  }
738 
742  virtual message get_item(const item_id& id) override
743  {
744  try
745  {
746  // ilog("Request for item ${id}", ("id", id));
747  if (id.item_type == graphene::net::block_message_type)
748  {
749  return _chain_db->with_read_lock([&]() {
750  auto opt_block = _chain_db->fetch_block_by_id(id.item_hash);
751  if (!opt_block)
752  elog("Couldn't find block ${id} -- corresponding ID in our chain is ${id2}",
753  ("id", id.item_hash)(
754  "id2", _chain_db->get_block_id_for_num(block_header::num_from_id(id.item_hash))));
755  FC_ASSERT(opt_block.valid());
756  // ilog("Serving up block #${num}", ("num", opt_block->block_num()));
757  return block_message(std::move(*opt_block));
758  });
759  }
760  return _chain_db->with_read_lock(
761  [&]() { return trx_message(_chain_db->get_recent_transaction(id.item_hash)); });
762  }
763  FC_CAPTURE_AND_RETHROW((id))
764  }
765 
766  virtual chain_id_type get_chain_id() const override
767  {
768  return _chain_db->get_chain_id();
769  }
770 
829  virtual std::vector<item_hash_t> get_blockchain_synopsis(const item_hash_t& reference_point,
830  uint32_t number_of_blocks_after_reference_point) override
831  {
832  try
833  {
834  std::vector<item_hash_t> synopsis;
835  _chain_db->with_read_lock([&]() {
836  synopsis.reserve(30);
837  uint32_t high_block_num;
838  uint32_t non_fork_high_block_num;
839  uint32_t low_block_num = _chain_db->last_non_undoable_block_num();
840  std::vector<block_id_type> fork_history;
841 
842  if (reference_point != item_hash_t())
843  {
844  // the node is asking for a summary of the block chain up to a specified
845  // block, which may or may not be on a fork
846  // for now, assume it's not on a fork
847  if (is_included_block(reference_point))
848  {
849  // reference_point is a block we know about and is on the main chain
850  uint32_t reference_point_block_num = block_header::num_from_id(reference_point);
851  assert(reference_point_block_num > 0);
852  high_block_num = reference_point_block_num;
853  non_fork_high_block_num = high_block_num;
854 
855  if (reference_point_block_num < low_block_num)
856  {
857  // we're on the same fork (at least as far as reference_point) but we've passed
858  // reference point and could no longer undo that far if we diverged after that
859  // block. This should probably only happen due to a race condition where
860  // the network thread calls this function, and then immediately pushes a bunch of blocks,
861  // then the main thread finally processes this function.
862  // with the current framework, there's not much we can do to tell the network
863  // thread what our current head block is, so we'll just pretend that
864  // our head is actually the reference point.
865  // this *may* enable us to fetch blocks that we're unable to push, but that should
866  // be a rare case (and correctly handled)
867  low_block_num = reference_point_block_num;
868  }
869  }
870  else
871  {
872  // block is a block we know about, but it is on a fork
873  try
874  {
875  fork_history = _chain_db->get_block_ids_on_fork(reference_point);
876  // returns a vector where the last element is the common ancestor with the preferred chain,
877  // and the first element is the reference point you passed in
878  assert(fork_history.size() >= 2);
879 
880  if (fork_history.front() != reference_point)
881  {
882  edump((fork_history)(reference_point));
883  assert(fork_history.front() == reference_point);
884  }
885  block_id_type last_non_fork_block = fork_history.back();
886  fork_history.pop_back(); // remove the common ancestor
887  boost::reverse(fork_history);
888 
889  if (last_non_fork_block == block_id_type()) // if the fork goes all the way back to genesis
890  // (does graphene's fork db allow this?)
891  {
892  non_fork_high_block_num = 0;
893  }
894  else
895  {
896  non_fork_high_block_num = block_header::num_from_id(last_non_fork_block);
897  }
898 
899  high_block_num = non_fork_high_block_num + fork_history.size();
900  assert(high_block_num == block_header::num_from_id(fork_history.back()));
901  }
902  catch (const fc::exception& e)
903  {
904  // unable to get fork history for some reason. maybe not linked?
905  // we can't return a synopsis of its chain
906  elog("Unable to construct a blockchain synopsis for reference hash ${hash}: ${exception}",
907  ("hash", reference_point)("exception", e));
908  throw;
909  }
910  if (non_fork_high_block_num < low_block_num)
911  {
912  wlog("Unable to generate a usable synopsis because the peer we're generating it for forked "
913  "too long ago "
914  "(our chains diverge after block #${non_fork_high_block_num} but only undoable to "
915  "block #${low_block_num})",
916  ("low_block_num", low_block_num)("non_fork_high_block_num", non_fork_high_block_num));
917  FC_THROW_EXCEPTION(graphene::net::block_older_than_undo_history,
918  "Peer is are on a fork I'm unable to switch to");
919  }
920  }
921  }
922  else
923  {
924  // no reference point specified, summarize the whole block chain
925  high_block_num = _chain_db->head_block_num();
926  non_fork_high_block_num = high_block_num;
927  if (high_block_num == 0)
928  {
929  return; // we have no blocks
930  }
931  }
932 
933  if (low_block_num == 0)
934  {
935  low_block_num = 1;
936  }
937 
938  // at this point:
939  // low_block_num is the block before the first block we can undo,
940  // non_fork_high_block_num is the block before the fork (if the peer is on a fork, or otherwise it is
941  // the same as high_block_num)
942  // high_block_num is the block number of the reference block, or the end of the chain if no reference
943  // provided
944 
945  // true_high_block_num is the ending block number after the network code appends any item ids it
946  // knows about that we don't
947  uint32_t true_high_block_num = high_block_num + number_of_blocks_after_reference_point;
948  do
949  {
950  // for each block in the synopsis, figure out where to pull the block id from.
951  // if it's <= non_fork_high_block_num, we grab it from the main blockchain;
952  // if it's not, we pull it from the fork history
953  if (low_block_num <= non_fork_high_block_num)
954  {
955  synopsis.push_back(_chain_db->get_block_id_for_num(low_block_num));
956  }
957  else
958  {
959  synopsis.push_back(fork_history[low_block_num - non_fork_high_block_num - 1]);
960  }
961  low_block_num += (true_high_block_num - low_block_num + 2) / 2;
962  } while (low_block_num <= high_block_num);
963 
964  // idump((synopsis));
965  return;
966  });
967 
968  return synopsis;
969  }
970  FC_CAPTURE_AND_RETHROW()
971  }
972 
981  virtual void sync_status(uint32_t item_type, uint32_t item_count) override
982  {
983  // any status reports to GUI go here
984  }
985 
989  virtual void connection_count_changed(uint32_t c) override
990  {
991  // any status reports to GUI go here
992  }
993 
994  virtual uint32_t get_block_number(const item_hash_t& block_id) override
995  {
996  try
997  {
998  return block_header::num_from_id(block_id);
999  }
1000  FC_CAPTURE_AND_RETHROW((block_id))
1001  }
1002 
1007  virtual fc::time_point_sec get_block_time(const item_hash_t& block_id) override
1008  {
1009  try
1010  {
1011  return _chain_db->with_read_lock([&]() {
1012  auto opt_block = _chain_db->fetch_block_by_id(block_id);
1013  if (opt_block.valid())
1014  {
1015  return opt_block->timestamp;
1016  }
1017  return fc::time_point_sec::min();
1018  });
1019  }
1020  FC_CAPTURE_AND_RETHROW((block_id))
1021  }
1022 
1024  virtual fc::time_point_sec get_blockchain_now() override
1025  {
1026  return fc::time_point::now();
1027  }
1028 
1029  virtual item_hash_t get_head_block_id() const override
1030  {
1031  return _chain_db->with_read_lock([&]() { return _chain_db->head_block_id(); });
1032  }
1033 
1034  virtual uint32_t estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const override
1035  {
1036  return 0; // there are no forks in graphene
1037  }
1038 
1039  virtual void error_encountered(const std::string& message, const fc::oexception& error) override
1040  {
1041  // notify GUI or something cool
1042  }
1043 
1044  void get_max_block_age(int32_t& result)
1045  {
1046  result = _max_block_age;
1047  return;
1048  }
1049 
1050  void shutdown()
1051  {
1052  _running = false;
1053  fc::usleep(fc::seconds(1));
1054  if (_p2p_network)
1055  {
1056  _p2p_network->close();
1057  fc::usleep(fc::seconds(1)); // p2p node has some calls to the database, give it a second to shutdown before
1058  // invalidating the chain db pointer
1059  }
1060  if (_chain_db)
1061  {
1062  _chain_db->close();
1063  }
1064  }
1065 
1066  template <typename API> fc::api<API> create_write_node_api(const std::string& api_name)
1067  {
1068  FC_ASSERT(_self->_remote_endpoint, "Write node RPC not configured properly or not currently connected.");
1069  auto ws_ptr = _self->_client.connect(*_self->_remote_endpoint);
1070  auto apic = std::make_shared<fc::rpc::websocket_api_connection>(*ws_ptr);
1071  auto login = apic->get_remote_api<login_api>(1);
1072  FC_ASSERT(login->login("", ""));
1073  fc::api_ptr ret = login->get_api_by_name(api_name);
1074  FC_ASSERT(ret, "Can't get API '${a}'.", ("a", api_name));
1075  return ret->template as<API>();
1076  }
1077 
1079 
1080  fc::path _data_dir;
1081  fc::path _shared_dir;
1082  const bpo::variables_map* _options = nullptr;
1084 
1085  std::shared_ptr<scorum::chain::database> _chain_db;
1086  std::shared_ptr<graphene::net::node> _p2p_network;
1087  std::shared_ptr<fc::http::websocket_server> _websocket_server;
1088  std::shared_ptr<fc::http::websocket_tls_server> _websocket_tls_server;
1089 
1090  // These plugins have API that push block to DB.
1091  // It is not expected for read-only mode
1092  const plugin_names_type _plugins_locked_in_readonly_mode = { "witness", "debug_node" };
1093 
1096  flat_map<std::string, std::function<fc::api_ptr(const api_context&)>> _api_factories_by_name;
1097  std::vector<std::string> _public_apis;
1098  int32_t _max_block_age = -1;
1100 
1101  bool _running;
1102 
1103  uint32_t allow_future_time = 5;
1104 };
1105 }
1106 
1107 application::application()
1108  : application(std::make_shared<chain::database>(chain::database::opt_default))
1109 {
1110 }
1111 
1112 application::application(std::shared_ptr<chain::database> db)
1113  : my(new detail::application_impl(this, db))
1114 {
1115 }
1116 
1118 {
1119  if (my->_p2p_network)
1120  {
1121  my->_p2p_network->close();
1122  my->_p2p_network.reset();
1123  }
1124  if (my->_chain_db)
1125  {
1126  my->_chain_db->close();
1127  }
1128 }
1129 
1130 std::vector<std::string> application::get_default_apis() const
1131 {
1132  std::vector<std::string> result;
1133 
1134  result.push_back(API_DATABASE);
1135  result.push_back("login_api");
1136  result.push_back(API_CHAIN);
1137  result.push_back(ADVERTISING_API_NAME);
1138  result.push_back("account_by_key_api");
1139  result.push_back(API_ACCOUNT_HISTORY);
1140  result.push_back(API_BLOCKCHAIN_HISTORY);
1141  result.push_back(API_ACCOUNT_STATISTICS);
1142  result.push_back(API_BLOCKCHAIN_STATISTICS);
1143 
1144  return result;
1145 }
1146 
1147 std::vector<std::string> application::get_default_plugins() const
1148 {
1149  std::vector<std::string> result;
1150 
1152  result.push_back("account_by_key");
1155 
1156  return result;
1157 }
1158 
1159 void application::set_program_options(boost::program_options::options_description& command_line_options,
1160  boost::program_options::options_description& configuration_file_options) const
1161 {
1162  const auto default_apis = get_default_apis();
1163  const auto default_plugins = get_default_plugins();
1164 
1165  const std::string str_default_apis = boost::algorithm::join(default_apis, " ");
1166  const std::string str_default_plugins = boost::algorithm::join(default_plugins, " ");
1167 
1168  // clang-format off
1169  configuration_file_options.add_options()
1170  ("p2p-endpoint", bpo::value<std::string>(), "Endpoint for P2P node to listen on")
1171  ("p2p-max-connections", bpo::value<uint32_t>(), "Maxmimum number of incoming connections on P2P endpoint")
1172  ("seed-node,s", bpo::value<std::vector<std::string>>()->composing(), "P2P nodes to connect to on startup (may specify multiple times)")
1173  ("checkpoint,c", bpo::value<std::vector<std::string>>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.")
1174  ("data-dir,d", bpo::value<boost::filesystem::path>()->default_value("witness_node_data_dir"), "Directory containing databases, configuration file, etc.")
1175  ("shared-file-dir", bpo::value<boost::filesystem::path>(), "Location of the shared memory file. Defaults to data_dir/blockchain")
1176  ("shared-file-size", bpo::value<std::string>()->default_value("54G"), "Size of the shared memory file. Default: 54G")
1177  ("rpc-endpoint", bpo::value<std::string>()->implicit_value("127.0.0.1:8090"), "Endpoint for websocket RPC to listen on")
1178  ("rpc-tls-endpoint", bpo::value<std::string>()->implicit_value("127.0.0.1:8089"), "Endpoint for TLS websocket RPC to listen on")
1179  ("read-forward-rpc", bpo::value<std::string>(), "Endpoint to forward write API calls to for a read node")
1180  ("server-pem,p", bpo::value<std::string>()->implicit_value("server.pem"), "The TLS certificate file for this server")
1181  ("server-pem-password,P", bpo::value<std::string>()->implicit_value(""), "Password for this certificate")
1182  ("api-user", bpo::value< std::vector<std::string> >()->composing(), "API user specification, may be specified multiple times")
1183  ("public-api", bpo::value< std::vector<std::string> >()->composing()->default_value(default_apis, str_default_apis), "Set an API to be publicly available, may be specified multiple times")
1184  ("enable-plugin", bpo::value< std::vector<std::string> >()->composing()->default_value(default_plugins, str_default_plugins), "Plugin(s) to enable, may be specified multiple times")
1185  ("max-block-age", bpo::value< int32_t >()->default_value(200), "Maximum age of head block when broadcasting tx via API")
1186  ("flush", bpo::value< uint32_t >()->default_value(100000), "Flush shared memory file to disk this many blocks")
1187  ("genesis-json,g", bpo::value<boost::filesystem::path>(), "File to read genesis state from")
1188  ("replay-blockchain", "Rebuild object graph by replaying all blocks")
1189  ("replay-skip-witness-schedule-check", bpo::value<bool>()->default_value(true), "Skip witness schedule check wile block replaying")
1190  ("resync-blockchain", "Delete all blocks and re-sync with network from scratch")
1191  ("force-validate", "Force validation of all transactions")
1192  ("read-only", "Node will not connect to p2p network and can only read from the chain state")
1193  ("check-locks", "Check correctness of chainbase locking")
1194  ("disable-get-block", "Disable get_block API call");
1195 
1196  // clang-format on
1197 
1198  command_line_options.add(configuration_file_options);
1199  command_line_options.add_options()("version,v", "Print version number and exit.");
1200 
1201  command_line_options.add(_cli_options);
1202  configuration_file_options.add(_cfg_options);
1203 
1204  boost::program_options::options_description api_description;
1205  api_description.add(get_api_config().get_options_descriptions());
1206  api_description.add(get_api_config(API_DATABASE).get_options_descriptions());
1207  command_line_options.add(api_description);
1208  configuration_file_options.add(api_description);
1209 }
1210 
1211 const std::string application::print_config(const boost::program_options::variables_map& vm)
1212 {
1213  namespace po = boost::program_options;
1214 
1215  std::stringstream stream;
1216  for (po::variables_map::const_iterator it = vm.begin(); it != vm.end(); it++)
1217  {
1218  stream << "> " << it->first;
1219 
1220  if (((boost::any)it->second.value()).empty())
1221  {
1222  stream << "(empty)";
1223  }
1224  if (vm[it->first].defaulted() || it->second.defaulted())
1225  {
1226  stream << "(default)";
1227  }
1228  stream << "=";
1229 
1230  // check if param is secure
1231  if (it->first == "server-pem-password" || it->first == "private-key")
1232  {
1233  stream << "..." << std::endl;
1234  continue;
1235  }
1236 
1237  try
1238  {
1239  stream << vm[it->first].as<int32_t>() << std::endl;
1240  continue;
1241  }
1242  catch (const boost::bad_any_cast&)
1243  {
1244  }
1245 
1246  try
1247  {
1248  stream << vm[it->first].as<uint32_t>() << std::endl;
1249  continue;
1250  }
1251  catch (const boost::bad_any_cast&)
1252  {
1253  }
1254 
1255  try
1256  {
1257  stream << vm[it->first].as<bool>() << std::endl;
1258  continue;
1259  }
1260  catch (const boost::bad_any_cast&)
1261  {
1262  }
1263 
1264  try
1265  {
1266  stream << vm[it->first].as<double>() << std::endl;
1267  continue;
1268  }
1269  catch (const boost::bad_any_cast&)
1270  {
1271  }
1272 
1273  try
1274  {
1275  stream << vm[it->first].as<boost::filesystem::path>().string() << std::endl;
1276  continue;
1277  }
1278  catch (const boost::bad_any_cast&)
1279  {
1280  }
1281 
1282  try
1283  {
1284  std::string temp = vm[it->first].as<std::string>();
1285  if (temp.size())
1286  {
1287  stream << temp << std::endl;
1288  }
1289  else
1290  {
1291  stream << "true" << std::endl;
1292  }
1293  continue;
1294  }
1295  catch (const boost::bad_any_cast&)
1296  {
1297  }
1298 
1299  // Assumes that the only remainder is vector<string>
1300  try
1301  {
1302  auto vect = vm[it->first].as<std::vector<std::string>>();
1303  auto i = 0;
1304  for (auto oit = vect.begin(); oit != vect.end(); ++oit, ++i)
1305  {
1306  stream << "\r> " << it->first << "[" << i << "]=" << (*oit) << std::endl;
1307  }
1308  }
1309  catch (const boost::bad_any_cast&)
1310  {
1311  stream << "UnknownType(" << ((boost::any)it->second.value()).type().name() << ")" << std::endl;
1312  }
1313  }
1314 
1315  return stream.str();
1316 }
1317 
1318 void application::initialize(const boost::program_options::variables_map& options)
1319 {
1320  ilog("initializing node with config:\n${config}", ("config", print_config(options)));
1321 
1322  my->_options = &options;
1323 
1324  get_api_config().set_options(options);
1326 }
1327 
1329 {
1330  try
1331  {
1332  _read_only = my->_options->count("read-only");
1333  my->startup();
1334  }
1335  catch (const fc::exception& e)
1336  {
1337  elog("${e}", ("e", e.to_detail_string()));
1338  throw;
1339  }
1340  catch (...)
1341  {
1342  elog("unexpected exception");
1343  throw;
1344  }
1345 }
1346 
1347 std::shared_ptr<abstract_plugin> application::get_plugin(const std::string& name) const
1348 {
1349  try
1350  {
1351  return my->_plugins_enabled.at(name);
1352  }
1353  catch (...)
1354  {
1355  }
1356 
1357  return null_plugin;
1358 }
1359 
1360 graphene::net::node_ptr application::p2p_node()
1361 {
1362  return my->_p2p_network;
1363 }
1364 
1365 std::shared_ptr<chain::database> application::chain_database() const
1366 {
1367  return my->_chain_db;
1368 }
1369 
1370 void application::set_block_production(bool producing_blocks)
1371 {
1372  my->_is_block_producer = producing_blocks;
1373 }
1374 
1375 optional<api_access_info> application::get_api_access_info(const std::string& username) const
1376 {
1377  return my->get_api_access_info(username);
1378 }
1379 
1380 void application::set_api_access_info(const std::string& username, api_access_info&& permissions)
1381 {
1382  my->set_api_access_info(username, std::move(permissions));
1383 }
1384 
1385 void application::register_api_factory(const std::string& name, std::function<fc::api_ptr(const api_context&)> factory)
1386 {
1387  return my->register_api_factory(name, factory);
1388 }
1389 
1391 {
1392  return my->create_api_by_name(ctx);
1393 }
1394 
1395 void application::get_max_block_age(int32_t& result)
1396 {
1397  my->get_max_block_age(result);
1398 }
1399 
1400 fc::api<network_broadcast_api>& application::get_write_node_net_api()
1401 {
1402  if (_remote_net_api)
1403  return *_remote_net_api;
1404 
1405  _remote_net_api = my->create_write_node_api<network_broadcast_api>(BOOST_PP_STRINGIZE(network_broadcast_api));
1406  return *_remote_net_api;
1407 }
1408 
1410 {
1411  for (auto& entry : my->_plugins_enabled)
1412  {
1413  entry.second->plugin_shutdown();
1414  }
1415  return;
1416 }
1417 
1419 {
1420  my->shutdown();
1421 }
1422 
1423 void application::register_abstract_plugin(std::shared_ptr<abstract_plugin> plug)
1424 {
1425  using boost::program_options::options_description;
1426 
1427  options_description plugin_cli_options("Options for plugin " + plug->plugin_name()), plugin_cfg_options;
1428  plug->plugin_set_program_options(plugin_cli_options, plugin_cfg_options);
1429 
1430  if (!plugin_cli_options.options().empty())
1431  {
1432  _cli_options.add(plugin_cli_options);
1433  }
1434 
1435  if (!plugin_cfg_options.options().empty())
1436  {
1437  _cfg_options.add(plugin_cfg_options);
1438  }
1439 
1440  my->_plugins_available[plug->plugin_name()] = plug;
1441 }
1442 
1443 void application::enable_plugin(const std::string& name)
1444 {
1445  auto it = my->_plugins_available.find(name);
1446  if (it == my->_plugins_available.end())
1447  {
1448  elog("can't enable plugin ${name}", ("name", name));
1449  }
1450  FC_ASSERT(it != my->_plugins_available.end());
1451  my->_plugins_enabled[name] = it->second;
1452 }
1453 
1454 void application::initialize_plugins(const boost::program_options::variables_map& options)
1455 {
1456  if (options.count("enable-plugin") > 0)
1457  {
1458  for (auto& arg : options.at("enable-plugin").as<std::vector<std::string>>())
1459  {
1460  std::vector<std::string> names;
1461  boost::split(names, arg, boost::is_any_of(" \t,"));
1462  for (const std::string& name : names)
1463  {
1464  // When no plugins are specified, the empty string is returned. Only enable non-empty plugin names
1465  if (name.size())
1466  {
1467  enable_plugin(name);
1468  }
1469  }
1470  }
1471  }
1472 
1473  bool read_only = options.count("read-only");
1474  for (auto& entry : my->_plugins_enabled)
1475  {
1476  const auto& plugin_name = entry.first;
1477 
1478  if (read_only
1479  && my->_plugins_locked_in_readonly_mode.find(plugin_name) != my->_plugins_locked_in_readonly_mode.end())
1480  {
1481  ilog("Skip ${p} plugin initialization in read-only mode", ("p", plugin_name));
1482  continue;
1483  }
1484 
1485  ilog("Initializing plugin ${name}", ("name", plugin_name));
1486  entry.second->plugin_initialize(options);
1487  }
1488 }
1489 
1491 {
1492  for (auto& entry : my->_plugins_enabled)
1493  {
1494  entry.second->plugin_startup();
1495  }
1496  return;
1497 }
1498 
1500 {
1501  std::cout << "scorum_blockchain_version: " << fc::string(SCORUM_BLOCKCHAIN_VERSION) << "\n";
1502  std::cout << "scorum_git_revision: " << fc::string(graphene::utilities::git_revision_sha) << "\n";
1503  std::cout << "fc_git_revision: " << fc::string(fc::git_revision_sha) << "\n";
1504  std::cout << "embeded_chain_id " << egenesis::get_egenesis_chain_id().str() << "\n";
1505 }
1506 
1507 void print_program_options(std::ostream& stream, const boost::program_options::options_description& options)
1508 {
1509  for (const boost::shared_ptr<bpo::option_description> od : options.options())
1510  {
1511  if (!od->description().empty())
1512  stream << "# " << od->description() << "\n";
1513 
1514  boost::any store;
1515  if (!od->semantic()->apply_default(store))
1516  {
1517  stream << "# " << od->long_name() << " = \n";
1518  }
1519  else
1520  {
1521  auto example = od->format_parameter();
1522  if (example.empty())
1523  {
1524  // This is a boolean switch
1525  stream << od->long_name() << " = "
1526  << "false\n";
1527  }
1528  else
1529  {
1530  // The string is formatted "arg (=<interesting part>)"
1531  example.erase(0, 6);
1532  example.erase(example.length() - 1);
1533  stream << od->long_name() << " = " << example << "\n";
1534  }
1535  }
1536  stream << "\n";
1537  }
1538 }
1539 
1540 fc::path get_data_dir_path(const boost::program_options::variables_map& options)
1541 {
1542  namespace bfs = boost::filesystem;
1543 
1544  FC_ASSERT(options.count("data-dir"), "Default value for 'data-dir' should be set.");
1545 
1546  return bfs::absolute(options["data-dir"].as<bfs::path>());
1547 }
1548 
1549 fc::path get_config_file_path(const boost::program_options::variables_map& options)
1550 {
1551  namespace bfs = boost::filesystem;
1552 
1553  bfs::path config_ini_path = get_data_dir_path(options) / SCORUMD_CONFIG_FILE_NAME;
1554 
1555  if (options.count("config-file"))
1556  {
1557  config_ini_path = bfs::absolute(options["config-file"].as<bfs::path>());
1558  }
1559 
1560  return config_ini_path;
1561 }
1562 
1563 void create_config_file_if_not_exist(const fc::path& config_ini_path,
1564  const boost::program_options::options_description& cfg_options)
1565 {
1566  FC_ASSERT(config_ini_path.is_absolute(), "config-file path should be absolute");
1567 
1568  if (!fc::exists(config_ini_path))
1569  {
1570  ilog("Writing new config file at ${path}", ("path", config_ini_path));
1571  if (!fc::exists(config_ini_path.parent_path()))
1572  {
1573  fc::create_directories(config_ini_path.parent_path());
1574  }
1575 
1576  std::ofstream out_cfg(config_ini_path.preferred_string());
1577 
1578  print_program_options(out_cfg, cfg_options);
1579 
1580  out_cfg.close();
1581  }
1582 }
1583 
1584 } // namespace app
1585 } // namespace scorum
#define API_ACCOUNT_HISTORY
#define API_ACCOUNT_STATISTICS
#define ACCOUNT_STATISTICS_PLUGIN_NAME
#define ADVERTISING_API_NAME
#define SCORUMD_CONFIG_FILE_NAME
Definition: application.hpp:38
#define API_BETTING
Definition: betting_api.hpp:3
#define API_BLOCKCHAIN_HISTORY
#define BLOCKCHAIN_HISTORY_PLUGIN_NAME
#define BLOCKCHAIN_MONITORING_PLUGIN_NAME
#define API_BLOCKCHAIN_STATISTICS
#define API_CHAIN
Definition: chain_api.hpp:3
std::vector< std::string > get_default_apis() const
void set_block_production(bool producing_blocks)
void enable_plugin(const std::string &name)
std::shared_ptr< chain::database > chain_database() const
fc::optional< fc::api< network_broadcast_api > > _remote_net_api
void initialize(const boost::program_options::variables_map &options)
std::shared_ptr< abstract_plugin > get_plugin(const std::string &name) const
void set_program_options(boost::program_options::options_description &command_line_options, boost::program_options::options_description &configuration_file_options) const
graphene::net::node_ptr p2p_node()
void register_abstract_plugin(std::shared_ptr< abstract_plugin > plug)
fc::api< network_broadcast_api > & get_write_node_net_api()
std::vector< std::string > get_default_plugins() const
fc::optional< api_access_info > get_api_access_info(const std::string &username) const
void register_api_factory(const std::string &name, std::function< fc::api_ptr(const api_context &)> factory)
fc::api_ptr create_api_by_name(const api_context &ctx)
void get_max_block_age(int32_t &result)
void set_api_access_info(const std::string &username, api_access_info &&permissions)
fc::optional< std::string > _remote_endpoint
void initialize_plugins(const boost::program_options::variables_map &options)
The chain_api class shows blockchain entrails.
Definition: chain_api.hpp:30
virtual void error_encountered(const std::string &message, const fc::oexception &error) override
virtual uint32_t estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const override
virtual void handle_message(const message &message_to_process) override
application_impl(application *self, std::shared_ptr< chain::database > chain_db)
virtual message get_item(const item_id &id) override
std::shared_ptr< fc::http::websocket_server > _websocket_server
fc::optional< fc::temp_file > _lock_file
flat_map< std::string, std::function< fc::api_ptr(const api_context &)> > _api_factories_by_name
void compute_genesis_state(scorum::chain::genesis_state_type &genesis_state)
virtual uint32_t get_block_number(const item_hash_t &block_id) override
virtual void sync_status(uint32_t item_type, uint32_t item_count) override
virtual std::vector< item_hash_t > get_blockchain_synopsis(const item_hash_t &reference_point, uint32_t number_of_blocks_after_reference_point) override
void on_connection(const fc::http::websocket_connection_ptr &c)
std::shared_ptr< fc::http::websocket_tls_server > _websocket_tls_server
virtual fc::time_point_sec get_blockchain_now() override
std::vector< std::string > _public_apis
void get_max_block_age(int32_t &result)
virtual chain_id_type get_chain_id() const override
fc::api< API > create_write_node_api(const std::string &api_name)
optional< api_access_info > get_api_access_info(const std::string &username) const
fc::api_ptr create_api_by_name(const api_context &ctx)
bool is_included_block(const block_id_type &block_id)
void set_api_access_info(const std::string &username, api_access_info &&permissions)
virtual bool handle_block(const graphene::net::block_message &blk_msg, bool sync_mode, std::vector< fc::uint160_t > &contained_transaction_message_ids) override
allows the application to validate an item prior to broadcasting to peers.
void register_api_factory(const std::string &name, std::function< fc::api_ptr(const api_context &)> factory)
virtual void connection_count_changed(uint32_t c) override
const bpo::variables_map * _options
virtual void handle_transaction(const graphene::net::trx_message &transaction_message) override
std::shared_ptr< scorum::chain::database > _chain_db
virtual item_hash_t get_head_block_id() const override
std::vector< fc::ip::endpoint > resolve_string_to_ip_endpoints(const std::string &endpoint_string)
void reset_p2p_node(const fc::path &data_dir)
virtual std::vector< item_hash_t > get_block_ids(const std::vector< item_hash_t > &blockchain_synopsis, uint32_t &remaining_item_count, uint32_t limit) override
virtual fc::time_point_sec get_block_time(const item_hash_t &block_id) override
virtual bool has_item(const graphene::net::item_id &id) override
std::shared_ptr< graphene::net::node > _p2p_network
The login_api class implements the bottom layer of the RPC API.
Definition: api.hpp:202
The network_broadcast_api class allows broadcasting of transactions.
Definition: api.hpp:56
The network_node_api class allows maintenance of p2p connections.
Definition: api.hpp:134
tracks the blockchain state in an extensible manner
Definition: database.hpp:52
@ skip_validate_invariants
used to skip database invariant check on block application
Definition: database.hpp:90
@ skip_transaction_signatures
used by non-witness nodes
Definition: database.hpp:80
void set_options(const boost::program_options::variables_map &options)
Definition: config_api.cpp:62
boost::program_options::options_description get_options_descriptions() const
Definition: config_api.cpp:48
#define SCORUM_BLOCKCHAIN_VERSION
Definition: config.hpp:91
#define API_DATABASE
Definition: database_api.hpp:3
std::map< std::string, std::shared_ptr< abstract_plugin > > plugins_type
std::set< std::string > plugin_names_type
void print_program_options(std::ostream &stream, const boost::program_options::options_description &options)
void print_application_version()
fc::path get_data_dir_path(const boost::program_options::variables_map &options)
void create_config_file_if_not_exist(const fc::path &config_ini_path, const boost::program_options::options_description &cfg_options)
fc::path get_config_file_path(const boost::program_options::variables_map &options)
fc::sha256 chain_id_type
Definition: types.hpp:61
fc::ripemd160 block_id_type
Definition: types.hpp:63
Definition: asset.cpp:15
config_api & get_api_config(std::string api_name)
Definition: config_api.cpp:114
std::vector< std::string > allowed_apis
Definition: api_access.hpp:40
std::map< std::string, api_access_info > permission_map
Definition: api_access.hpp:45
api_context(application &_app, const std::string &_api_name, std::weak_ptr< api_session_data > _session)
Definition: application.cpp:96
static uint32_t num_from_id(const block_id_type &id)
Definition: block.cpp:14