diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index e0de1d05..90538087 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -343,6 +343,15 @@ struct get_impacted_account_visitor void operator()( const sidechain_address_delete_operation& op ){ _impacted.insert( op.sidechain_address_account ); } + void operator()( const bitcoin_transaction_send_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_transaction_sign_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_send_transaction_process_operation& op ){ + _impacted.insert( op.payer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 85d5a91b..9c068ba5 100755 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -122,6 +122,7 @@ add_library( graphene_chain son_wallet_withdraw_evaluator.cpp sidechain_address_evaluator.cpp + sidechain_transaction_evaluator.cpp ${HEADERS} ${PROTOCOL_HEADERS} diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 0be37c42..1d7b4370 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include @@ -86,6 +87,7 @@ #include #include #include +#include #include @@ -267,6 +269,9 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -316,6 +321,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); //Implementation object indexes add_index< primary_index >(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 1b0b5158..6cf7c7b0 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -330,6 +330,15 @@ struct get_impacted_account_visitor void operator()( const sidechain_address_delete_operation& op ) { _impacted.insert( op.sidechain_address_account ); } + void operator()( const bitcoin_transaction_send_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_transaction_sign_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_send_transaction_process_operation& op ) { + _impacted.insert( op.payer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp old mode 100644 new mode 100755 index 37eccf80..d633056f --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -50,6 +50,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -155,7 +156,10 @@ namespace graphene { namespace chain { son_wallet_withdraw_process_operation, sidechain_address_add_operation, sidechain_address_update_operation, - sidechain_address_delete_operation + sidechain_address_delete_operation, + bitcoin_transaction_send_operation, + bitcoin_transaction_sign_operation, + bitcoin_send_transaction_process_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp b/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp new file mode 100644 index 00000000..eb7e942d --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + + struct bitcoin_transaction_send_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + // TODO: BTC Transaction Structs go here + fc::flat_map> signatures; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct bitcoin_transaction_sign_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + proposal_id_type proposal_id; + std::vector signatures; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + + struct bitcoin_send_transaction_process_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + bitcoin_transaction_id_type bitcoin_transaction_id; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation, (fee)(payer)(signatures) ) + +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation, (fee)(payer)(proposal_id)(signatures) ) + +FC_REFLECT( graphene::chain::bitcoin_send_transaction_process_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::bitcoin_send_transaction_process_operation, (fee)(payer)(bitcoin_transaction_id) ) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index ac6ec067..1caf1f9c 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -151,6 +151,7 @@ namespace graphene { namespace chain { son_wallet_deposit_object_type, son_wallet_withdraw_object_type, sidechain_address_object_type, + bitcoin_transaction_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -218,6 +219,7 @@ namespace graphene { namespace chain { class son_wallet_deposit_object; class son_wallet_withdraw_object; class sidechain_address_object; + class bitcoin_transaction_object; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; @@ -250,6 +252,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, son_wallet_deposit_object_type, son_wallet_deposit_object> son_wallet_deposit_id_type; typedef object_id< protocol_ids, son_wallet_withdraw_object_type, son_wallet_withdraw_object> son_wallet_withdraw_id_type; typedef object_id< protocol_ids, sidechain_address_object_type, sidechain_address_object> sidechain_address_id_type; + typedef object_id< protocol_ids, bitcoin_transaction_object_type,bitcoin_transaction_object> bitcoin_transaction_id_type; // implementation types class global_property_object; @@ -440,6 +443,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (son_wallet_deposit_object_type) (son_wallet_withdraw_object_type) (sidechain_address_object_type) + (bitcoin_transaction_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -517,6 +521,7 @@ FC_REFLECT_TYPENAME( graphene::chain::son_wallet_id_type ) FC_REFLECT_TYPENAME( graphene::chain::son_wallet_deposit_id_type ) FC_REFLECT_TYPENAME( graphene::chain::son_wallet_withdraw_id_type ) FC_REFLECT_TYPENAME( graphene::chain::sidechain_address_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::bitcoin_transaction_id_type ) FC_REFLECT( graphene::chain::void_t, ) diff --git a/libraries/chain/include/graphene/chain/sidechain_transaction_evaluator.hpp b/libraries/chain/include/graphene/chain/sidechain_transaction_evaluator.hpp new file mode 100644 index 00000000..aac04698 --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_transaction_evaluator.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +class bitcoin_transaction_send_evaluator : public evaluator +{ +public: + typedef bitcoin_transaction_send_operation operation_type; + + void_result do_evaluate(const bitcoin_transaction_send_operation& o); + object_id_type do_apply(const bitcoin_transaction_send_operation& o); +}; + +class bitcoin_transaction_sign_evaluator : public evaluator +{ +public: + typedef bitcoin_transaction_sign_operation operation_type; + + void_result do_evaluate(const bitcoin_transaction_sign_operation& o); + object_id_type do_apply(const bitcoin_transaction_sign_operation& o); + void update_proposal( const bitcoin_transaction_sign_operation& o ); +}; + +class bitcoin_send_transaction_process_evaluator : public evaluator +{ +public: + typedef bitcoin_send_transaction_process_operation operation_type; + + void_result do_evaluate(const bitcoin_send_transaction_process_operation& o); + object_id_type do_apply(const bitcoin_send_transaction_process_operation& o); +}; + +} } // namespace graphene::chain \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp b/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp new file mode 100644 index 00000000..95417852 --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp @@ -0,0 +1,39 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class bitcoin_transaction_object + * @brief tracks state of bitcoin transaction. + * @ingroup object + */ + class bitcoin_transaction_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = bitcoin_transaction_object_type; + // Bitcoin structs go here. + bool processed = false; + fc::flat_map> signatures; + }; + + struct by_processed; + using bitcoin_transaction_multi_index_type = multi_index_container< + bitcoin_transaction_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + member + > + > + >; + using bitcoin_transaction_index = generic_index; +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::bitcoin_transaction_object, (graphene::db::object), + (processed)(signatures) ) diff --git a/libraries/chain/sidechain_transaction_evaluator.cpp b/libraries/chain/sidechain_transaction_evaluator.cpp new file mode 100755 index 00000000..7a684b79 --- /dev/null +++ b/libraries/chain/sidechain_transaction_evaluator.cpp @@ -0,0 +1,136 @@ +#include + +#include +#include +#include +#include +#include + +namespace graphene +{ +namespace chain +{ + +void_result bitcoin_transaction_send_evaluator::do_evaluate(const bitcoin_transaction_send_operation &op) +{ + try + { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == GRAPHENE_SON_ACCOUNT, "SON paying account must be set as payer." ); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +object_id_type bitcoin_transaction_send_evaluator::do_apply(const bitcoin_transaction_send_operation &op) +{ + try + { + const auto &new_bitcoin_transaction_object = db().create([&](bitcoin_transaction_object &obj) { + obj.processed = false; + obj.signatures = op.signatures; + }); + return new_bitcoin_transaction_object.id; + } + FC_CAPTURE_AND_RETHROW((op)) +} + +void_result bitcoin_transaction_sign_evaluator::do_evaluate(const bitcoin_transaction_sign_operation &op) +{ + try + { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + const auto &proposal_idx = db().get_index_type().indices().get(); + const auto &proposal_itr = proposal_idx.find(op.proposal_id); + FC_ASSERT(proposal_idx.end() != proposal_itr, "proposal not found"); + // Checks can this SON approve this proposal + auto can_this_son_approve_this_proposal = [&]() { + const auto &sidx = db().get_index_type().indices().get(); + auto son_obj = sidx.find(op.payer); + if (son_obj == sidx.end()) + { + return false; + } + // TODO: Check if the SON is included in the PW script. + return true; + }; + + FC_ASSERT(can_this_son_approve_this_proposal(), "Invalid approval received"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +object_id_type bitcoin_transaction_sign_evaluator::do_apply(const bitcoin_transaction_sign_operation &op) +{ + try + { + const auto &proposal = op.proposal_id(db()); + const auto &sidx = db().get_index_type().indices().get(); + auto son_obj = sidx.find(op.payer); + + db().modify(proposal, [&](proposal_object &po) { + auto bitcoin_transaction_send_op = po.proposed_transaction.operations[0].get(); + bitcoin_transaction_send_op.signatures[son_obj->id] = op.signatures; + po.proposed_transaction.operations[0] = bitcoin_transaction_send_op; + }); + + update_proposal(op); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +void bitcoin_transaction_sign_evaluator::update_proposal(const bitcoin_transaction_sign_operation &op) +{ + database &d = db(); + proposal_update_operation update_op; + + update_op.fee_paying_account = op.payer; + update_op.proposal = op.proposal_id; + update_op.active_approvals_to_add = {op.payer}; + + bool skip_fee_old = trx_state->skip_fee; + bool skip_fee_schedule_check_old = trx_state->skip_fee_schedule_check; + trx_state->skip_fee = true; + trx_state->skip_fee_schedule_check = true; + + d.apply_operation(*trx_state, update_op); + + trx_state->skip_fee = skip_fee_old; + trx_state->skip_fee_schedule_check = skip_fee_schedule_check_old; +} + +void_result bitcoin_send_transaction_process_evaluator::do_evaluate(const bitcoin_send_transaction_process_operation &op) +{ + try + { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == GRAPHENE_SON_ACCOUNT, "SON paying account must be set as payer." ); + const auto& btidx = db().get_index_type().indices().get(); + const auto btobj = btidx.find(op.bitcoin_transaction_id); + FC_ASSERT(btobj != btidx.end(), "Bitcoin Transaction Object not found"); + FC_ASSERT(btobj->processed == false, "Bitcoin Transaction already processed"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +object_id_type bitcoin_send_transaction_process_evaluator::do_apply(const bitcoin_send_transaction_process_operation &op) +{ + try + { + const auto &btidx = db().get_index_type().indices().get(); + auto btobj = btidx.find(op.bitcoin_transaction_id); + if (btobj != btidx.end()) + { + db().modify(*btobj, [&op](bitcoin_transaction_object &bto) { + bto.processed = true; + }); + } + return op.bitcoin_transaction_id; + } + FC_CAPTURE_AND_RETHROW((op)) +} + +} // namespace chain +} // namespace graphene 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 old mode 100644 new mode 100755 index fe042306..64fea1be --- 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 @@ -36,6 +36,8 @@ protected: virtual std::string transfer( const std::string& from, const std::string& to, const uint64_t amount ) = 0; virtual std::string sign_transaction( const std::string& transaction ) = 0; virtual std::string send_transaction( const std::string& transaction ) = 0; + //virtual std::string transfer_deposit_to_primary_wallet (const sidechain_event_data& sed) = 0; + //virtual std::string transfer_withdrawal_from_primary_wallet(const std::string& user_address, double sidechain_amount) = 0; private: 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 index 6128ced8..854bac96 100644 --- 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 @@ -28,8 +28,12 @@ public: void send_btc_tx( const std::string& tx_hex ); std::string add_multisig_address( const std::vector public_keys ); bool connection_is_not_defined() const; + std::string create_raw_transaction(const std::string& txid, const std::string& vout, const std::string& out_address, double transfer_amount); + std::string sign_raw_transaction_with_wallet(const std::string& tx_hash); + std::string sign_raw_transaction_with_privkey(const std::string& tx_hash, const std::string& private_key); void import_address( const std::string& address_or_script); std::vector list_unspent(); + std::vector list_unspent_by_address_and_amount(const std::string& address, double transfer_amount); std::string prepare_tx(const std::vector& ins, const fc::flat_map outs); private: @@ -80,6 +84,11 @@ public: std::string transfer( const std::string& from, const std::string& to, const uint64_t amount ); std::string sign_transaction( const std::string& transaction ); std::string send_transaction( const std::string& transaction ); + std::string sign_and_send_transaction_with_wallet ( const std::string& tx_json ); + std::string transfer_all_btc(const std::string& from_address, const std::string& to_address); + std::string transfer_deposit_to_primary_wallet (const sidechain_event_data& sed); + std::string transfer_withdrawal_from_primary_wallet(const std::string& user_address, double sidechain_amount); + private: std::string ip; @@ -87,6 +96,7 @@ private: uint32_t rpc_port; std::string rpc_user; std::string rpc_password; + std::map _private_keys; std::unique_ptr listener; std::unique_ptr bitcoin_client; diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index e5680d45..5af5e5fc 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -109,6 +109,9 @@ void peerplays_sidechain_plugin_impl::plugin_set_program_options( ("bitcoin-address", bpo::value()->default_value("2N911a7smwDzUGARg8s7Q1ViizFCw6gWcbR"), "Bitcoin address") ("bitcoin-public-key", bpo::value()->default_value("02d0f137e717fb3aab7aff99904001d49a0a636c5e1342f8927a4ba2eaee8e9772"), "Bitcoin public key") ("bitcoin-private-key", bpo::value()->default_value("cVN31uC9sTEr392DLVUEjrtMgLA8Yb3fpYmTRj7bomTm6nn2ANPr"), "Bitcoin private key") + ("bitcoin-private-keys", bpo::value>()->composing()->multitoken()-> + DEFAULT_VALUE_VECTOR(std::make_pair("02d0f137e717fb3aab7aff99904001d49a0a636c5e1342f8927a4ba2eaee8e9772", "cVN31uC9sTEr392DLVUEjrtMgLA8Yb3fpYmTRj7bomTm6nn2ANPr")), + "Tuple of [Bitcoin PublicKey, Bitcoin Private key] (may specify multiple times)") ; cfg.add(cli); } diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp old mode 100644 new mode 100755 diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp old mode 100644 new mode 100755 index 9dd5ab39..dd644f3d --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -112,6 +112,8 @@ 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("] }"); + ilog(body); + const auto reply = send_post_request( body ); if( reply.body.empty() ) @@ -170,6 +172,72 @@ std::string bitcoin_rpc_client::add_multisig_address( const std::vector bitcoin_rpc_client::list_unspent() return result; } +std::vector bitcoin_rpc_client::list_unspent_by_address_and_amount(const std::string& address, double minimum_amount) +{ + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": \"listunspent\", \"params\": ["); + body += std::string("1,999999,[\""); + body += address; + body += std::string("\"],true,{\"minimumAmount\":"); + body += std::to_string(minimum_amount); + body += std::string("}] }"); + + ilog(body); + + const auto reply = send_post_request( body ); + + std::vector result; + if( reply.body.empty() ) + { + wlog("Failed to list unspent txo"); + return result; + } + + 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((reply_str)); + if( json.count( "result" ) ) + { + for(auto& entry: json.get_child("result")) + { + btc_txout txo; + txo.txid_ = entry.second.get_child("txid").get_value(); + txo.out_num_ = entry.second.get_child("vout").get_value(); + txo.amount_ = entry.second.get_child("amount").get_value(); + result.push_back(txo); + } + } + } else if( json.count( "error" ) && !json.get_child( "error" ).empty() ) { + wlog( "Failed to list unspent txo! Reply: ${msg}", ("msg", reply_str) ); + } + return result; +} + std::string bitcoin_rpc_client::prepare_tx(const std::vector &ins, const fc::flat_map outs) { std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": \"createrawtransaction\", \"params\": ["); @@ -249,21 +362,21 @@ std::string bitcoin_rpc_client::prepare_tx(const std::vector &ins, co { if(!first) body += ","; - body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":\"" + fc::to_string(entry.out_num_) + "\"}"; + body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":" + std::to_string(entry.out_num_) + "}"; first = false; } - body += "]"; + body += "],["; first = true; - body += "{"; for(const auto& entry: outs) { if(!first) body += ","; - body += "\"" + entry.first + "\":\"" + fc::to_string(entry.second) + "\""; + body += "{\"" + entry.first + "\":" + std::to_string(entry.second) + "}"; first = false; } - body += "}"; - body += std::string("] }"); + body += std::string("]] }"); + + ilog(body); const auto reply = send_post_request( body ); @@ -282,7 +395,7 @@ std::string bitcoin_rpc_client::prepare_tx(const std::vector &ins, co if( reply.status == 200 ) { idump((reply_str)); if( json.count( "result" ) ) - return json.get_child("result").get_value(); + return reply_str; } else if( json.count( "error" ) && !json.get_child( "error" ).empty() ) { wlog( "Failed to create raw transaction: [${body}]! Reply: ${msg}", ("body", body)("msg", reply_str) ); } @@ -351,6 +464,21 @@ sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain rpc_user = options.at("bitcoin-node-rpc-user").as(); rpc_password = options.at("bitcoin-node-rpc-password").as(); + if( options.count("bitcoin-private-keys") ) + { + const std::vector pub_priv_keys = options["bitcoin-private-keys"].as>(); + for (const std::string& itr_key_pair : pub_priv_keys) + { + auto key_pair = graphene::app::dejsonify >(itr_key_pair, 5); + ilog("Public Key: ${public}", ("public", key_pair.first)); + if(!key_pair.first.length() || !key_pair.second.length()) + { + FC_THROW("Invalid public private key pair."); + } + _private_keys[key_pair.first] = key_pair.second; + } + } + fc::http::connection conn; try { conn.connect_to( fc::ip::endpoint( fc::ip::address( ip ), rpc_port ) ); @@ -454,6 +582,170 @@ std::string sidechain_net_handler_bitcoin::send_transaction( const std::string& return ""; } +std::string sidechain_net_handler_bitcoin::sign_and_send_transaction_with_wallet ( const std::string& tx_json ) +{ + std::string reply_str = tx_json; + + ilog(reply_str); + + std::stringstream ss_utx(reply_str); + boost::property_tree::ptree pt; + boost::property_tree::read_json( ss_utx, pt ); + + if( !(pt.count( "error" ) && pt.get_child( "error" ).empty()) || !pt.count("result") ) { + return ""; + } + + std::string unsigned_tx_hex = pt.get("result"); + + reply_str = bitcoin_client->sign_raw_transaction_with_wallet(unsigned_tx_hex); + ilog(reply_str); + std::stringstream ss_stx(reply_str); + boost::property_tree::ptree stx_json; + boost::property_tree::read_json( ss_stx, stx_json ); + + if( !(stx_json.count( "error" ) && stx_json.get_child( "error" ).empty()) || !stx_json.count("result") || !stx_json.get_child("result").count("hex") ) { + return ""; + } + + std::string signed_tx_hex = stx_json.get("result.hex"); + + bitcoin_client->send_btc_tx(signed_tx_hex); + + return reply_str; +} + +std::string sidechain_net_handler_bitcoin::transfer_all_btc(const std::string& from_address, const std::string& to_address) +{ + uint64_t fee_rate = bitcoin_client->receive_estimated_fee(); + uint64_t min_fee_rate = 1000; + fee_rate = std::max(fee_rate, min_fee_rate); + + double min_amount = ((double)fee_rate/100000000.0); // Account only for relay fee for now + double total_amount = 0.0; + std::vector unspent_utxo= bitcoin_client->list_unspent_by_address_and_amount(from_address, 0); + + if(unspent_utxo.size() == 0) + { + wlog("Failed to find UTXOs to spend for ${pw}",("pw", from_address)); + return ""; + } + else + { + for(const auto& utx: unspent_utxo) + { + total_amount += utx.amount_; + } + + if(min_amount >= total_amount) + { + wlog("Failed not enough BTC to transfer from ${fa}",("fa", from_address)); + return ""; + } + } + + fc::flat_map outs; + outs[to_address] = total_amount - min_amount; + + std::string reply_str = bitcoin_client->prepare_tx(unspent_utxo, outs); + return sign_and_send_transaction_with_wallet(reply_str); +} + +std::string sidechain_net_handler_bitcoin::transfer_deposit_to_primary_wallet ( const sidechain_event_data& sed ) +{ + const auto& idx = database.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + return ""; + } + + std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; + + std::stringstream ss(pw_address_json); + boost::property_tree::ptree json; + boost::property_tree::read_json( ss, json ); + + std::string pw_address = json.get("address"); + + std::string txid = sed.sidechain_transaction_id; + std::string suid = sed.sidechain_uid; + std::string nvout = suid.substr(suid.find_last_of("-")+1); + uint64_t deposit_amount = sed.sidechain_amount.value; + uint64_t fee_rate = bitcoin_client->receive_estimated_fee(); + uint64_t min_fee_rate = 1000; + fee_rate = std::max(fee_rate, min_fee_rate); + deposit_amount -= fee_rate; // Deduct minimum relay fee + double transfer_amount = (double)deposit_amount/100000000.0; + + std::vector ins; + fc::flat_map outs; + + btc_txout utxo; + utxo.txid_ = txid; + utxo.out_num_ = std::stoul(nvout); + + ins.push_back(utxo); + + outs[pw_address] = transfer_amount; + + std::string reply_str = bitcoin_client->prepare_tx(ins, outs); + return sign_and_send_transaction_with_wallet(reply_str); +} + +std::string sidechain_net_handler_bitcoin::transfer_withdrawal_from_primary_wallet(const std::string& user_address, double sidechain_amount) { + const auto& idx = database.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) + { + return ""; + } + + std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; + + std::stringstream ss(pw_address_json); + boost::property_tree::ptree json; + boost::property_tree::read_json( ss, json ); + + std::string pw_address = json.get("address"); + + uint64_t fee_rate = bitcoin_client->receive_estimated_fee(); + uint64_t min_fee_rate = 1000; + fee_rate = std::max(fee_rate, min_fee_rate); + + double min_amount = sidechain_amount + ((double)fee_rate/100000000.0); // Account only for relay fee for now + double total_amount = 0.0; + std::vector unspent_utxo= bitcoin_client->list_unspent_by_address_and_amount(pw_address, 0); + + if(unspent_utxo.size() == 0) + { + wlog("Failed to find UTXOs to spend for ${pw}",("pw", pw_address)); + return ""; + } + else + { + for(const auto& utx: unspent_utxo) + { + total_amount += utx.amount_; + } + + if(min_amount > total_amount) + { + wlog("Failed not enough BTC to spend for ${pw}",("pw", pw_address)); + return ""; + } + } + + fc::flat_map outs; + outs[user_address] = sidechain_amount; + if((total_amount - min_amount) > 0.0) + { + outs[pw_address] = total_amount - min_amount; + } + + std::string reply_str = bitcoin_client->prepare_tx(unspent_utxo, outs); + return sign_and_send_transaction_with_wallet(reply_str); +} + void sidechain_net_handler_bitcoin::handle_event( const std::string& event_data ) { std::string block = bitcoin_client->receive_full_block( event_data ); if( block != "" ) { diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index 3c2c2577..706334eb 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include diff --git a/tests/tests/sidechain_transaction_tests.cpp b/tests/tests/sidechain_transaction_tests.cpp new file mode 100755 index 00000000..0246d009 --- /dev/null +++ b/tests/tests/sidechain_transaction_tests.cpp @@ -0,0 +1,380 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include +#include +#include +#include + +using namespace graphene; +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(sidechain_transaction_tests, database_fixture) + +BOOST_AUTO_TEST_CASE(bitcoin_transaction_send_test) +{ + + try + { + BOOST_TEST_MESSAGE("bitcoin_transaction_send_test"); + + generate_blocks(HARDFORK_SON_TIME); + generate_block(); + set_expiration(db, trx); + + ACTORS((alice)(bob)); + + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + + transfer(committee_account, alice_id, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, bob_id, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + + generate_block(); + set_expiration(db, trx); + + std::string test_url = "https://create_son_test"; + + // create deposit vesting + vesting_balance_id_type deposit_alice; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer{}; + trx.operations.push_back(op); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + deposit_alice = ptx.operation_results[0].get(); + } + generate_block(); + set_expiration(db, trx); + + // create payment normal vesting + vesting_balance_id_type payment_alice; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + trx.operations.push_back(op); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment_alice = ptx.operation_results[0].get(); + } + + generate_block(); + set_expiration(db, trx); + + // alice becomes son + { + flat_map sidechain_public_keys; + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin address"; + + son_create_operation op; + op.owner_account = alice_id; + op.url = test_url; + op.deposit = deposit_alice; + op.pay_vb = payment_alice; + op.signing_key = alice_public_key; + op.sidechain_public_keys = sidechain_public_keys; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + set_expiration(db, trx); + + // create deposit vesting + vesting_balance_id_type deposit_bob; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer{}; + trx.operations.push_back(op); + sign(trx, bob_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + deposit_bob = ptx.operation_results[0].get(); + } + generate_block(); + set_expiration(db, trx); + + // create payment normal vesting + vesting_balance_id_type payment_bob; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + trx.operations.push_back(op); + sign(trx, bob_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment_bob = ptx.operation_results[0].get(); + } + generate_block(); + set_expiration(db, trx); + + // bob becomes son + { + flat_map sidechain_public_keys; + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin address"; + + son_create_operation op; + op.owner_account = bob_id; + op.url = test_url; + op.deposit = deposit_bob; + op.pay_vb = payment_bob; + op.signing_key = bob_public_key; + op.sidechain_public_keys = sidechain_public_keys; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + generate_block(); + + const auto& acc_idx = db.get_index_type().indices().get(); + auto acc_itr = acc_idx.find(GRAPHENE_SON_ACCOUNT); + BOOST_REQUIRE(acc_itr != acc_idx.end()); + db.modify(*acc_itr, [&](account_object &obj) { + obj.active.account_auths.clear(); + obj.active.add_authority(bob_id, 1); + obj.active.add_authority(alice_id, 1); + obj.active.weight_threshold = 2; + obj.owner.account_auths.clear(); + obj.owner.add_authority(bob_id, 1); + obj.owner.add_authority(alice_id, 1); + obj.owner.weight_threshold = 2; + }); + + /*const auto &son_btc_account = db.create([&](account_object &obj) { + obj.name = "son_btc_account"; + obj.statistics = db.create([&](account_statistics_object &acc_stat) { acc_stat.owner = obj.id; }).id; + obj.membership_expiration_date = time_point_sec::maximum(); + obj.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + obj.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + + obj.owner.add_authority(bob_id, 1); + obj.active.add_authority(bob_id, 1); + obj.owner.add_authority(alice_id, 1); + obj.active.add_authority(alice_id, 1); + obj.active.weight_threshold = 2; + obj.owner.weight_threshold = 2; + }); + + db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) + { + _gpo.parameters.extensions.value.son_btc_account = son_btc_account.get_id(); + if( _gpo.pending_parameters ) + _gpo.pending_parameters->extensions.value.son_btc_account = son_btc_account.get_id(); + });*/ + + generate_block(); + + const global_property_object &gpo = db.get_global_properties(); + + { + BOOST_TEST_MESSAGE("Send bitcoin_transaction_send_operation"); + + bitcoin_transaction_send_operation send_op; + + send_op.payer = GRAPHENE_SON_ACCOUNT; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = alice_id; + proposal_op.proposed_ops.push_back(op_wrapper(send_op)); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(db.head_block_time().sec_since_epoch() + lifetime); + + trx.operations.push_back(proposal_op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check proposal results"); + + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + auto obj = idx.find(proposal_id_type(0)); + BOOST_REQUIRE(obj != idx.end()); + + const auto& btidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(btidx.size() == 0); + + std::vector a1 = {'a', 'l', 'i', 'c', 'e', '1'}; + std::vector a2 = {'a', 'l', 'i', 'c', 'e', '2'}; + std::vector a3 = {'a', 'l', 'i', 'c', 'e', '3'}; + + { + BOOST_TEST_MESSAGE("Send bitcoin_transaction_sign_operation"); + + bitcoin_transaction_sign_operation sign_op; + + sign_op.payer = alice_id; + sign_op.proposal_id = proposal_id_type(0); + sign_op.signatures.push_back(a1); + sign_op.signatures.push_back(a2); + sign_op.signatures.push_back(a3); + + trx.operations.push_back(sign_op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + + BOOST_REQUIRE(idx.size() == 1); + BOOST_REQUIRE(btidx.size() == 0); + auto pobj = idx.find(proposal_id_type(0)); + BOOST_REQUIRE(pobj != idx.end()); + + const auto& sidx = db.get_index_type().indices().get(); + const auto son_obj1 = sidx.find( alice_id ); + BOOST_REQUIRE(son_obj1 != sidx.end()); + + auto bitcoin_transaction_send_op = pobj->proposed_transaction.operations[0].get(); + BOOST_REQUIRE(bitcoin_transaction_send_op.signatures.size() == 1); + BOOST_REQUIRE(bitcoin_transaction_send_op.signatures[son_obj1->id][0] == a1); + BOOST_REQUIRE(bitcoin_transaction_send_op.signatures[son_obj1->id][1] == a2); + BOOST_REQUIRE(bitcoin_transaction_send_op.signatures[son_obj1->id][2] == a3); + + std::vector b1 = {'b', 'o', 'b', '1'}; + std::vector b2 = {'b', 'o', 'b', '2'}; + std::vector b3 = {'b', 'o', 'b', '3'}; + + { + BOOST_TEST_MESSAGE("Send bitcoin_transaction_sign_operation"); + + bitcoin_transaction_sign_operation sign_op; + + sign_op.payer = bob_id; + sign_op.proposal_id = proposal_id_type(0); + sign_op.signatures.push_back(b1); + sign_op.signatures.push_back(b2); + sign_op.signatures.push_back(b3); + + trx.operations.push_back(sign_op); + set_expiration(db, trx); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + + BOOST_REQUIRE(idx.size() == 0); + + const auto son_obj2 = sidx.find( bob_id ); + BOOST_REQUIRE(son_obj2 != sidx.end()); + + BOOST_REQUIRE(btidx.size() == 1); + + const auto btobj = btidx.find(bitcoin_transaction_id_type(0)); + BOOST_REQUIRE(btobj != btidx.end()); + + BOOST_REQUIRE(btobj->processed == false); + + auto sigs = btobj->signatures; + + BOOST_REQUIRE(sigs[son_obj1->id][0] == a1); + BOOST_REQUIRE(sigs[son_obj1->id][1] == a2); + BOOST_REQUIRE(sigs[son_obj1->id][2] == a3); + + BOOST_REQUIRE(sigs[son_obj2->id][0] == b1); + BOOST_REQUIRE(sigs[son_obj2->id][1] == b2); + BOOST_REQUIRE(sigs[son_obj2->id][2] == b3); + + { + BOOST_TEST_MESSAGE("Send bitcoin_send_transaction_process_operation"); + + bitcoin_send_transaction_process_operation process_op; + process_op.bitcoin_transaction_id = bitcoin_transaction_id_type(0); + process_op.payer = GRAPHENE_SON_ACCOUNT; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = alice_id; + proposal_op.proposed_ops.push_back(op_wrapper(process_op)); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(db.head_block_time().sec_since_epoch() + lifetime); + + trx.operations.push_back(proposal_op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + BOOST_REQUIRE(idx.size() == 1); + obj = idx.find(proposal_id_type(1)); + BOOST_REQUIRE(obj != idx.end()); + + + { + BOOST_TEST_MESSAGE("Send proposal_update_operation"); + + proposal_update_operation puo; + puo.fee_paying_account = bob_id; + puo.proposal = obj->id; + puo.active_approvals_to_add = { bob_id }; + + trx.operations.push_back(puo); + set_expiration(db, trx); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + BOOST_REQUIRE(idx.size() == 1); + obj = idx.find(proposal_id_type(1)); + BOOST_REQUIRE(obj != idx.end()); + + BOOST_REQUIRE(btobj != btidx.end()); + BOOST_REQUIRE(btobj->processed == false); + + { + BOOST_TEST_MESSAGE("Send proposal_update_operation"); + + proposal_update_operation puo; + puo.fee_paying_account = alice_id; + puo.proposal = obj->id; + puo.active_approvals_to_add = { alice_id }; + + trx.operations.push_back(puo); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + BOOST_REQUIRE(idx.size() == 0); + + BOOST_REQUIRE(btobj != btidx.end()); + BOOST_REQUIRE(btobj->processed == true); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END()