text: /*
   * Copyright (c) 2015 Cryptonomex, Inc., and contributors.
   *
   * The MIT License
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  #include <btcm/witness/witness.hpp>
  
  #include <btcm/chain/database.hpp>
  #include <btcm/chain/base_objects.hpp>
  
  #include <graphene/utilities/key_conversion.hpp>
  
  #include <fc/smart_ref_impl.hpp>
  #include <fc/thread/thread.hpp>
  
  #include <iostream>
  
  using namespace btcm::witness_plugin;
  using std::string;
  using std::vector;
  
  namespace bpo = boost::program_options;
  
  void new_chain_banner( const btcm::chain::database& db )
  {
     std::cerr << "\n"
        "********************************\n"
        "*                              *\n"
        "*   ------- NEW CHAIN ------   *\n"
        "*   -   Welcome to BTCM!  -   *\n"
        "*   ------------------------   *\n"
        "*                              *\n"
        "********************************\n"
        "\n";
     if( db.get_slot_at_time( fc::time_point::now() ) > 200 )
     {
        std::cerr << "Your genesis seems to have an old timestamp\n"
           "Please consider using the --genesis-timestamp option to give your genesis a recent timestamp\n"
           "\n"
           ;
     }
  }
  
  void witness_plugin::plugin_set_program_options(
     boost::program_options::options_description& command_line_options,
     boost::program_options::options_description& config_file_options)
  {
     string witness_id_example = "initwitness";
     command_line_options.add_options()
           ("enable-stale-production", bpo::bool_switch()->notifier([this](bool e){_production_enabled = e;}), "Enable block production, even if the chain is stale.")
           ("required-participation", bpo::bool_switch()->notifier([this](int e){_required_witness_participation = uint32_t(e*BTCM_1_PERCENT);}), "Percent of witnesses (0-99) that must be participating in order to produce blocks")
           ("witness,w", bpo::value<vector<string>>()->composing()->multitoken(),
            ("name of witness controlled by this node (e.g. " + witness_id_example+" )" ).c_str())
           ("private-key", bpo::value<vector<string>>()->composing()->multitoken(), "WIF PRIVATE KEY to be used by one or more witnesses or miners" )
          ;
     config_file_options.add(command_line_options);
  }
  
  std::string witness_plugin::plugin_name()const
  {
     return "witness";
  }
  
  using std::vector;
  using std::pair;
  using std::string;
  
  void witness_plugin::plugin_initialize(const boost::program_options::variables_map& options)
  { try {
     ilog("witness plugin:  plugin_initialize() begin");
     
     _options = &options;
     LOAD_VALUE_SET(options, "witness", _witnesses, string)
  
     if( options.count("private-key") )
     {
        const std::vector<std::string> keys = options["private-key"].as<std::vector<std::string>>();
        for (const std::string& wif_key : keys )
        {
           fc::optional<fc::ecc::private_key> private_key = graphene::utilities::wif_to_key(wif_key);
           FC_ASSERT( private_key.valid(), "unable to parse private key" );
           _private_keys[private_key->get_public_key()] = *private_key;
        }
     }
  
  
  
     ilog("witness plugin:  plugin_initialize() end");
  } FC_LOG_AND_RETHROW() }
  
  void witness_plugin::plugin_startup()
  { try {
     ilog("witness plugin:  plugin_startup() begin");
     chain::database& d = database();
  
     if( !_witnesses.empty() )
     {
        ilog("Launching block production for ${n} witnesses.", ("n", _witnesses.size()));
        app().set_block_production(true);
        if( _production_enabled )
        {
           if( d.head_block_num() == 0 )
              new_chain_banner(d);
           _production_skip_flags |= btcm::chain::database::skip_undo_history_check;
        }
        schedule_production_loop();
     } else
        elog("No witnesses configured! Please add witness names and private keys to configuration.");
     ilog("witness plugin:  plugin_startup() end");
  } FC_CAPTURE_AND_RETHROW() }
  
  void witness_plugin::plugin_shutdown() { /* nothing to do */ }
  
  void witness_plugin::schedule_production_loop()
  {
     //Schedule for the next second's tick regardless of chain state
     // If we would wait less than 50ms, wait for the whole second.
     fc::time_point now = fc::time_point::now();
     int64_t time_to_next_second = 1000000 - (now.time_since_epoch().count() % 1000000);
     if( time_to_next_second < 50000 )      // we must sleep for at least 50ms
         time_to_next_second += 1000000;
  
     fc::time_point next_wakeup( now + fc::microseconds( time_to_next_second ) );
  
     _block_production_task = fc::schedule([this]{block_production_loop();},
                                           next_wakeup, "Witness Block Production");
  }
  
  block_production_condition::block_production_condition_enum witness_plugin::block_production_loop()
  {
     if( fc::time_point::now() < fc::time_point(BTCM_GENESIS_TIME) )
     {
        wlog( "waiting until genesis time to produce block: ${t}", ("t",BTCM_GENESIS_TIME) );
        schedule_production_loop();
        return block_production_condition::wait_for_genesis;
     }
  
     block_production_condition::block_production_condition_enum result;
     fc::limited_mutable_variant_object capture( GRAPHENE_MAX_NESTED_OBJECTS );
     try
     {
        result = maybe_produce_block(capture);
     }
     catch( const fc::canceled_exception& )
     {
        //We're trying to exit. Go ahead and let this one out.
        throw;
     }
     catch( const btcm::chain::unknown_hardfork_exception& e )
     {
        // Hit a hardfork that the current node know nothing about, stop production and inform user
        elog( "${e}\nNode may be out of date...", ("e", e.to_detail_string()) );
        throw;
     }
     catch( const fc::exception& e )
     {
        elog("Got exception while generating block:\n${e}", ("e", e.to_detail_string()));
        result = block_production_condition::exception_producing_block;
     }
  
     switch( result )
     {
        case block_production_condition::produced:
           ilog("Generated block #${n} with timestamp ${t} at time ${c} by ${w}", (capture));
           break;
        case block_production_condition::not_synced:
           break;
        case block_production_condition::not_my_turn:
           break;
        case block_production_condition::not_time_yet:
           break;
        case block_production_condition::no_private_key:
           ilog("Not producing block for ${scheduled_witness} because I don't have the private key for ${scheduled_key}", (capture) );
           break;
        case block_production_condition::low_participation:
           elog("Not producing block because node appears to be on a minority fork with only ${pct}% witness participation", (capture) );
           break;
        case block_production_condition::lag:
           elog("Not producing block because node didn't wake up within 500ms of the slot time.");
           break;
        case block_production_condition::consecutive:
           elog("Not producing block because the last block was generated by the same witness.\nThis node is probably disconnected from the network so block production has been disabled.\nDisable this check with --allow-consecutive option.");
           break;
        case block_production_condition::exception_producing_block:
           elog("Failure when producing block with no transactions");
           break;
        case block_production_condition::wait_for_genesis:
           break;
     }
  
     schedule_production_loop();
     return result;
  }
  
  block_production_condition::block_production_condition_enum witness_plugin::maybe_produce_block( fc::limited_mutable_variant_object& capture )
  {
     chain::database& db = database();
     fc::time_point_sec now = fc::time_point::now() + fc::microseconds( 500000 );
  
     // If the next block production opportunity is in the present or future, we're synced.
     if( !_production_enabled )
     {
        if( db.get_slot_time(1) >= now )
           _production_enabled = true;
        else
           return block_production_condition::not_synced;
     }
  
     // is anyone scheduled to produce now or one second in the future?
     uint32_t slot = db.get_slot_at_time( now );
     if( slot == 0 )
     {
        capture("next_time", db.get_slot_time(1));
        return block_production_condition::not_time_yet;
     }
  
     //
     // this assert should not fail, because now <= db.head_block_time()
     // should have resulted in slot == 0.
     //
     // if this assert triggers, there is a serious bug in get_slot_at_time()
     // which would result in allowing a later block to have a timestamp
     // less than or equal to the previous block
     //
     assert( now > db.head_block_time() );
  
     string scheduled_witness = db.get_scheduled_witness( slot );
     // we must control the witness scheduled to produce the next block.
     if( _witnesses.find( scheduled_witness ) == _witnesses.end() )
     {
        capture("scheduled_witness", scheduled_witness);
        return block_production_condition::not_my_turn;
     }
  
     const auto& witness_by_name = db.get_index_type< chain::witness_index >().indices().get< chain::by_name >();
     auto itr = witness_by_name.find( scheduled_witness );
  
     fc::time_point_sec scheduled_time = db.get_slot_time( slot );
     btcm::chain::public_key_type scheduled_key = itr->signing_key;
     auto private_key_itr = _private_keys.find( scheduled_key );
  
     if( private_key_itr == _private_keys.end() )
     {
        capture("scheduled_witness", scheduled_witness);
        capture("scheduled_key", scheduled_key);
        return block_production_condition::no_private_key;
     }
  
     uint32_t prate = db.witness_participation_rate();
     if( prate < _required_witness_participation )
     {
        capture("pct", uint32_t(100*uint64_t(prate) / BTCM_1_PERCENT));
        return block_production_condition::low_participation;
     }
  
     if( llabs((scheduled_time - now).count()) > fc::milliseconds( 500 ).count() )
     {
        capture("scheduled_time", scheduled_time)("now", now);
        return block_production_condition::lag;
     }
  
     int retry = 0;
     do
     {
        try
        {
        auto block = db.generate_block(
           scheduled_time,
           scheduled_witness,
           private_key_itr->second,
           _production_skip_flags
           );
           capture("n", block.block_num())("t", block.timestamp)("c", now)("w",scheduled_witness);
           fc::async( [this,block](){ p2p_node().broadcast(graphene::net::block_message(block)); } );
  
           return block_production_condition::produced;
        }
        catch( fc::exception& e )
        {
           elog( "${e}", ("e",e.to_detail_string()) );
           elog( "Clearing pending transactions and attempting again" );
           db.clear_pending();
           retry++;
        }
     } while( retry < 2 );
  
     return block_production_condition::exception_producing_block;
  }
  
  /**
   * Every time a block is produced, this method is called. This method will iterate through all
   * mining accounts specified by commandline and for which the private key is known. The first
   * account that isn't already scheduled in the mining queue is selected to mine for the
   * BLOCK_INTERVAL minus 1 second. If a POW is solved or a a new block comes in then the
   * worker will stop early.
   *
   * Work is farmed out to N threads in parallel based upon the value specified on the command line.
   *
   * The miner assumes that the next block will be produced on time and that network propagation
   * will take at least 1 second. This 1 second consists of the time it took to receive the block
   * and how long it will take to broadcast the work. In other words, we assume 0.5s broadcast times
   * and therefore do not even attempt work that cannot be delivered on time.
   */
  
  BTCM_DEFINE_PLUGIN( witness, btcm::witness_plugin::witness_plugin )
  