BitShares-Core  5.0.0
BitShares blockchain implementation and command-line interface software
witness.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  */
25 
28 
30 
31 #include <fc/thread/thread.hpp>
32 #include <fc/io/fstream.hpp>
33 
34 #include <boost/filesystem/path.hpp>
35 
36 #include <iostream>
37 
38 using namespace graphene::witness_plugin;
39 using std::string;
40 using std::vector;
41 
42 namespace bpo = boost::program_options;
43 
45 {
46  ilog("\n"
47  "********************************\n"
48  "* *\n"
49  "* ------- NEW CHAIN ------ *\n"
50  "* - Welcome to BitShares! - *\n"
51  "* ------------------------ *\n"
52  "* *\n"
53  "********************************\n"
54  "\n");
55  if( db.get_slot_at_time( fc::time_point::now() ) > 200 )
56  {
57  wlog("Your genesis seems to have an old timestamp");
58  wlog("Please consider using the --genesis-timestamp option to give your genesis a recent timestamp");
59  }
60 }
61 
62 void witness_plugin::plugin_set_program_options(
63  boost::program_options::options_description& command_line_options,
64  boost::program_options::options_description& config_file_options)
65 {
66  auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan")));
67  string witness_id_example = fc::json::to_string(chain::witness_id_type(5));
68  command_line_options.add_options()
69  ("enable-stale-production", bpo::bool_switch()->notifier([this](bool e){_production_enabled = e;}),
70  "Enable block production, even if the chain is stale.")
71  ("required-participation", bpo::value<uint32_t>()->default_value(33),
72  "Percent of witnesses (0-100) that must be participating in order to produce blocks")
73  ("witness-id,w", bpo::value<vector<string>>()->composing()->multitoken(),
74  ("ID of witness controlled by this node (e.g. " + witness_id_example +
75  ", quotes are required, may specify multiple times)").c_str())
76  ("private-key", bpo::value<vector<string>>()->composing()->multitoken()->
77  DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()),
78  graphene::utilities::key_to_wif(default_priv_key))),
79  "Tuple of [PublicKey, WIF private key] (may specify multiple times)")
80  ("private-key-file", bpo::value<vector<boost::filesystem::path>>()->composing()->multitoken(),
81  "Path to a file containing tuples of [PublicKey, WIF private key]."
82  " The file has to contain exactly one tuple (i.e. private - public key pair) per line."
83  " This option may be specified multiple times, thus multiple files can be provided.")
84  ;
85  config_file_options.add(command_line_options);
86 }
87 
88 std::string witness_plugin::plugin_name()const
89 {
90  return "witness";
91 }
92 
93 void witness_plugin::add_private_key(const std::string& key_id_to_wif_pair_string)
94 {
95  auto key_id_to_wif_pair = graphene::app::dejsonify<std::pair<chain::public_key_type, std::string>>
96  (key_id_to_wif_pair_string, 5);
97  fc::optional<fc::ecc::private_key> private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second);
98  if (!private_key)
99  {
100  // the key isn't in WIF format; see if they are still passing the old native private key format. This is
101  // just here to ease the transition, can be removed soon
102  try
103  {
104  private_key = fc::variant(key_id_to_wif_pair.second, 2).as<fc::ecc::private_key>(1);
105  }
106  catch (const fc::exception&)
107  {
108  FC_THROW("Invalid WIF-format private key ${key_string}", ("key_string", key_id_to_wif_pair.second));
109  }
110  }
111 
112  if (_private_keys.find(key_id_to_wif_pair.first) == _private_keys.end())
113  {
114  ilog("Public Key: ${public}", ("public", key_id_to_wif_pair.first));
115  _private_keys[key_id_to_wif_pair.first] = *private_key;
116  }
117 }
118 
119 void witness_plugin::plugin_initialize(const boost::program_options::variables_map& options)
120 { try {
121  ilog("witness plugin: plugin_initialize() begin");
122  _options = &options;
123  LOAD_VALUE_SET(options, "witness-id", _witnesses, chain::witness_id_type)
124 
125  if( options.count("private-key") )
126  {
127  const std::vector<std::string> key_id_to_wif_pair_strings = options["private-key"].as<std::vector<std::string>>();
128  for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings)
129  {
130  add_private_key(key_id_to_wif_pair_string);
131  }
132  }
133  if (options.count("private-key-file"))
134  {
135  const std::vector<boost::filesystem::path> key_id_to_wif_pair_files =
136  options["private-key-file"].as<std::vector<boost::filesystem::path>>();
137  for (const boost::filesystem::path& key_id_to_wif_pair_file : key_id_to_wif_pair_files)
138  {
139  if (fc::exists(key_id_to_wif_pair_file))
140  {
141  std::string file_content;
142  fc::read_file_contents(key_id_to_wif_pair_file, file_content);
143  std::istringstream file_content_as_stream(file_content);
144 
145  std::string line; // key_id_to_wif_pair_string
146  while (std::getline(file_content_as_stream, line))
147  {
148  add_private_key(line);
149  }
150  }
151  else
152  {
153  FC_THROW("Failed to load private key file from ${path}", ("path", key_id_to_wif_pair_file.string()));
154  }
155  }
156  }
157  if(options.count("required-participation"))
158  {
159  auto required_participation = options["required-participation"].as<uint32_t>();
160  FC_ASSERT(required_participation <= 100);
161  _required_witness_participation = options["required-participation"].as<uint32_t>()*GRAPHENE_1_PERCENT;
162  if(required_participation < 10)
163  wlog("witness plugin: Warning - Low required participation of ${rp}% found", ("rp", required_participation));
164  else if(required_participation > 90)
165  wlog("witness plugin: Warning - High required participation of ${rp}% found", ("rp", required_participation));
166  }
167  ilog("witness plugin: plugin_initialize() end");
168 } FC_LOG_AND_RETHROW() }
169 
171 { try {
172  ilog("witness plugin: plugin_startup() begin");
173  chain::database& d = database();
174  if( !_witnesses.empty() )
175  {
176  ilog("Launching block production for ${n} witnesses.", ("n", _witnesses.size()));
177  app().set_block_production(true);
178  if( _production_enabled )
179  {
180  if( d.head_block_num() == 0 )
181  new_chain_banner(d);
182  _production_skip_flags |= graphene::chain::database::skip_undo_history_check;
183  }
184  refresh_witness_key_cache();
185  d.applied_block.connect( [this]( const chain::signed_block& b )
186  {
187  refresh_witness_key_cache();
188  });
189  schedule_production_loop();
190  }
191  else
192  {
193  ilog("No witness configured.");
194  }
195  ilog("witness plugin: plugin_startup() end");
197 
199 {
201 }
202 
204 {
205  _shutting_down = true;
206 
207  try {
208  if( _block_production_task.valid() )
209  _block_production_task.cancel_and_wait(__FUNCTION__);
210  } catch(fc::canceled_exception&) {
211  //Expected exception. Move along.
212  } catch(fc::exception& e) {
213  edump((e.to_detail_string()));
214  }
215 }
216 
217 void witness_plugin::refresh_witness_key_cache()
218 {
219  const auto& db = database();
220  for( const chain::witness_id_type wit_id : _witnesses )
221  {
222  const chain::witness_object* wit_obj = db.find( wit_id );
223  if( wit_obj )
224  _witness_key_cache[wit_id] = wit_obj->signing_key;
225  else
226  _witness_key_cache[wit_id] = fc::optional<chain::public_key_type>();
227  }
228 }
229 
230 void witness_plugin::schedule_production_loop()
231 {
232  if (_shutting_down) return;
233 
234  //Schedule for the next second's tick regardless of chain state
235  // If we would wait less than 50ms, wait for the whole second.
237  int64_t time_to_next_second = 1000000 - (now.time_since_epoch().count() % 1000000);
238  if( time_to_next_second < 50000 ) // we must sleep for at least 50ms
239  time_to_next_second += 1000000;
240 
241  fc::time_point next_wakeup( now + fc::microseconds( time_to_next_second ) );
242 
243  _block_production_task = fc::schedule([this]{block_production_loop();},
244  next_wakeup, "Witness Block Production");
245 }
246 
247 block_production_condition::block_production_condition_enum witness_plugin::block_production_loop()
248 {
251 
252  if (_shutting_down)
253  {
255  }
256  else
257  {
258  try
259  {
260  result = maybe_produce_block(capture);
261  }
262  catch( const fc::canceled_exception& )
263  {
264  //We're trying to exit. Go ahead and let this one out.
265  throw;
266  }
267  catch( const fc::exception& e )
268  {
269  elog("Got exception while generating block:\n${e}", ("e", e.to_detail_string()));
271  }
272  }
273 
274  switch( result )
275  {
277  ilog("Generated block #${n} with ${x} transaction(s) and timestamp ${t} at time ${c}", (capture));
278  break;
280  ilog("Not producing block because production is disabled until we receive a recent block "
281  "(see: --enable-stale-production)");
282  break;
284  break;
286  break;
288  ilog("Not producing block because I don't have the private key for ${scheduled_key}", (capture) );
289  break;
291  elog("Not producing block because node appears to be on a minority fork with only ${pct}% witness participation",
292  (capture) );
293  break;
295  elog("Not producing block because node didn't wake up within 2500ms of the slot time.");
296  break;
298  elog( "exception producing block" );
299  break;
301  ilog( "shutdown producing block" );
302  return result;
303  default:
304  elog( "unknown condition ${result} while producing block", ("result", (unsigned char)result) );
305  break;
306  }
307 
308  schedule_production_loop();
309  return result;
310 }
311 
312 block_production_condition::block_production_condition_enum witness_plugin::maybe_produce_block(
314 {
315  chain::database& db = database();
316  fc::time_point now_fine = fc::time_point::now();
317  fc::time_point_sec now = now_fine + fc::microseconds( 500000 );
318 
319  // If the next block production opportunity is in the present or future, we're synced.
320  if( !_production_enabled )
321  {
322  if( db.get_slot_time(1) >= now )
323  _production_enabled = true;
324  else
326  }
327 
328  // is anyone scheduled to produce now or one second in the future?
329  uint32_t slot = db.get_slot_at_time( now );
330  if( slot == 0 )
331  {
332  capture("next_time", db.get_slot_time(1));
334  }
335 
336  //
337  // this assert should not fail, because now <= db.head_block_time()
338  // should have resulted in slot == 0.
339  //
340  // if this assert triggers, there is a serious bug in get_slot_at_time()
341  // which would result in allowing a later block to have a timestamp
342  // less than or equal to the previous block
343  //
344  assert( now > db.head_block_time() );
345 
346  graphene::chain::witness_id_type scheduled_witness = db.get_scheduled_witness( slot );
347  // we must control the witness scheduled to produce the next block.
348  if( _witnesses.find( scheduled_witness ) == _witnesses.end() )
349  {
350  capture("scheduled_witness", scheduled_witness);
352  }
353 
354  fc::time_point_sec scheduled_time = db.get_slot_time( slot );
355  graphene::chain::public_key_type scheduled_key = *_witness_key_cache[scheduled_witness]; // should be valid
356  auto private_key_itr = _private_keys.find( scheduled_key );
357 
358  if( private_key_itr == _private_keys.end() )
359  {
360  capture("scheduled_key", scheduled_key);
362  }
363 
364  uint32_t prate = db.witness_participation_rate();
365  if( prate < _required_witness_participation )
366  {
367  capture("pct", uint32_t(100*uint64_t(prate) / GRAPHENE_1_PERCENT));
369  }
370 
371  if( llabs((scheduled_time - now).count()) > fc::milliseconds( 2500 ).count() )
372  {
373  capture("scheduled_time", scheduled_time)("now", now);
375  }
376 
377  auto block = db.generate_block(
378  scheduled_time,
379  scheduled_witness,
380  private_key_itr->second,
381  _production_skip_flags
382  );
383  capture("n", block.block_num())("t", block.timestamp)("c", now)("x", block.transactions.size());
384  fc::async( [this,block](){ p2p_node().broadcast(net::block_message(block)); } );
385 
387 }
static string to_string(const variant &v, output_formatting format=stringify_large_ints_and_doubles, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:638
bool exists(const path &p)
Definition: filesystem.cpp:209
bool valid() const
Definition: future.hpp:311
auto async(Functor &&f, const char *desc FC_TASK_NAME_DEFAULT_ARG, priority prio=priority()) -> fc::future< decltype(f())>
Definition: thread.hpp:227
T as(uint32_t max_depth) const
Definition: variant.hpp:336
fc::signal< void(const signed_block &)> applied_block
Definition: database.hpp:197
#define GRAPHENE_MAX_NESTED_OBJECTS
Definition: config.hpp:31
tracks the blockchain state in an extensible manner
Definition: database.hpp:70
#define elog(FORMAT,...)
Definition: logger.hpp:129
fc::optional< fc::ecc::private_key > wif_to_key(const std::string &wif_key)
microseconds milliseconds(int64_t s)
Definition: time.hpp:35
Used to generate a useful error report when an exception is thrown.At each level in the stack where t...
Definition: exception.hpp:56
std::string to_detail_string(log_level ll=log_level::all) const
Definition: exception.cpp:183
uint32_t head_block_num() const
Definition: db_getter.cpp:69
signed_block generate_block(const fc::time_point_sec when, witness_id_type witness_id, const fc::ecc::private_key &block_signing_private_key, uint32_t skip)
Definition: db_block.cpp:368
#define FC_THROW(...)
Definition: exception.hpp:366
virtual void plugin_shutdown() override
Cleanly shut down the plugin.
Definition: witness.cpp:198
void read_file_contents(const fc::path &filename, std::string &result)
Definition: fstream.cpp:107
#define wlog(FORMAT,...)
Definition: logger.hpp:123
fc::istream & getline(fc::istream &, std::string &, char delim= '\n')
Definition: iostream.cpp:78
static sha256 hash(const char *d, uint32_t dlen)
Definition: sha256.cpp:41
provides stack-based nullable value similar to boost::optional
Definition: optional.hpp:20
std::string key_to_wif(const fc::sha256 &private_secret)
uint32_t get_slot_at_time(fc::time_point_sec when) const
chain::database & database()
Definition: plugin.hpp:114
void set_block_production(bool producing_blocks)
#define edump(SEQ)
Definition: logger.hpp:182
int64_t count() const
Definition: time.hpp:28
void cancel_and_wait(const char *reason FC_CANCELATION_REASON_DEFAULT_ARG)
Definition: future.hpp:314
time_point_sec head_block_time() const
Definition: db_getter.cpp:64
#define ilog(FORMAT,...)
Definition: logger.hpp:117
#define LOAD_VALUE_SET(options, name, container, type)
Definition: plugin.hpp:140
#define FC_CAPTURE_AND_RETHROW(...)
Definition: exception.hpp:478
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
uint32_t witness_participation_rate() const
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object&#39;s.
Definition: variant.hpp:198
static private_key regenerate(const fc::sha256 &secret)
#define DEFAULT_VALUE_VECTOR(value)
Definition: plugin.hpp:139
const microseconds & time_since_epoch() const
Definition: time.hpp:54
virtual void broadcast(const message &item_to_broadcast)
Definition: node.cpp:4958
std::string plugin_name() const override
Definition: witness.cpp:88
application & app() const
Definition: plugin.hpp:115
virtual void plugin_startup() override
Begin normal runtime operations.
Definition: witness.cpp:170
virtual void plugin_initialize(const boost::program_options::variables_map &options) override
Perform early startup routines and register plugin indexes, callbacks, etc.
Definition: witness.cpp:119
static time_point now()
Definition: time.cpp:13
net::node & p2p_node()
Definition: plugin.hpp:117
an elliptic curve private key.
Definition: elliptic.hpp:89
#define FC_LOG_AND_RETHROW()
Definition: exception.hpp:394
auto schedule(Functor &&f, const fc::time_point &t, const char *desc FC_TASK_NAME_DEFAULT_ARG, priority prio=priority()) -> fc::future< decltype(f())>
Definition: thread.hpp:231
#define GRAPHENE_1_PERCENT
Definition: config.hpp:103
void new_chain_banner(const graphene::chain::database &db)
Definition: witness.cpp:44
witness_id_type get_scheduled_witness(uint32_t slot_num) const
Get the witness scheduled for block production in a slot.
fc::time_point_sec get_slot_time(uint32_t slot_num) const