|
|
|
|
@ -3,11 +3,14 @@
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
|
|
#include <bitcoin/bitcoin.hpp>
|
|
|
|
|
|
|
|
|
|
#include <boost/algorithm/hex.hpp>
|
|
|
|
|
#include <boost/property_tree/json_parser.hpp>
|
|
|
|
|
#include <boost/property_tree/ptree.hpp>
|
|
|
|
|
|
|
|
|
|
#include <fc/crypto/base64.hpp>
|
|
|
|
|
#include <fc/crypto/hex.hpp>
|
|
|
|
|
#include <fc/log/logger.hpp>
|
|
|
|
|
#include <fc/network/ip.hpp>
|
|
|
|
|
|
|
|
|
|
@ -15,6 +18,7 @@
|
|
|
|
|
#include <graphene/chain/protocol/son_wallet.hpp>
|
|
|
|
|
#include <graphene/chain/son_info.hpp>
|
|
|
|
|
#include <graphene/chain/son_wallet_object.hpp>
|
|
|
|
|
#include <graphene/peerplays_sidechain/bitcoin_utils.hpp>
|
|
|
|
|
|
|
|
|
|
namespace graphene { namespace peerplays_sidechain {
|
|
|
|
|
|
|
|
|
|
@ -179,6 +183,34 @@ std::string bitcoin_rpc_client::createpsbt(const std::vector<btc_txout> &ins, co
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string bitcoin_rpc_client::converttopsbt(const std::string &hex) {
|
|
|
|
|
std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"converttopsbt\", \"method\": "
|
|
|
|
|
"\"converttopsbt\", \"params\": [\"" +
|
|
|
|
|
hex + "\"] }");
|
|
|
|
|
|
|
|
|
|
const auto reply = send_post_request(body);
|
|
|
|
|
|
|
|
|
|
if (reply.body.empty()) {
|
|
|
|
|
wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__));
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::stringstream ss(std::string(reply.body.begin(), reply.body.end()));
|
|
|
|
|
boost::property_tree::ptree json;
|
|
|
|
|
boost::property_tree::read_json(ss, json);
|
|
|
|
|
|
|
|
|
|
if (reply.status == 200) {
|
|
|
|
|
if (json.find("result") != json.not_found()) {
|
|
|
|
|
return json.get<std::string>("result");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (json.count("error") && !json.get_child("error").empty()) {
|
|
|
|
|
wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str()));
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string bitcoin_rpc_client::createrawtransaction(const std::vector<btc_txout> &ins, const fc::flat_map<std::string, double> outs) {
|
|
|
|
|
std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"createrawtransaction\", "
|
|
|
|
|
"\"method\": \"createrawtransaction\", \"params\": [");
|
|
|
|
|
@ -454,6 +486,33 @@ std::string bitcoin_rpc_client::getblock(const std::string &block_hash, int32_t
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string bitcoin_rpc_client::getblockchaininfo() {
|
|
|
|
|
std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getblockchaininfo\", \"method\": "
|
|
|
|
|
"\"getblockchaininfo\", \"params\": [] }");
|
|
|
|
|
|
|
|
|
|
const auto reply = send_post_request(body);
|
|
|
|
|
|
|
|
|
|
if (reply.body.empty()) {
|
|
|
|
|
wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__));
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::stringstream ss(std::string(reply.body.begin(), reply.body.end()));
|
|
|
|
|
boost::property_tree::ptree json;
|
|
|
|
|
boost::property_tree::read_json(ss, json);
|
|
|
|
|
|
|
|
|
|
if (reply.status == 200) {
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
boost::property_tree::json_parser::write_json(ss, json.get_child("result"));
|
|
|
|
|
return ss.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (json.count("error") && !json.get_child("error").empty()) {
|
|
|
|
|
wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str()));
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string bitcoin_rpc_client::gettransaction(const std::string &txid, const bool include_watch_only) {
|
|
|
|
|
std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"gettransaction\", \"method\": "
|
|
|
|
|
"\"gettransaction\", \"params\": [\"" +
|
|
|
|
|
@ -527,7 +586,9 @@ std::vector<btc_txout> bitcoin_rpc_client::listunspent(const uint32_t minconf, c
|
|
|
|
|
btc_txout txo;
|
|
|
|
|
txo.txid_ = entry.second.get_child("txid").get_value<std::string>();
|
|
|
|
|
txo.out_num_ = entry.second.get_child("vout").get_value<unsigned int>();
|
|
|
|
|
txo.amount_ = entry.second.get_child("amount").get_value<double>();
|
|
|
|
|
string amount = entry.second.get_child("amount").get_value<std::string>();
|
|
|
|
|
amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end());
|
|
|
|
|
txo.amount_ = std::stoll(amount);
|
|
|
|
|
result.push_back(txo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -565,7 +626,9 @@ std::vector<btc_txout> bitcoin_rpc_client::listunspent_by_address_and_amount(con
|
|
|
|
|
btc_txout txo;
|
|
|
|
|
txo.txid_ = entry.second.get_child("txid").get_value<std::string>();
|
|
|
|
|
txo.out_num_ = entry.second.get_child("vout").get_value<unsigned int>();
|
|
|
|
|
txo.amount_ = entry.second.get_child("amount").get_value<double>();
|
|
|
|
|
string amount = entry.second.get_child("amount").get_value<std::string>();
|
|
|
|
|
amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end());
|
|
|
|
|
txo.amount_ = std::stoll(amount);
|
|
|
|
|
result.push_back(txo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -879,6 +942,31 @@ sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain
|
|
|
|
|
bitcoin_client->loadwallet(wallet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string blockchain_info = bitcoin_client->getblockchaininfo();
|
|
|
|
|
std::stringstream bci_ss(std::string(blockchain_info.begin(), blockchain_info.end()));
|
|
|
|
|
boost::property_tree::ptree bci_json;
|
|
|
|
|
boost::property_tree::read_json(bci_ss, bci_json);
|
|
|
|
|
network = network_type::mainnet;
|
|
|
|
|
if (bci_json.count("chain")) {
|
|
|
|
|
std::string chain = bci_json.get<std::string>("chain");
|
|
|
|
|
if (chain != "mainnet") {
|
|
|
|
|
network = network_type::testnet;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (network == network_type::mainnet) {
|
|
|
|
|
payment_address_p2kh = libbitcoin::wallet::payment_address::mainnet_p2kh;
|
|
|
|
|
payment_address_p2sh = libbitcoin::wallet::payment_address::mainnet_p2sh;
|
|
|
|
|
ec_private_wif = libbitcoin::wallet::ec_private::mainnet_wif;
|
|
|
|
|
ec_private_p2kh = libbitcoin::wallet::ec_private::mainnet_p2kh;
|
|
|
|
|
ec_private_version = libbitcoin::wallet::ec_private::mainnet;
|
|
|
|
|
} else {
|
|
|
|
|
payment_address_p2kh = libbitcoin::wallet::payment_address::testnet_p2kh;
|
|
|
|
|
payment_address_p2sh = libbitcoin::wallet::payment_address::testnet_p2sh;
|
|
|
|
|
ec_private_wif = libbitcoin::wallet::ec_private::testnet_wif;
|
|
|
|
|
ec_private_p2kh = libbitcoin::wallet::ec_private::testnet_p2kh;
|
|
|
|
|
ec_private_version = libbitcoin::wallet::ec_private::testnet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
listener = std::unique_ptr<zmq_listener>(new zmq_listener(ip, zmq_port));
|
|
|
|
|
listener->event_received.connect([this](const std::string &event_data) {
|
|
|
|
|
std::thread(&sidechain_net_handler_bitcoin::handle_event, this, event_data).detach();
|
|
|
|
|
@ -937,13 +1025,12 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po)
|
|
|
|
|
|
|
|
|
|
if (son_sets_equal) {
|
|
|
|
|
auto active_sons = gpo.active_sons;
|
|
|
|
|
vector<string> son_pubkeys_bitcoin;
|
|
|
|
|
vector<pair<string, uint16_t>> son_pubkeys_bitcoin;
|
|
|
|
|
for (const son_info &si : active_sons) {
|
|
|
|
|
son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin));
|
|
|
|
|
son_pubkeys_bitcoin.push_back(make_pair(si.sidechain_public_keys.at(sidechain_type::bitcoin), si.weight));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t nrequired = son_pubkeys_bitcoin.size() * 2 / 3 + 1;
|
|
|
|
|
string reply_str = bitcoin_client->createmultisig(nrequired, son_pubkeys_bitcoin);
|
|
|
|
|
string reply_str = create_multisig_address(son_pubkeys_bitcoin);
|
|
|
|
|
|
|
|
|
|
std::stringstream active_pw_ss(reply_str);
|
|
|
|
|
boost::property_tree::ptree active_pw_pt;
|
|
|
|
|
@ -1068,16 +1155,12 @@ void sidechain_net_handler_bitcoin::process_primary_wallet() {
|
|
|
|
|
const chain::global_property_object &gpo = database.get_global_properties();
|
|
|
|
|
|
|
|
|
|
auto active_sons = gpo.active_sons;
|
|
|
|
|
vector<string> son_pubkeys_bitcoin;
|
|
|
|
|
vector<std::pair<string, uint16_t>> son_pubkeys_bitcoin;
|
|
|
|
|
for (const son_info &si : active_sons) {
|
|
|
|
|
son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin));
|
|
|
|
|
son_pubkeys_bitcoin.push_back(std::make_pair(si.sidechain_public_keys.at(sidechain_type::bitcoin), si.weight));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!wallet_password.empty()) {
|
|
|
|
|
bitcoin_client->walletpassphrase(wallet_password, 5);
|
|
|
|
|
}
|
|
|
|
|
uint32_t nrequired = son_pubkeys_bitcoin.size() * 2 / 3 + 1;
|
|
|
|
|
string reply_str = bitcoin_client->createmultisig(nrequired, son_pubkeys_bitcoin);
|
|
|
|
|
string reply_str = create_multisig_address(son_pubkeys_bitcoin);
|
|
|
|
|
|
|
|
|
|
std::stringstream active_pw_ss(reply_str);
|
|
|
|
|
boost::property_tree::ptree active_pw_pt;
|
|
|
|
|
@ -1274,8 +1357,7 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction() {
|
|
|
|
|
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;
|
|
|
|
|
uint64_t total_amount = 0.0;
|
|
|
|
|
std::vector<btc_txout> inputs = bitcoin_client->listunspent_by_address_and_amount(prev_pw_address, 0);
|
|
|
|
|
|
|
|
|
|
if (inputs.size() == 0) {
|
|
|
|
|
@ -1286,14 +1368,14 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction() {
|
|
|
|
|
total_amount += utx.amount_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (min_amount >= total_amount) {
|
|
|
|
|
if (fee_rate >= total_amount) {
|
|
|
|
|
elog("Failed not enough BTC to transfer from ${fa}", ("fa", prev_pw_address));
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fc::flat_map<std::string, double> outputs;
|
|
|
|
|
outputs[active_pw_address] = total_amount - min_amount;
|
|
|
|
|
outputs[active_pw_address] = double(total_amount - fee_rate) / 100000000.0;
|
|
|
|
|
|
|
|
|
|
return create_transaction(inputs, outputs);
|
|
|
|
|
}
|
|
|
|
|
@ -1334,7 +1416,7 @@ std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_
|
|
|
|
|
|
|
|
|
|
outputs[pw_address] = transfer_amount;
|
|
|
|
|
|
|
|
|
|
return create_transaction(inputs, outputs);
|
|
|
|
|
return create_transaction_psbt(inputs, outputs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const son_wallet_withdraw_object &swwo) {
|
|
|
|
|
@ -1356,8 +1438,7 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s
|
|
|
|
|
uint64_t min_fee_rate = 1000;
|
|
|
|
|
fee_rate = std::max(fee_rate, min_fee_rate);
|
|
|
|
|
|
|
|
|
|
double min_amount = ((double)(swwo.withdraw_amount.value + fee_rate) / 100000000.0); // Account only for relay fee for now
|
|
|
|
|
double total_amount = 0.0;
|
|
|
|
|
uint64_t total_amount = 0;
|
|
|
|
|
std::vector<btc_txout> inputs = bitcoin_client->listunspent_by_address_and_amount(pw_address, 0);
|
|
|
|
|
|
|
|
|
|
if (inputs.size() == 0) {
|
|
|
|
|
@ -1368,7 +1449,7 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s
|
|
|
|
|
total_amount += utx.amount_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (min_amount > total_amount) {
|
|
|
|
|
if (fee_rate > total_amount) {
|
|
|
|
|
elog("Failed not enough BTC to spend for ${pw}", ("pw", pw_address));
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
@ -1376,20 +1457,30 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s
|
|
|
|
|
|
|
|
|
|
fc::flat_map<std::string, double> outputs;
|
|
|
|
|
outputs[swwo.withdraw_address] = swwo.withdraw_amount.value / 100000000.0;
|
|
|
|
|
if ((total_amount - min_amount) > 0.0) {
|
|
|
|
|
outputs[pw_address] = total_amount - min_amount;
|
|
|
|
|
if ((total_amount - fee_rate) > 0.0) {
|
|
|
|
|
outputs[pw_address] = double(total_amount - fee_rate) / 100000000.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return create_transaction(inputs, outputs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Creates segwit multisig address
|
|
|
|
|
// Function to actually create segwit multisig address should return json string with address info, or empty string in case of failure
|
|
|
|
|
std::string sidechain_net_handler_bitcoin::create_multisig_address(const std::vector<std::pair<std::string, uint16_t>> &son_pubkeys) {
|
|
|
|
|
std::string new_addr = "";
|
|
|
|
|
//new_addr = create_multisig_address_raw(son_pubkeys);
|
|
|
|
|
//new_addr = create_multisig_address_psbt(son_pubkeys);
|
|
|
|
|
new_addr = create_multisig_address_standalone(son_pubkeys);
|
|
|
|
|
return new_addr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Creates transaction in any format
|
|
|
|
|
// Function to actually create transaction should return transaction string, or empty string in case of failure
|
|
|
|
|
std::string sidechain_net_handler_bitcoin::create_transaction(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs) {
|
|
|
|
|
std::string new_tx = "";
|
|
|
|
|
//new_tx = create_transaction_raw(inputs, outputs);
|
|
|
|
|
new_tx = create_transaction_psbt(inputs, outputs);
|
|
|
|
|
//new_tx = create_transaction_standalone(inputs, outputs);
|
|
|
|
|
//new_tx = create_transaction_psbt(inputs, outputs);
|
|
|
|
|
new_tx = create_transaction_standalone(inputs, outputs);
|
|
|
|
|
return new_tx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1399,7 +1490,11 @@ std::string sidechain_net_handler_bitcoin::sign_transaction(const sidechain_tran
|
|
|
|
|
complete = false;
|
|
|
|
|
std::string new_tx = "";
|
|
|
|
|
//new_tx = sign_transaction_raw(sto, complete);
|
|
|
|
|
new_tx = sign_transaction_psbt(sto, complete);
|
|
|
|
|
if (sto.object_id.type() == 30) {
|
|
|
|
|
new_tx = sign_transaction_psbt(sto, complete);
|
|
|
|
|
} else {
|
|
|
|
|
new_tx = sign_transaction_standalone(sto, complete);
|
|
|
|
|
}
|
|
|
|
|
//new_tx = sign_transaction_standalone(sto, complete);
|
|
|
|
|
return new_tx;
|
|
|
|
|
}
|
|
|
|
|
@ -1407,7 +1502,304 @@ std::string sidechain_net_handler_bitcoin::sign_transaction(const sidechain_tran
|
|
|
|
|
bool sidechain_net_handler_bitcoin::send_transaction(const sidechain_transaction_object &sto, std::string &sidechain_transaction) {
|
|
|
|
|
sidechain_transaction = "";
|
|
|
|
|
//return send_transaction_raw(sto, sidechain_transaction);
|
|
|
|
|
return send_transaction_psbt(sto, sidechain_transaction);
|
|
|
|
|
//return send_transaction_psbt(sto, sidechain_transaction);
|
|
|
|
|
if (sto.object_id.type() == 30) {
|
|
|
|
|
return send_transaction_psbt(sto, sidechain_transaction);
|
|
|
|
|
} else {
|
|
|
|
|
return send_transaction_standalone(sto, sidechain_transaction);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string sidechain_net_handler_bitcoin::create_multisig_address_raw(const std::vector<std::pair<std::string, uint16_t>> &son_pubkeys) {
|
|
|
|
|
vector<std::string> pubkeys;
|
|
|
|
|
for (auto s : son_pubkeys) {
|
|
|
|
|
pubkeys.push_back(s.first);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!wallet_password.empty()) {
|
|
|
|
|
bitcoin_client->walletpassphrase(wallet_password, 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t nrequired = pubkeys.size() * 2 / 3 + 1;
|
|
|
|
|
return bitcoin_client->addmultisigaddress(nrequired, pubkeys);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string sidechain_net_handler_bitcoin::create_multisig_address_psbt(const std::vector<std::pair<std::string, uint16_t>> &son_pubkeys) {
|
|
|
|
|
vector<std::string> pubkeys;
|
|
|
|
|
for (auto s : son_pubkeys) {
|
|
|
|
|
pubkeys.push_back(s.first);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!wallet_password.empty()) {
|
|
|
|
|
bitcoin_client->walletpassphrase(wallet_password, 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t nrequired = pubkeys.size() * 2 / 3 + 1;
|
|
|
|
|
return bitcoin_client->addmultisigaddress(nrequired, pubkeys);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::vector<unsigned char>> read_byte_arrays_from_string(const std::string &string_buf)
|
|
|
|
|
{
|
|
|
|
|
std::stringstream ss(string_buf);
|
|
|
|
|
boost::property_tree::ptree json;
|
|
|
|
|
boost::property_tree::read_json(ss, json);
|
|
|
|
|
|
|
|
|
|
std::vector<bytes> data;
|
|
|
|
|
for(auto &v: json)
|
|
|
|
|
{
|
|
|
|
|
std::string hex = v.second.data();
|
|
|
|
|
bytes item;
|
|
|
|
|
item.resize(hex.size() / 2);
|
|
|
|
|
fc::from_hex(hex, (char*)&item[0], item.size());
|
|
|
|
|
data.push_back(item);
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string write_byte_arrays_to_string(const std::vector<std::vector<unsigned char>>& data)
|
|
|
|
|
{
|
|
|
|
|
std::string res = "[";
|
|
|
|
|
for (unsigned int idx = 0; idx < data.size(); ++idx) {
|
|
|
|
|
res += "\"" + fc::to_hex((char*)&data[idx][0], data[idx].size()) + "\"";
|
|
|
|
|
if (idx != data.size() - 1)
|
|
|
|
|
res += ",";
|
|
|
|
|
}
|
|
|
|
|
res += "]";
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
libbitcoin::chain::script get_multisig_witness_script(const std::vector<libbitcoin::wallet::ec_public> &son_pubkeys)
|
|
|
|
|
{
|
|
|
|
|
libbitcoin::point_list keys;
|
|
|
|
|
for (auto& key : son_pubkeys) {
|
|
|
|
|
keys.push_back(key.point());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t nrequired = son_pubkeys.size() * 2 / 3 + 1;
|
|
|
|
|
libbitcoin::chain::script multisig = libbitcoin::chain::script::to_pay_multisig_pattern(nrequired, keys);
|
|
|
|
|
return multisig;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
libbitcoin::chain::script getRedeemScript(const std::vector<libbitcoin::wallet::ec_public> &son_pubkeys) {
|
|
|
|
|
libbitcoin::chain::script multisig = get_multisig_witness_script(son_pubkeys);
|
|
|
|
|
libbitcoin::data_chunk multisig_hash = libbitcoin::to_chunk(libbitcoin::sha256_hash(multisig.to_data(0)));
|
|
|
|
|
libbitcoin::machine::operation::list redeemscript_ops {libbitcoin::machine::operation(libbitcoin::machine::opcode(0)), libbitcoin::machine::operation(multisig_hash)};
|
|
|
|
|
libbitcoin::chain::script redeem_script = libbitcoin::chain::script(redeemscript_ops);
|
|
|
|
|
return redeem_script;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
libbitcoin::machine::operation script_num(uint32_t val)
|
|
|
|
|
{
|
|
|
|
|
if (val < 16)
|
|
|
|
|
return libbitcoin::machine::operation::opcode_from_positive(val & 0xff);
|
|
|
|
|
libbitcoin::data_chunk result;
|
|
|
|
|
while (val) {
|
|
|
|
|
result.push_back(val & 0xff);
|
|
|
|
|
val >>= 8;
|
|
|
|
|
}
|
|
|
|
|
// - If the most significant byte is >= 0x80 and the value is positive, push a
|
|
|
|
|
// new zero-byte to make the significant byte < 0x80 again.
|
|
|
|
|
if (result.back() & 0x80)
|
|
|
|
|
result.push_back(0);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
libbitcoin::chain::script get_weighted_multisig_witness_script(const std::vector<std::pair<std::string, uint16_t>> &son_pubkeys)
|
|
|
|
|
{
|
|
|
|
|
using namespace libbitcoin;
|
|
|
|
|
using namespace libbitcoin::chain;
|
|
|
|
|
using namespace libbitcoin::machine;
|
|
|
|
|
using namespace libbitcoin::wallet;
|
|
|
|
|
|
|
|
|
|
// Online visualizer/debugger
|
|
|
|
|
// https://siminchen.github.io/bitcoinIDE/build/editor.html
|
|
|
|
|
//
|
|
|
|
|
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
|
|
|
// 03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ELSE 0 OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 02d67c26cf20153fe7625ca1454222d3b3aeb53b122d8a0f7d32a3dd4b2c2016f4 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 025f7cfda933516fd590c5a34ad4a68e3143b6f4155a64b3aab2c55fb851150f61 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 0228155bb1ddcd11c7f14a2752565178023aa963f84ea6b6a052bddebad6fe9866 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 037500441cfb4484da377073459511823b344f1ef0d46bac1efd4c7c466746f666 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 02ef0d79bfdb99ab0be674b1d5d06c24debd74bffdc28d466633d6668cc281cccf OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 0317941e4219548682fb8d8e172f0a8ce4d83ce21272435c85d598558c8e060b7f OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 0266065b27f7e3d3ad45b471b1cd4e02de73fc4737dc2679915a45e293c5adcf84 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 023821cc3da7be9e8cdceb8f146e9ddd78a9519875ecc5b42fe645af690544bccf OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 0229ff2b2106b76c27c393e82d71c20eec32bcf1f0cf1a9aca8a237269a67ff3e5 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 024d113381cc09deb8a6da62e0470644d1a06de82be2725b5052668c8845a4a8da OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 03df2462a5a2f681a3896f61964a65566ff77448be9a55a6da18506fd9c6c051c1 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 02bafba3096f546cc5831ce1e49ba7142478a659f2d689bbc70ed37235255172a8 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 0287bcbd4f5d357f89a86979b386402445d7e9a5dccfd16146d1d2ab0dc2c32ae8 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// OP_SWAP
|
|
|
|
|
// 02053859d76aa375d6f343a60e3678e906c008015e32fe4712b1fd2b26473bdd73 OP_CHECKSIG
|
|
|
|
|
// OP_IF OP_1 OP_ADD OP_ENDIF
|
|
|
|
|
// 11 OP_GREATERTHANOREQUAL
|
|
|
|
|
|
|
|
|
|
libbitcoin::machine::operation::list witness_script_ops;
|
|
|
|
|
|
|
|
|
|
uint16_t idx = 0;
|
|
|
|
|
uint32_t total_weight = 0;
|
|
|
|
|
for (auto son_pubkey : son_pubkeys) {
|
|
|
|
|
ec_public key = ec_public(son_pubkey.first);
|
|
|
|
|
data_chunk key_data = to_chunk(key.point());
|
|
|
|
|
uint16_t weight = son_pubkey.second;
|
|
|
|
|
|
|
|
|
|
total_weight = total_weight + weight;
|
|
|
|
|
|
|
|
|
|
witness_script_ops.emplace_back(key_data);
|
|
|
|
|
witness_script_ops.emplace_back(opcode::checksig);
|
|
|
|
|
|
|
|
|
|
witness_script_ops.emplace_back(opcode::if_);
|
|
|
|
|
witness_script_ops.emplace_back(script_num(weight));
|
|
|
|
|
if (idx == 0) {
|
|
|
|
|
witness_script_ops.emplace_back(opcode::else_);
|
|
|
|
|
witness_script_ops.emplace_back(opcode::push_size_0);
|
|
|
|
|
} else {
|
|
|
|
|
witness_script_ops.emplace_back(opcode::add);
|
|
|
|
|
}
|
|
|
|
|
witness_script_ops.emplace_back(opcode::endif);
|
|
|
|
|
|
|
|
|
|
if (idx < son_pubkeys.size() - 1) {
|
|
|
|
|
witness_script_ops.emplace_back(opcode::swap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
idx = idx + 1;
|
|
|
|
|
}
|
|
|
|
|
witness_script_ops.emplace_back(script_num(total_weight * 2 / 3));
|
|
|
|
|
witness_script_ops.emplace_back(opcode::greaterthanorequal);
|
|
|
|
|
|
|
|
|
|
return script(witness_script_ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void read_tx_data_from_string(const std::string &string_buf, std::vector<unsigned char> &tx, std::vector<uint64_t> &in_amounts)
|
|
|
|
|
{
|
|
|
|
|
std::stringstream ss(string_buf);
|
|
|
|
|
boost::property_tree::ptree json;
|
|
|
|
|
boost::property_tree::read_json(ss, json);
|
|
|
|
|
std::string tx_hex = json.get<std::string>("tx_hex");
|
|
|
|
|
tx.clear();
|
|
|
|
|
tx.resize(tx_hex.size() / 2);
|
|
|
|
|
fc::from_hex(tx_hex, (char*)&tx[0], tx.size());
|
|
|
|
|
in_amounts.clear();
|
|
|
|
|
for(auto &v: json.get_child("in_amounts"))
|
|
|
|
|
in_amounts.push_back(fc::to_uint64(v.second.data()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string save_tx_data_to_string(const std::vector<unsigned char> &tx, const std::vector<uint64_t> &in_amounts)
|
|
|
|
|
{
|
|
|
|
|
std::string res = "{\"tx_hex\":\"" + fc::to_hex((const char*)&tx[0], tx.size()) + "\",\"in_amounts\":[";
|
|
|
|
|
for (unsigned int idx = 0; idx < in_amounts.size(); ++idx) {
|
|
|
|
|
res += fc::to_string(in_amounts[idx]);
|
|
|
|
|
if (idx != in_amounts.size() - 1)
|
|
|
|
|
res += ",";
|
|
|
|
|
}
|
|
|
|
|
res += "]}";
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string sidechain_net_handler_bitcoin::create_multisig_address_standalone(const std::vector<std::pair<std::string, uint16_t>> &son_pubkeys) {
|
|
|
|
|
|
|
|
|
|
//using namespace libbitcoin;
|
|
|
|
|
//using namespace libbitcoin::chain;
|
|
|
|
|
//using namespace libbitcoin::machine;
|
|
|
|
|
//using namespace libbitcoin::wallet;
|
|
|
|
|
//
|
|
|
|
|
//uint32_t nrequired = son_pubkeys.size() * 2 / 3 + 1;
|
|
|
|
|
//point_list keys;
|
|
|
|
|
//for (auto son_pubkey : son_pubkeys) {
|
|
|
|
|
// keys.push_back(ec_public(son_pubkey.first));
|
|
|
|
|
//}
|
|
|
|
|
//script witness_script = script::to_pay_multisig_pattern(nrequired, keys);
|
|
|
|
|
//
|
|
|
|
|
//// sha256 of witness script
|
|
|
|
|
//data_chunk multisig_hash = to_chunk(sha256_hash(witness_script.to_data(0)));
|
|
|
|
|
//
|
|
|
|
|
//// redeem script
|
|
|
|
|
//libbitcoin::machine::operation::list redeemscript_ops{libbitcoin::machine::operation(opcode(0)), libbitcoin::machine::operation(multisig_hash)};
|
|
|
|
|
//script redeem_script = script(redeemscript_ops);
|
|
|
|
|
//
|
|
|
|
|
//// address
|
|
|
|
|
//payment_address address = payment_address(redeem_script, payment_address_p2sh);
|
|
|
|
|
//
|
|
|
|
|
//std::stringstream ss;
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
//ss << "{\"result\": {\"address\": \"" << address.encoded() << "\", \"redeemScript\": \"" << encode_base16(witness_script.to_data(0)) << "\"" << "}, \"error\":null}";
|
|
|
|
|
//std::string res = ss.str();
|
|
|
|
|
//
|
|
|
|
|
//std::cout << "Redeem Script Hash: " << encode_base16(address.hash()) << std::endl;
|
|
|
|
|
//std::cout << "Payment Address: " << address.encoded() << std::endl;
|
|
|
|
|
//std::cout << "Redeem Script: " << redeem_script.to_string(0) << std::endl;
|
|
|
|
|
//std::cout << "Witness Script: " << witness_script.to_string(0) << std::endl;
|
|
|
|
|
//std::cout << "Witness Script: " << encode_base16(witness_script.to_data(0)) << std::endl;
|
|
|
|
|
//
|
|
|
|
|
//std::cout << res << std::endl;
|
|
|
|
|
////create_multisig_address_psbt(son_pubkeys);
|
|
|
|
|
//
|
|
|
|
|
//return res;
|
|
|
|
|
|
|
|
|
|
using namespace libbitcoin;
|
|
|
|
|
using namespace libbitcoin::chain;
|
|
|
|
|
using namespace libbitcoin::machine;
|
|
|
|
|
using namespace libbitcoin::wallet;
|
|
|
|
|
|
|
|
|
|
script witness_script = get_weighted_multisig_witness_script(son_pubkeys);
|
|
|
|
|
|
|
|
|
|
std::cout << "Witness Script is valid: " << witness_script.is_valid() << std::endl;
|
|
|
|
|
std::cout << "Witness Script operations are valid: " << witness_script.is_valid_operations() << std::endl;
|
|
|
|
|
|
|
|
|
|
// sha256 of witness script
|
|
|
|
|
data_chunk multisig_hash = to_chunk(sha256_hash(witness_script.to_data(0)));
|
|
|
|
|
|
|
|
|
|
// redeem script
|
|
|
|
|
libbitcoin::machine::operation::list redeemscript_ops{libbitcoin::machine::operation(opcode(0)), libbitcoin::machine::operation(multisig_hash)};
|
|
|
|
|
script redeem_script = script(redeemscript_ops);
|
|
|
|
|
|
|
|
|
|
// address
|
|
|
|
|
payment_address address = payment_address(redeem_script, payment_address_p2sh);
|
|
|
|
|
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
|
|
|
|
|
ss << "{\"result\": {\"address\": \"" << address.encoded() << "\", \"redeemScript\": \"" << encode_base16(witness_script.to_data(0)) << "\""
|
|
|
|
|
<< "}, \"error\":null}";
|
|
|
|
|
std::string res = ss.str();
|
|
|
|
|
|
|
|
|
|
std::cout << "Redeem Script Hash: " << encode_base16(address.hash()) << std::endl;
|
|
|
|
|
std::cout << "Payment Address: " << address.encoded() << std::endl;
|
|
|
|
|
std::cout << "Redeem Script: " << redeem_script.to_string(0) << std::endl;
|
|
|
|
|
std::cout << "Witness Script: " << witness_script.to_string(0) << std::endl;
|
|
|
|
|
std::cout << "Witness Script: " << encode_base16(witness_script.to_data(0)) << std::endl;
|
|
|
|
|
|
|
|
|
|
std::cout << res << std::endl;
|
|
|
|
|
//create_multisig_address_psbt(son_pubkeys);
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string sidechain_net_handler_bitcoin::create_transaction_raw(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs) {
|
|
|
|
|
@ -1475,8 +1867,34 @@ std::string sidechain_net_handler_bitcoin::create_transaction_standalone(const s
|
|
|
|
|
// }
|
|
|
|
|
// ]
|
|
|
|
|
//}
|
|
|
|
|
libbitcoin::chain::transaction tx;
|
|
|
|
|
tx.set_version(2u);
|
|
|
|
|
std::vector<uint64_t> in_amounts;
|
|
|
|
|
for (auto in : inputs) {
|
|
|
|
|
libbitcoin::chain::input bin;
|
|
|
|
|
libbitcoin::hash_digest tx_id;
|
|
|
|
|
libbitcoin::decode_hash(tx_id, in.txid_);
|
|
|
|
|
bin.set_previous_output(libbitcoin::chain::output_point(tx_id, in.out_num_));
|
|
|
|
|
bin.set_sequence(libbitcoin::max_input_sequence);
|
|
|
|
|
tx.inputs().push_back(bin);
|
|
|
|
|
in_amounts.push_back(in.amount_);
|
|
|
|
|
}
|
|
|
|
|
for (auto out : outputs) {
|
|
|
|
|
libbitcoin::chain::output bout;
|
|
|
|
|
uint64_t satoshis = out.second * 100000000.0;
|
|
|
|
|
bout.set_value(satoshis);
|
|
|
|
|
libbitcoin::wallet::payment_address addr(out.first);
|
|
|
|
|
if (addr.version() == payment_address_p2sh) {
|
|
|
|
|
bout.set_script(libbitcoin::chain::script::to_pay_script_hash_pattern(addr));
|
|
|
|
|
} else {
|
|
|
|
|
bout.set_script(libbitcoin::chain::script::to_pay_key_hash_pattern(addr));
|
|
|
|
|
}
|
|
|
|
|
tx.outputs().push_back(bout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "";
|
|
|
|
|
std::string tx_raw = save_tx_data_to_string(tx.to_data(), in_amounts);
|
|
|
|
|
|
|
|
|
|
return tx_raw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string sidechain_net_handler_bitcoin::sign_transaction_raw(const sidechain_transaction_object &sto, bool &complete) {
|
|
|
|
|
@ -1578,8 +1996,42 @@ std::string sidechain_net_handler_bitcoin::sign_transaction_psbt(const sidechain
|
|
|
|
|
std::string sidechain_net_handler_bitcoin::sign_transaction_standalone(const sidechain_transaction_object &sto, bool &complete) {
|
|
|
|
|
complete = false;
|
|
|
|
|
|
|
|
|
|
complete = true;
|
|
|
|
|
return "";
|
|
|
|
|
std::string pubkey = plugin.get_current_son_object().sidechain_public_keys.at(sidechain);
|
|
|
|
|
uint16_t weight = 0;
|
|
|
|
|
std::string prvkey = get_private_key(pubkey);
|
|
|
|
|
using namespace libbitcoin;
|
|
|
|
|
using namespace libbitcoin::machine;
|
|
|
|
|
using namespace libbitcoin::wallet;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
libbitcoin::data_chunk data;
|
|
|
|
|
libbitcoin::ec_secret key;
|
|
|
|
|
libbitcoin::decode_base16(key, prvkey);
|
|
|
|
|
std::vector<uint64_t> in_amounts;
|
|
|
|
|
read_tx_data_from_string(sto.transaction, data, in_amounts);
|
|
|
|
|
libbitcoin::chain::transaction tx;
|
|
|
|
|
if (!tx.from_data(data)) {
|
|
|
|
|
elog("Failed to decode transaction ${tx}", ("tx", sto.transaction));
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::pair<std::string, uint16_t>> son_pubkeys;
|
|
|
|
|
for (auto& son: sto.signers) {
|
|
|
|
|
std::string pub_key = son.sidechain_public_keys.at(sidechain_type::bitcoin);
|
|
|
|
|
son_pubkeys.push_back(std::make_pair(pub_key, son.weight));
|
|
|
|
|
if (son.son_id == plugin.get_current_son_id())
|
|
|
|
|
weight = son.weight;
|
|
|
|
|
}
|
|
|
|
|
libbitcoin::chain::script witness_script = get_weighted_multisig_witness_script(son_pubkeys);
|
|
|
|
|
vector<endorsement> sigs;
|
|
|
|
|
sigs.resize(tx.inputs().size());
|
|
|
|
|
for (unsigned int itr = 0; itr < sigs.size(); itr++) {
|
|
|
|
|
libbitcoin::chain::script::create_endorsement(sigs.at(itr), key, witness_script, tx, itr, sighash_algorithm::all, script_version::zero, in_amounts[itr]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string tx_signature = write_byte_arrays_to_string(sigs);
|
|
|
|
|
complete = (sto.current_weight + weight > sto.threshold);
|
|
|
|
|
return tx_signature;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool sidechain_net_handler_bitcoin::send_transaction_raw(const sidechain_transaction_object &sto, std::string &sidechain_transaction) {
|
|
|
|
|
@ -1632,6 +2084,54 @@ bool sidechain_net_handler_bitcoin::send_transaction_psbt(const sidechain_transa
|
|
|
|
|
return false;
|
|
|
|
|
} // namespace peerplays_sidechain
|
|
|
|
|
|
|
|
|
|
bool sidechain_net_handler_bitcoin::send_transaction_standalone(const sidechain_transaction_object &sto, std::string &sidechain_transaction) {
|
|
|
|
|
sidechain_transaction = "";
|
|
|
|
|
|
|
|
|
|
libbitcoin::data_chunk tx_buf;
|
|
|
|
|
std::vector<uint64_t> in_amounts;
|
|
|
|
|
read_tx_data_from_string(sto.transaction, tx_buf, in_amounts);
|
|
|
|
|
libbitcoin::chain::transaction tx;
|
|
|
|
|
if (!tx.from_data(tx_buf)) {
|
|
|
|
|
elog("Failed to decode transaction ${tx}", ("tx", sto.transaction));
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::pair<std::string, uint16_t>> son_pubkeys;
|
|
|
|
|
for (auto& son: sto.signers) {
|
|
|
|
|
std::string pub_key = son.sidechain_public_keys.at(sidechain_type::bitcoin);
|
|
|
|
|
son_pubkeys.push_back(std::make_pair(pub_key, son.weight));
|
|
|
|
|
}
|
|
|
|
|
libbitcoin::chain::script witness_script = get_weighted_multisig_witness_script(son_pubkeys);
|
|
|
|
|
|
|
|
|
|
std::vector<libbitcoin::data_stack> scripts;
|
|
|
|
|
for (uint32_t idx = 0; idx < tx.inputs().size(); idx++)
|
|
|
|
|
scripts.push_back({witness_script.to_data(0)});
|
|
|
|
|
|
|
|
|
|
for (auto sig_data: sto.signatures) {
|
|
|
|
|
if (sig_data.second.size()) {
|
|
|
|
|
std::vector<libbitcoin::data_chunk> sigs = read_byte_arrays_from_string(sig_data.second);
|
|
|
|
|
FC_ASSERT(sigs.size() == scripts.size());
|
|
|
|
|
// place signatures in reverse order
|
|
|
|
|
for (uint32_t idx = 0; idx < scripts.size(); idx++)
|
|
|
|
|
{
|
|
|
|
|
auto& s = scripts.at(idx);
|
|
|
|
|
s.insert(s.begin(), sigs[idx]);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (uint32_t idx = 0; idx < scripts.size(); idx++)
|
|
|
|
|
{
|
|
|
|
|
auto& s = scripts.at(idx);
|
|
|
|
|
s.insert(s.begin(), libbitcoin::data_chunk());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (uint32_t idx = 0; idx < tx.inputs().size(); idx++)
|
|
|
|
|
tx.inputs()[idx].set_witness(scripts[idx]);
|
|
|
|
|
|
|
|
|
|
return bitcoin_client->sendrawtransaction(libbitcoin::encode_base16(tx.to_data()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) {
|
|
|
|
|
std::string block = bitcoin_client->getblock(event_data);
|
|
|
|
|
if (block != "") {
|
|
|
|
|
|