Add Bitcoin network listener to a SON plugin

This commit is contained in:
Srdjan Obucina 2019-10-22 21:52:55 +02:00
parent e0242bcf86
commit da8b9f2639
11 changed files with 426 additions and 18 deletions

View file

@ -22,6 +22,7 @@ RUN \
libreadline-dev \
libssl-dev \
libtool \
libzmq3-dev \
locales \
ntp \
pkg-config \

View file

@ -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

View file

@ -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 )

View file

@ -1,10 +1,9 @@
#pragma once
#include <graphene/app/plugin.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/account_object.hpp>
#include <fc/thread/future.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/database.hpp>
namespace graphene { namespace peerplays_sidechain {
using namespace chain;
@ -30,5 +29,5 @@ class peerplays_sidechain_plugin : public graphene::app::plugin
std::unique_ptr<detail::peerplays_sidechain_plugin_impl> my;
};
} } //graphene::peerplays_sidechain_plugin
} } //graphene::peerplays_sidechain

View file

@ -0,0 +1,19 @@
#pragma once
#include <boost/program_options.hpp>
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

View file

@ -0,0 +1,77 @@
#pragma once
#include <graphene/peerplays_sidechain/sidechain_net_handler.hpp>
#include <string>
#include <zmq.hpp>
#include <fc/signals.hpp>
#include <fc/network/http/connection.hpp>
#include <graphene/chain/database.hpp>
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<void( const std::string& )> block_received;
private:
void handle_zmq();
std::vector<zmq::message_t> 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<zmq_listener> listener;
std::unique_ptr<bitcoin_rpc_client> bitcoin_client;
graphene::chain::database* db;
};
} } // graphene::peerplays_sidechain

View file

@ -0,0 +1,29 @@
#pragma once
#include <graphene/peerplays_sidechain/sidechain_net_handler.hpp>
#include <vector>
#include <boost/program_options.hpp>
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<std::unique_ptr<sidechain_net_handler>> net_handlers;
};
} } // graphene::peerplays_sidechain

View file

@ -1,11 +1,15 @@
#include <graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp>
#include <fc/log/logger.hpp>
#include <graphene/peerplays_sidechain/sidechain_net_manager.hpp>
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<uint32_t>(), "Parameter")
("optional-parameter", boost::program_options::value<uint32_t>(), "Optional parameter")
("bitcoin-node-ip", bpo::value<string>()->implicit_value("127.0.0.1"), "IP address of Bitcoin node")
("bitcoin-node-zmq-port", bpo::value<uint32_t>()->implicit_value(28332), "ZMQ port of Bitcoin node")
("bitcoin-node-rpc-port", bpo::value<uint32_t>()->implicit_value(18332), "RPC port of Bitcoin node")
("bitcoin-node-rpc-user", bpo::value<string>(), "Bitcoin RPC user")
("bitcoin-node-rpc-password", bpo::value<string>(), "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<uint32_t>();
}
if (options.count("optional-parameter")) {
my->optional_parameter = options["optional-parameter"].as<uint32_t>();
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

View file

@ -0,0 +1,12 @@
#include <graphene/peerplays_sidechain/sidechain_net_handler.hpp>
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

View file

@ -0,0 +1,223 @@
#include <graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp>
#include <thread>
#include <boost/algorithm/hex.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <fc/crypto/base64.hpp>
#include <fc/logger/log.hpp>
#include <fc/network/ip.hpp>
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<int64_t>();
}
}
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<std::string>();
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<int>();
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::message_t> zmq_listener::receive_multipart() {
std::vector<zmq::message_t> 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<char*>( msg[0].data() ), msg[0].size() );
const auto hash = boost::algorithm::hex( std::string( static_cast<char*>( 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<std::string>();
zmq_port = options.at("bitcoin-node-zmq-port").as<uint32_t>();
rpc_port = options.at("bitcoin-node-rpc-port").as<uint32_t>();
rpc_user = options.at("bitcoin-node-rpc-user").as<std::string>();
rpc_password = options.at("bitcoin-node-rpc-password").as<std::string>();
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<zmq_listener>( new zmq_listener( ip, zmq_port ) );
bitcoin_client = std::unique_ptr<bitcoin_rpc_client>( 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

View file

@ -0,0 +1,35 @@
#include <graphene/peerplays_sidechain/sidechain_net_manager.hpp>
#include <fc/log/logger.hpp>
#include <graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp>
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<sidechain_net_handler> h = std::unique_ptr<sidechain_net_handler>(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