diff --git a/Dockerfile b/Dockerfile index 8a970e39..dc4caae4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN \ libreadline-dev \ libssl-dev \ libtool \ + libzmq3-dev \ locales \ ntp \ pkg-config \ diff --git a/README.md b/README.md index 8207bb29..941afa68 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ This is a quick introduction to get new developers and witnesses up to speed on The following dependencies were necessary for a clean install of Ubuntu 18.04 LTS: ``` - sudo apt-get install gcc-5 g++-5 cmake make libbz2-dev\ - libdb++-dev libdb-dev libssl-dev openssl libreadline-dev\ - autoconf libtool git + sudo apt-get install autoconf bash build-essential ca-certificates cmake \ + doxygen git graphviz libbz2-dev libcurl4-openssl-dev libncurses-dev \ + libreadline-dev libssl-dev libtool libzmq3-dev locales ntp pkg-config \ + wget ``` ## Build Boost 1.67.0 diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt index 931d4f45..47bb066c 100644 --- a/libraries/plugins/peerplays_sidechain/CMakeLists.txt +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -2,6 +2,9 @@ file(GLOB HEADERS "include/graphene/peerplays_sidechain/*.hpp") add_library( peerplays_sidechain peerplays_sidechain_plugin.cpp + sidechain_net_manager.cpp + sidechain_net_handler.cpp + sidechain_net_handler_bitcoin.cpp ) target_link_libraries( peerplays_sidechain graphene_chain graphene_app ) diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp index d32fb09d..45628223 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp @@ -1,10 +1,9 @@ #pragma once #include -#include -#include -#include +#include +#include namespace graphene { namespace peerplays_sidechain { using namespace chain; @@ -30,5 +29,5 @@ class peerplays_sidechain_plugin : public graphene::app::plugin std::unique_ptr my; }; -} } //graphene::peerplays_sidechain_plugin +} } //graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp new file mode 100644 index 00000000..af697c85 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace graphene { namespace peerplays_sidechain { + +class sidechain_net_handler { +public: + sidechain_net_handler(const boost::program_options::variables_map& options); + virtual ~sidechain_net_handler(); +protected: + virtual void handle_block( const std::string& block_hash ) = 0; + +private: + +}; + +} } // graphene::peerplays_sidechain + diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp new file mode 100644 index 00000000..df763377 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include +#include + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +class bitcoin_rpc_client { +public: + bitcoin_rpc_client( std::string _ip, uint32_t _rpc, std::string _user, std::string _password) ; + std::string receive_full_block( const std::string& block_hash ); + int32_t receive_confirmations_tx( const std::string& tx_hash ); + bool receive_mempool_entry_tx( const std::string& tx_hash ); + uint64_t receive_estimated_fee(); + void send_btc_tx( const std::string& tx_hex ); + bool connection_is_not_defined() const; + +private: + + fc::http::reply send_post_request( std::string body ); + + std::string ip; + uint32_t rpc_port; + std::string user; + std::string password; + + fc::http::header authorization; +}; + +// ============================================================================= + +class zmq_listener { +public: + zmq_listener( std::string _ip, uint32_t _zmq ); + bool connection_is_not_defined() const { return zmq_port == 0; } + + fc::signal block_received; +private: + void handle_zmq(); + std::vector receive_multipart(); + + std::string ip; + uint32_t zmq_port; + + zmq::context_t ctx; + zmq::socket_t socket; +}; + +// ============================================================================= + +class sidechain_net_handler_bitcoin : public sidechain_net_handler { +public: + sidechain_net_handler_bitcoin(const boost::program_options::variables_map& options); + virtual ~sidechain_net_handler_bitcoin(); + + void handle_block( const std::string& block_hash ); +private: + std::string ip; + uint32_t zmq_port; + uint32_t rpc_port; + std::string rpc_user; + std::string rpc_password; + + std::unique_ptr listener; + std::unique_ptr bitcoin_client; + graphene::chain::database* db; + +}; + +} } // graphene::peerplays_sidechain + diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp new file mode 100644 index 00000000..703fe370 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +#include + +namespace graphene { namespace peerplays_sidechain { + +enum networks { + bitcoin, + //ethereum +}; + +class sidechain_net_manager { +public: + sidechain_net_manager(); + virtual ~sidechain_net_manager(); + + bool create_handler(peerplays_sidechain::networks network, const boost::program_options::variables_map& options); +private: + + std::vector> net_handlers; + +}; + +} } // graphene::peerplays_sidechain + diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index 36d0b713..e528765d 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -1,11 +1,15 @@ #include +#include +#include + +namespace bpo = boost::program_options; + namespace graphene { namespace peerplays_sidechain { namespace detail { - class peerplays_sidechain_plugin_impl { public: @@ -16,8 +20,8 @@ class peerplays_sidechain_plugin_impl peerplays_sidechain_plugin& _self; - uint32_t parameter; - uint32_t optional_parameter; + peerplays_sidechain::sidechain_net_manager _net_manager; + }; peerplays_sidechain_plugin_impl::~peerplays_sidechain_plugin_impl() @@ -48,8 +52,11 @@ void peerplays_sidechain_plugin::plugin_set_program_options( ) { cli.add_options() - ("parameter", boost::program_options::value(), "Parameter") - ("optional-parameter", boost::program_options::value(), "Optional parameter") + ("bitcoin-node-ip", bpo::value()->implicit_value("127.0.0.1"), "IP address of Bitcoin node") + ("bitcoin-node-zmq-port", bpo::value()->implicit_value(28332), "ZMQ port of Bitcoin node") + ("bitcoin-node-rpc-port", bpo::value()->implicit_value(18332), "RPC port of Bitcoin node") + ("bitcoin-node-rpc-user", bpo::value(), "Bitcoin RPC user") + ("bitcoin-node-rpc-password", bpo::value(), "Bitcoin RPC password") ; cfg.add(cli); } @@ -58,11 +65,12 @@ void peerplays_sidechain_plugin::plugin_initialize(const boost::program_options: { ilog("peerplays sidechain plugin: plugin_initialize()"); - if (options.count("parameter")) { - my->parameter = options["optional-parameter"].as(); - } - if (options.count("optional-parameter")) { - my->optional_parameter = options["optional-parameter"].as(); + if( options.count( "bitcoin-node-ip" ) && options.count( "bitcoin-node-zmq-port" ) && options.count( "bitcoin-node-rpc-port" ) + && options.count( "bitcoin-node-rpc-user" ) && options.count( "bitcoin-node-rpc-password" ) ) + { + my->_net_manager.create_handler(networks::bitcoin, options); + } else { + wlog("Haven't set up bitcoin sidechain parameters"); } } @@ -71,4 +79,5 @@ void peerplays_sidechain_plugin::plugin_startup() ilog("peerplays sidechain plugin: plugin_startup()"); } -} } +} } // graphene::peerplays_sidechain + diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp new file mode 100644 index 00000000..19e7db91 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp @@ -0,0 +1,12 @@ +#include + +namespace graphene { namespace peerplays_sidechain { + +sidechain_net_handler::sidechain_net_handler(const boost::program_options::variables_map& options) { +} + +sidechain_net_handler::~sidechain_net_handler() { +} + +} } // graphene::peerplays_sidechain + diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp new file mode 100644 index 00000000..130fb544 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -0,0 +1,223 @@ +#include + +#include + +#include +#include +#include + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +// ============================================================================= + +bitcoin_rpc_client::bitcoin_rpc_client( std::string _ip, uint32_t _rpc, std::string _user, std::string _password ): + ip( _ip ), rpc_port( _rpc ), user( _user ), password( _password ) +{ + authorization.key = "Authorization"; + authorization.val = "Basic " + fc::base64_encode( user + ":" + password ); +} + +std::string bitcoin_rpc_client::receive_full_block( const std::string& block_hash ) +{ + fc::http::connection conn; + conn.connect_to( fc::ip::endpoint( fc::ip::address( ip ), rpc_port ) ); + + const auto url = "http://" + ip + ":" + std::to_string( rpc_port ) + "/rest/block/" + block_hash + ".json"; + + const auto reply = conn.request( "GET", url ); + if ( reply.status != 200 ) + return ""; + + ilog( "Receive Bitcoin block: ${hash}", ( "hash", block_hash ) ); + return std::string( reply.body.begin(), reply.body.end() ); +} + +int32_t bitcoin_rpc_client::receive_confirmations_tx( const std::string& tx_hash ) +{ + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"curltest\", \"method\": \"getrawtransaction\", \"params\": [") + + std::string("\"") + tx_hash + std::string("\"") + ", " + "true" + std::string("] }"); + + const auto reply = send_post_request( body ); + + if ( reply.status != 200 ) + return 0; + + const auto result = std::string( reply.body.begin(), reply.body.end() ); + + std::stringstream ss( result ); + boost::property_tree::ptree tx; + boost::property_tree::read_json( ss, tx ); + + if( tx.count( "result" ) ) { + if( tx.get_child( "result" ).count( "confirmations" ) ) { + return tx.get_child( "result" ).get_child( "confirmations" ).get_value(); + } + } + return 0; +} + +bool bitcoin_rpc_client::receive_mempool_entry_tx( const std::string& tx_hash ) +{ + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"curltest\", \"method\": \"getmempoolentry\", \"params\": [") + + std::string("\"") + tx_hash + std::string("\"") + std::string("] }"); + + const auto reply = send_post_request( body ); + + if ( reply.status != 200 ) + return false; + + return true; +} + +uint64_t bitcoin_rpc_client::receive_estimated_fee() +{ + static const auto confirmation_target_blocks = 6; + + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"estimated_feerate\", \"method\": \"estimatesmartfee\", \"params\": [") + + std::to_string(confirmation_target_blocks) + std::string("] }"); + + const auto reply = send_post_request( body ); + + if( reply.status != 200 ) + return 0; + + std::stringstream ss( std::string( reply.body.begin(), reply.body.end() ) ); + boost::property_tree::ptree json; + boost::property_tree::read_json( ss, json ); + + if( json.count( "result" ) ) + if ( json.get_child( "result" ).count( "feerate" ) ) { + auto feerate_str = json.get_child( "result" ).get_child( "feerate" ).get_value(); + feerate_str.erase( std::remove( feerate_str.begin(), feerate_str.end(), '.' ), feerate_str.end() ); + return std::stoll( feerate_str ); + } + return 0; +} + +void bitcoin_rpc_client::send_btc_tx( const std::string& tx_hex ) +{ + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"send_tx\", \"method\": \"sendrawtransaction\", \"params\": [") + + std::string("\"") + tx_hex + std::string("\"") + std::string("] }"); + + const auto reply = send_post_request( body ); + + if( reply.body.empty() ) + return; + + std::string reply_str( reply.body.begin(), reply.body.end() ); + + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json( ss, json ); + + if( reply.status == 200 ) { + idump(( tx_hex )); + return; + } else if( json.count( "error" ) && !json.get_child( "error" ).empty() ) { + const auto error_code = json.get_child( "error" ).get_child( "code" ).get_value(); + if( error_code == -27 ) // transaction already in block chain + return; + + wlog( "BTC tx is not sent! Reply: ${msg}", ("msg", reply_str) ); + } +} + +bool bitcoin_rpc_client::connection_is_not_defined() const +{ + return ip.empty() || rpc_port == 0 || user.empty() || password.empty(); +} + +fc::http::reply bitcoin_rpc_client::send_post_request( std::string body ) +{ + fc::http::connection conn; + conn.connect_to( fc::ip::endpoint( fc::ip::address( ip ), rpc_port ) ); + + const auto url = "http://" + ip + ":" + std::to_string( rpc_port ); + + return conn.request( "POST", url, body, fc::http::headers{authorization} ); +} + +// ============================================================================= + +zmq_listener::zmq_listener( std::string _ip, uint32_t _zmq ): ip( _ip ), zmq_port( _zmq ), ctx( 1 ), socket( ctx, ZMQ_SUB ) { + std::thread( &zmq_listener::handle_zmq, this ).detach(); +} + +std::vector zmq_listener::receive_multipart() { + std::vector msgs; + + int32_t more; + size_t more_size = sizeof( more ); + while ( true ) { + zmq::message_t msg; + socket.recv( &msg, 0 ); + socket.getsockopt( ZMQ_RCVMORE, &more, &more_size ); + + if ( !more ) + break; + msgs.push_back( std::move(msg) ); + } + + return msgs; +} + +void zmq_listener::handle_zmq() { + socket.setsockopt( ZMQ_SUBSCRIBE, "hashblock", 0 ); + socket.connect( "tcp://" + ip + ":" + std::to_string( zmq_port ) ); + + while ( true ) { + auto msg = receive_multipart(); + const auto header = std::string( static_cast( msg[0].data() ), msg[0].size() ); + const auto hash = boost::algorithm::hex( std::string( static_cast( msg[1].data() ), msg[1].size() ) ); + + block_received( hash ); + } +} + +// ============================================================================= + +sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(const boost::program_options::variables_map& options) : + sidechain_net_handler(options) { + ip = options.at("bitcoin-node-ip").as(); + zmq_port = options.at("bitcoin-node-zmq-port").as(); + rpc_port = options.at("bitcoin-node-rpc-port").as(); + rpc_user = options.at("bitcoin-node-rpc-user").as(); + rpc_password = options.at("bitcoin-node-rpc-password").as(); + + fc::http::connection conn; + try { + conn.connect_to( fc::ip::endpoint( fc::ip::address( ip ), rpc_port ) ); + } catch ( fc::exception e ) { + elog( "No BTC node running at ${ip} or wrong rpc port: ${port}", ("ip", ip) ("port", rpc_port) ); + FC_ASSERT( false ); + } + + listener = std::unique_ptr( new zmq_listener( ip, zmq_port ) ); + bitcoin_client = std::unique_ptr( new bitcoin_rpc_client( ip, rpc_port, rpc_user, rpc_password ) ); + //db = _db; + + listener->block_received.connect([this]( const std::string& block_hash ) { + std::thread( &sidechain_net_handler_bitcoin::handle_block, this, block_hash ).detach(); + } ); + + //db->send_btc_tx.connect([this]( const sidechain::bitcoin_transaction& trx ) { + // std::thread( &sidechain_net_handler_bitcoin::send_btc_tx, this, trx ).detach(); + //} ); +} + +sidechain_net_handler_bitcoin::~sidechain_net_handler_bitcoin() { +} + +void sidechain_net_handler_bitcoin::handle_block( const std::string& block_hash ) { + ilog("peerplays sidechain plugin: sidechain_net_handler_bitcoin::handle_block"); + ilog(" block_hash: ${block_hash}", ("block_hash", block_hash)); +} + +// ============================================================================= + +} } // graphene::peerplays_sidechain + diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp new file mode 100644 index 00000000..9c436d64 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp @@ -0,0 +1,35 @@ +#include + +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +sidechain_net_manager::sidechain_net_manager() { + ilog(__FUNCTION__); +} + +sidechain_net_manager::~sidechain_net_manager() { + ilog(__FUNCTION__); +} + +bool sidechain_net_manager::create_handler(peerplays_sidechain::networks network, const boost::program_options::variables_map& options) { + ilog(__FUNCTION__); + + bool ret_val = false; + + switch (network) { + case networks::bitcoin: { + std::unique_ptr h = std::unique_ptr(new sidechain_net_handler_bitcoin(options)); + net_handlers.push_back(std::move(h)); + ret_val = true; + } + default: + assert(false); + } + + return ret_val; +} + +} } // graphene::peerplays_sidechain +