Compare commits

...

15 commits

6 changed files with 641 additions and 149 deletions

View file

@ -1,11 +1,14 @@
#include <fc/crypto/base58.hpp>
#include <fc/crypto/elliptic.hpp>
#include <fc/crypto/hex.hpp>
#include <fc/crypto/ripemd160.hpp>
#include <fc/crypto/sha256.hpp>
#include <fc/io/raw.hpp>
#include <graphene/peerplays_sidechain/bitcoin_utils.hpp>
#include <secp256k1.h>
#include <boost/property_tree/json_parser.hpp>
namespace graphene { namespace peerplays_sidechain {
static const unsigned char OP_0 = 0x00;
@ -32,6 +35,7 @@ static const unsigned char OP_SWAP = 0x7c;
static const unsigned char OP_EQUAL = 0x87;
static const unsigned char OP_ADD = 0x93;
static const unsigned char OP_GREATERTHAN = 0xa0;
static const unsigned char OP_GREATERTHANOREQUAL = 0xa2;
static const unsigned char OP_HASH160 = 0xa9;
static const unsigned char OP_CHECKSIG = 0xac;
@ -351,7 +355,7 @@ void add_number_to_script(bytes &script, unsigned char data) {
add_data_to_script(script, {data});
}
bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, int>> key_data) {
bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, uint64_t>> key_data) {
int total_weight = 0;
bytes result;
add_number_to_script(result, 0);
@ -366,15 +370,26 @@ bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, int>> ke
result.push_back(OP_ADD);
result.push_back(OP_ENDIF);
}
int threshold_weight = 2 * total_weight / 3;
uint64_t threshold_weight = 2 * total_weight / 3;
add_number_to_script(result, static_cast<unsigned char>(threshold_weight));
result.push_back(OP_GREATERTHAN);
result.push_back(OP_GREATERTHANOREQUAL);
return result;
}
/** The Bech32 character set for encoding. */
const char *charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
/** The Bech32 character set for decoding. */
const int8_t charset_rev[128] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1};
/** Concatenate two byte arrays. */
bytes cat(bytes x, const bytes &y) {
x.insert(x.end(), y.begin(), y.end());
@ -409,6 +424,20 @@ uint32_t polymod(const bytes &values) {
return chk;
}
/** Expand a HRP for use in checksum computation. */
bytes bech32_expand_hrp(const std::string &hrp) {
bytes ret;
ret.reserve(hrp.size() + 90);
ret.resize(hrp.size() * 2 + 1);
for (size_t i = 0; i < hrp.size(); ++i) {
unsigned char c = hrp[i];
ret[i] = c >> 5;
ret[i + hrp.size() + 1] = c & 0x1f;
}
ret[hrp.size()] = 0;
return ret;
}
/** Create a checksum. */
bytes bech32_checksum(const std::string &hrp, const bytes &values) {
bytes enc = cat(expand_hrp(hrp), values);
@ -422,8 +451,17 @@ bytes bech32_checksum(const std::string &hrp, const bytes &values) {
return ret;
}
/** Verify a checksum. */
bool bech32_verify_checksum(const std::string &hrp, const bytes &values) {
// PolyMod computes what value to xor into the final values to make the checksum 0. However,
// if we required that the checksum was 0, it would be the case that appending a 0 to a valid
// list of values would result in a new valid list. For that reason, Bech32 requires the
// resulting checksum to be 1 instead.
return polymod(cat(bech32_expand_hrp(hrp), values)) == 1;
}
/** Encode a Bech32 string. */
std::string bech32(const std::string &hrp, const bytes &values) {
std::string bech32_encode(const std::string &hrp, const bytes &values) {
bytes checksum = bech32_checksum(hrp, values);
bytes combined = cat(values, checksum);
std::string ret = hrp + '1';
@ -434,6 +472,33 @@ std::string bech32(const std::string &hrp, const bytes &values) {
return ret;
}
/** Decode a Bech32 string. */
bytes bech32_decode(const std::string &str) {
if (str.size() > 90)
FC_THROW("Invalid bech32 string ${a}", ("a", str));
for (unsigned char c : str) {
if (c < 33 || c > 126)
FC_THROW("Invalid bech32 string ${a}", ("a", str));
if (c >= 'A' && c <= 'Z')
FC_THROW("Invalid bech32 string ${a}", ("a", str));
}
size_t pos = str.rfind('1');
if (pos == str.npos || pos == 0 || pos + 7 > str.size())
FC_THROW("Invalid bech32 string ${a}", ("a", str));
std::string hrp = str.substr(0, pos);
bytes values(str.size() - 1 - pos);
for (size_t i = 0; i < str.size() - 1 - pos; ++i) {
unsigned char c = str[i + pos + 1];
int8_t rev = (c < 33 || c > 126) ? -1 : charset_rev[c];
if (rev == -1)
FC_THROW("Invalid bech32 string ${a}", ("a", str));
values[i] = rev;
}
if (!bech32_verify_checksum(hrp, values))
FC_THROW("Invalid bech32 string ${a}", ("a", str));
return bytes(values.begin(), values.end() - 6);
}
/** Convert from one power-of-2 number base to another. */
template <int frombits, int tobits, bool pad>
bool convertbits(bytes &out, const bytes &in) {
@ -464,10 +529,23 @@ std::string segwit_addr_encode(const std::string &hrp, uint8_t witver, const byt
bytes enc;
enc.push_back(witver);
convertbits<8, 5, true>(enc, witprog);
std::string ret = bech32(hrp, enc);
std::string ret = bech32_encode(hrp, enc);
return ret;
}
/** Decode a SegWit address. */
bytes segwit_addr_decode(const std::string &addr) {
bytes dec = bech32_decode(addr);
if (dec.size() < 1)
FC_THROW("Invalid bech32 address ${a}", ("a", addr));
bytes conv;
if (!convertbits<5, 8, false>(conv, bytes(dec.begin() + 1, dec.end())) ||
conv.size() < 2 || conv.size() > 40 || dec[0] > 16 || (dec[0] == 0 && conv.size() != 20 && conv.size() != 32)) {
FC_THROW("Invalid bech32 address ${a}", ("a", addr));
}
return conv;
}
std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network) {
// calc script hash
fc::sha256 sh = fc::sha256::hash(reinterpret_cast<const char *>(&script[0]), script.size());
@ -476,14 +554,23 @@ std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_networ
case (mainnet):
return segwit_addr_encode("bc", 0, wp);
case (testnet):
case (regtest):
return segwit_addr_encode("tb", 0, wp);
case (regtest):
return segwit_addr_encode("bcrt", 0, wp);
default:
FC_THROW("Unknown bitcoin network type");
}
FC_THROW("Unknown bitcoin network type");
}
bytes lock_script_from_pw_address(const std::string &address) {
bytes result;
result.push_back(OP_0);
bytes script_hash = segwit_addr_decode(address);
add_data_to_script(result, script_hash);
return result;
}
bytes lock_script_for_redeem_script(const bytes &script) {
bytes result;
result.push_back(OP_0);
@ -677,4 +764,86 @@ bytes add_signatures_to_unsigned_tx(const bytes &unsigned_tx, const std::vector<
return ret;
}
std::string get_weighted_multisig_address(const std::vector<std::pair<std::string, uint64_t>> &public_keys, bitcoin_network network) {
std::vector<std::pair<fc::ecc::public_key, uint64_t>> key_data;
for (auto p : public_keys) {
fc::ecc::public_key_data kd;
fc::from_hex(p.first, kd.begin(), kd.size());
key_data.push_back(std::make_pair(fc::ecc::public_key(kd), p.second));
}
bytes redeem_script = generate_redeem_script(key_data);
return p2wsh_address_from_redeem_script(redeem_script, network);
}
bytes get_weighted_multisig_redeem_script(std::vector<std::pair<std::string, uint64_t>> public_keys) {
std::vector<std::pair<fc::ecc::public_key, uint64_t>> key_data;
for (auto p : public_keys)
{
fc::ecc::public_key_data kd;
fc::from_hex(p.first, kd.begin(), kd.size());
key_data.push_back(std::make_pair(fc::ecc::public_key(kd), p.second));
}
return generate_redeem_script(key_data);
}
void read_tx_data_from_string(const std::string &string_buf, bytes &tx, std::vector<uint64_t> &in_amounts, bytes &redeem_script)
{
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 script = json.get<std::string>("redeem_script");
redeem_script.clear();
redeem_script.resize(script.size() / 2);
fc::from_hex(script, (char*)&redeem_script[0], redeem_script.size());
}
std::string save_tx_data_to_string(const bytes &tx, const std::vector<uint64_t> &in_amounts, const bytes &redeem_script)
{
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 += "],\"redeem_script\":\"" + fc::to_hex((const char*)&redeem_script[0], redeem_script.size()) + "\"}";
return res;
}
std::vector<bytes> read_bytes_array_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_bytes_array_to_string(const std::vector<bytes>& 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;
}
}} // namespace graphene::peerplays_sidechain

View file

@ -10,9 +10,13 @@ enum bitcoin_network {
regtest
};
bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, int>> key_data);
std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network = mainnet);
bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, uint64_t>> key_data);
std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network = regtest);
bytes lock_script_for_redeem_script(const bytes &script);
bytes lock_script_from_pw_address(const std::string &address);
std::string get_weighted_multisig_address(const std::vector<std::pair<std::string, uint64_t>> &public_keys, bitcoin_network network = regtest);
bytes get_weighted_multisig_redeem_script(std::vector<std::pair<std::string, uint64_t>> public_keys);
std::vector<bytes> signatures_for_raw_transaction(const bytes &unsigned_tx,
std::vector<uint64_t> in_amounts,
@ -64,6 +68,13 @@ bytes add_signatures_to_unsigned_tx(const bytes &unsigned_tx,
const std::vector<std::vector<bytes>> &signatures,
const bytes &redeem_script);
void read_tx_data_from_string(const std::string &string_buf, bytes& tx, std::vector<uint64_t>& in_amounts, bytes& redeem_script);
std::string save_tx_data_to_string(const bytes& tx, const std::vector<uint64_t>& in_amounts, const bytes& redeem_script);
std::vector<bytes> read_bytes_array_from_string(const std::string &string_buf);
std::string write_bytes_array_to_string(const std::vector<bytes>& data);
struct btc_outpoint {
fc::uint256 hash;
uint32_t n;
@ -73,6 +84,19 @@ struct btc_outpoint {
};
struct btc_in {
btc_in() = default;
btc_in(const btc_in &) = default;
btc_in(btc_in &&) = default;
btc_in &operator=(const btc_in &) = default;
btc_in(const std::string &txid, uint32_t out, uint32_t sequence = 0xffffffff) {
prevout.n = out;
prevout.hash = fc::uint256(txid);
// reverse hash due to the different from_hex algo in bitcoin
std::reverse(prevout.hash.data(), prevout.hash.data() + prevout.hash.data_size());
nSequence = sequence;
}
btc_outpoint prevout;
bytes scriptSig;
uint32_t nSequence;
@ -83,6 +107,16 @@ struct btc_in {
};
struct btc_out {
btc_out() = default;
btc_out(const btc_out &) = default;
btc_out(btc_out &&) = default;
btc_out &operator=(const btc_out &) = default;
btc_out(const std::string &address, uint64_t amount) :
nValue(amount),
scriptPubKey(lock_script_from_pw_address(address)) {
}
int64_t nValue;
bytes scriptPubKey;

View file

@ -43,7 +43,7 @@ public:
std::string walletlock();
std::string walletprocesspsbt(std::string const &tx_psbt);
bool walletpassphrase(const std::string &passphrase, uint32_t timeout = 60);
uint64_t outputamount(const std::string& txid, uint32_t output_num);
private:
fc::http::reply send_post_request(std::string body, bool show_log = false);
@ -103,13 +103,18 @@ private:
fc::future<void> on_changed_objects_task;
std::string create_transaction(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs);
std::string create_multisig_address(const std::vector<std::string> &son_pubkeys_bitcoin, const std::vector<uint64_t>& son_votes);
std::string create_transaction(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs, const std::string& extra_data);
std::string sign_transaction(const sidechain_transaction_object &sto, bool &complete);
bool send_transaction(const sidechain_transaction_object &sto, std::string &sidechain_transaction);
std::string create_multisig_address_raw(const std::vector<std::string> &son_pubkeys);
std::string create_multisig_address_psbt(const std::vector<std::string> &son_pubkeys);
std::string create_multisig_address_standalone(const std::vector<std::string> &son_pubkeys, const std::vector<uint64_t>& son_votes);
std::string create_transaction_raw(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs);
std::string create_transaction_psbt(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs);
std::string create_transaction_standalone(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs);
std::string create_transaction_standalone(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs, const std::string& redeem_script);
std::string sign_transaction_raw(const sidechain_transaction_object &sto, bool &complete);
std::string sign_transaction_psbt(const sidechain_transaction_object &sto, bool &complete);
@ -117,6 +122,7 @@ private:
bool send_transaction_raw(const sidechain_transaction_object &sto, std::string &sidechain_transaction);
bool send_transaction_psbt(const sidechain_transaction_object &sto, std::string &sidechain_transaction);
bool send_transaction_standalone(const sidechain_transaction_object &sto, std::string &sidechain_transaction);
void handle_event(const std::string &event_data);
std::vector<info_for_vin> extract_info_from_block(const std::string &_block);

View file

@ -1,4 +1,5 @@
#include <graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp>
#include <graphene/peerplays_sidechain/bitcoin_utils.hpp>
#include <algorithm>
#include <thread>
@ -8,6 +9,7 @@
#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 +17,7 @@
#include <graphene/chain/protocol/son_wallet.hpp>
#include <graphene/chain/son_info.hpp>
#include <graphene/chain/son_wallet_object.hpp>
#include <graphene/utilities/key_conversion.hpp>
namespace graphene { namespace peerplays_sidechain {
@ -480,13 +483,13 @@ std::vector<btc_txout> bitcoin_rpc_client::listunspent() {
std::vector<btc_txout> bitcoin_rpc_client::listunspent_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 += std::string("0,999999,[\"");
body += address;
body += std::string("\"],true,{\"minimumAmount\":");
body += std::to_string(minimum_amount);
body += std::string("}] }");
const auto reply = send_post_request(body);
const auto reply = send_post_request(body, true);
std::vector<btc_txout> result;
if (reply.body.empty()) {
@ -703,6 +706,38 @@ bool bitcoin_rpc_client::walletpassphrase(const std::string &passphrase, uint32_
return false;
}
uint64_t bitcoin_rpc_client::outputamount(const std::string &txid, uint32_t output_num)
{
const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"peerplays_plugin\", "
"\"method\": \"getrawtransaction\", \"params\": [\"") +
txid + std::string("\", false] }");
const auto reply = send_post_request(body, true);
if (reply.body.empty()) {
wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__));
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 (reply.status == 200) {
if (json.find("result") != json.not_found()) {
auto txraw_str = json.get<std::string>("result");
bytes txraw;
txraw.resize(txraw_str.size() / 2);
fc::from_hex(txraw_str, (char*)&txraw[0], txraw.size());
btc_tx tx;
tx.fill_from_bytes(txraw);
return tx.vout[output_num].nValue;
}
}
return 0;
}
fc::http::reply bitcoin_rpc_client::send_post_request(std::string body, bool show_log) {
fc::http::connection conn;
conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), rpc_port));
@ -801,6 +836,13 @@ sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain
if (!key_pair.first.length() || !key_pair.second.length()) {
FC_THROW("Invalid public private key pair.");
}
fc::optional<fc::ecc::private_key> privkey = graphene::utilities::wif_to_key(key_pair.second);
if (!privkey)
FC_THROW("Invalid private key.");
fc::ecc::public_key derived = privkey->get_public_key();
fc::ecc::public_key_data kd = derived.serialize();
if (fc::to_hex(kd.data, kd.size()) != key_pair.first)
FC_THROW("Public key doesn't match private key.");
private_keys[key_pair.first] = key_pair.second;
}
}
@ -852,86 +894,105 @@ void sidechain_net_handler_bitcoin::recreate_primary_wallet() {
auto active_sons = gpo.active_sons;
vector<string> son_pubkeys_bitcoin;
vector<uint64_t> son_votes;
for (const son_info &si : active_sons) {
son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin));
son_votes.push_back(si.weight);
}
if (!wallet_password.empty()) {
bitcoin_client->walletpassphrase(wallet_password, 5);
std::string full_address_info = create_multisig_address(son_pubkeys_bitcoin, son_votes);
ilog("multisig address: ${addr}", ("addr", full_address_info));
if(!full_address_info.size())
{
elog("Failed to create multisig address");
return;
}
uint32_t nrequired = son_pubkeys_bitcoin.size() * 2 / 3 + 1;
string reply_str = bitcoin_client->addmultisigaddress(nrequired, son_pubkeys_bitcoin);
std::stringstream address_info_ss(full_address_info);
boost::property_tree::ptree address_info_pt;
boost::property_tree::read_json(address_info_ss, address_info_pt);
std::stringstream active_pw_ss(reply_str);
boost::property_tree::ptree active_pw_pt;
boost::property_tree::read_json(active_pw_ss, active_pw_pt);
if (active_pw_pt.count("error") && active_pw_pt.get_child("error").empty()) {
std::string address = address_info_pt.get<std::string>("address");
std::string redeem_script = address_info_pt.get<std::string>("redeemScript");
std::stringstream res;
boost::property_tree::json_parser::write_json(res, active_pw_pt.get_child("result"));
son_wallet_update_operation op;
op.payer = gpo.parameters.son_account();
op.son_wallet_id = active_sw->id;
op.sidechain = sidechain_type::bitcoin;
op.address = full_address_info;
son_wallet_update_operation op;
op.payer = gpo.parameters.son_account();
op.son_wallet_id = active_sw->id;
op.sidechain = sidechain_type::bitcoin;
op.address = res.str();
proposal_create_operation proposal_op;
proposal_op.fee_paying_account = plugin.get_current_son_object().son_account;
proposal_op.proposed_ops.emplace_back(op);
uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3;
proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime);
proposal_create_operation proposal_op;
proposal_op.fee_paying_account = plugin.get_current_son_object().son_account;
proposal_op.proposed_ops.emplace_back(op);
uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3;
proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime);
signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op);
try {
database.push_transaction(trx, database::validation_steps::skip_block_size_check);
if (plugin.app().p2p_node())
plugin.app().p2p_node()->broadcast(net::trx_message(trx));
} catch (fc::exception e) {
elog("Sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what()));
return;
}
signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op);
try {
database.push_transaction(trx, database::validation_steps::skip_block_size_check);
if (plugin.app().p2p_node())
plugin.app().p2p_node()->broadcast(net::trx_message(trx));
} catch (fc::exception e) {
elog("Sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what()));
const auto &prev_sw = std::next(active_sw);
if (prev_sw != swi.rend()) {
ilog("transfer all btc...");
std::stringstream prev_sw_ss(prev_sw->addresses.at(sidechain_type::bitcoin));
boost::property_tree::ptree prev_sw_pt;
boost::property_tree::read_json(prev_sw_ss, prev_sw_pt);
std::string active_pw_address = address;
std::string prev_pw_address = prev_sw_pt.get<std::string>("address");
std::string prev_redeem_script = prev_sw_pt.get<std::string>("redeemScript");
ilog("From ${from} to ${to}", ("from", prev_pw_address)("to", active_pw_address));
if (prev_pw_address == active_pw_address) {
elog("BTC previous and new primary wallet addresses are same. No funds moving needed [from ${prev_sw} to ${active_sw}]", ("prev_sw", prev_sw->id)("active_sw", active_sw->id));
return;
}
const auto &prev_sw = std::next(active_sw);
if (prev_sw != swi.rend()) {
std::stringstream prev_sw_ss(prev_sw->addresses.at(sidechain_type::bitcoin));
boost::property_tree::ptree prev_sw_pt;
boost::property_tree::read_json(prev_sw_ss, prev_sw_pt);
uint64_t fee_rate = bitcoin_client->estimatesmartfee();
uint64_t min_fee_rate = 1000;
fee_rate = std::max(fee_rate, min_fee_rate);
std::string active_pw_address = active_pw_pt.get_child("result").get<std::string>("address");
std::string prev_pw_address = prev_sw_pt.get<std::string>("address");
double min_amount = ((double)fee_rate / 100000000.0); // Account only for relay fee for now
double total_amount = 0.0;
std::vector<btc_txout> inputs = bitcoin_client->listunspent_by_address_and_amount(prev_pw_address, 0);
if (prev_pw_address == active_pw_address) {
elog("BTC previous and new primary wallet addresses are same. No funds moving needed [from ${prev_sw} to ${active_sw}]", ("prev_sw", prev_sw->id)("active_sw", active_sw->id));
return;
if (inputs.size() == 0) {
elog("Failed to find UTXOs to spend for ${pw}", ("pw", prev_pw_address));
return;
} else {
for (const auto &utx : inputs) {
total_amount += utx.amount_;
}
uint64_t fee_rate = bitcoin_client->estimatesmartfee();
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<btc_txout> inputs = bitcoin_client->listunspent_by_address_and_amount(prev_pw_address, 0);
if (inputs.size() == 0) {
elog("Failed to find UTXOs to spend for ${pw}", ("pw", prev_pw_address));
if (min_amount >= total_amount) {
elog("Failed not enough BTC to transfer from ${fa}, ${ma} >= ${ta}", ("fa", prev_pw_address)("ma", min_amount)("ta", total_amount));
return;
} else {
for (const auto &utx : inputs) {
total_amount += utx.amount_;
}
}
}
if (min_amount >= 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;
std::string tx_str = create_transaction(inputs, outputs, prev_redeem_script);
ilog("Tx: [${tx}]", ("tx", tx_str));
if (!tx_str.empty()) {
auto signer_sons = prev_sw->sons;
std::vector<son_id_type> signers;
for (const son_info &si : signer_sons) {
signers.push_back(si.son_id);
}
fc::flat_map<std::string, double> outputs;
outputs[active_pw_address] = total_amount - min_amount;
std::string tx_str = create_transaction(inputs, outputs);
std::string tx_str = create_transaction(inputs, outputs, "");
if (!tx_str.empty()) {
@ -1000,7 +1061,7 @@ bool sidechain_net_handler_bitcoin::process_deposit(const son_wallet_deposit_obj
outputs[pw_address] = transfer_amount;
std::string tx_str = create_transaction(inputs, outputs);
std::string tx_str = create_transaction(inputs, outputs, "");
if (!tx_str.empty()) {
const chain::global_property_object &gpo = database.get_global_properties();
@ -1076,7 +1137,7 @@ bool sidechain_net_handler_bitcoin::process_withdrawal(const son_wallet_withdraw
outputs[pw_address] = total_amount - min_amount;
}
std::string tx_str = create_transaction(inputs, outputs);
std::string tx_str = create_transaction(inputs, outputs, "");
if (!tx_str.empty()) {
const chain::global_property_object &gpo = database.get_global_properties();
@ -1135,13 +1196,22 @@ bool sidechain_net_handler_bitcoin::send_sidechain_transaction(const sidechain_t
return send_transaction(sto, sidechain_transaction);
}
std::string sidechain_net_handler_bitcoin::create_multisig_address(const std::vector<std::string> &son_pubkeys_bitcoin, const std::vector<uint64_t>& son_votes)
{
std::string new_address = "";
//new_address = create_multisig_address_raw(son_pubkeys);
//new_address = create_multisig_address_psbt(son_pubkeys_bitcoin);
new_address = create_multisig_address_standalone(son_pubkeys_bitcoin, son_votes);
return new_address;
}
// 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 sidechain_net_handler_bitcoin::create_transaction(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs, const std::string& extra_data) {
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, extra_data);
return new_tx;
}
@ -1151,15 +1221,77 @@ 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);
//new_tx = sign_transaction_standalone(sto, complete);
//new_tx = sign_transaction_psbt(sto, complete);
new_tx = sign_transaction_standalone(sto, complete);
return new_tx;
}
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);
return send_transaction_standalone(sto, sidechain_transaction);
}
std::string sidechain_net_handler_bitcoin::create_multisig_address_raw(const std::vector<std::string> &son_pubkeys)
{
if (!wallet_password.empty()) {
bitcoin_client->walletpassphrase(wallet_password, 5);
}
uint32_t nrequired = son_pubkeys.size() * 2 / 3 + 1;
string reply_str = bitcoin_client->addmultisigaddress(nrequired, son_pubkeys);
std::stringstream active_pw_ss(reply_str);
boost::property_tree::ptree active_pw_pt;
boost::property_tree::read_json(active_pw_ss, active_pw_pt);
if (!active_pw_pt.count("error") || !active_pw_pt.get_child("error").empty()) {
elog("Failed to recreate primary wallet");
return "";
}
std::stringstream res;
boost::property_tree::json_parser::write_json(res, active_pw_pt.get_child("result"));
return res.str();
}
std::string sidechain_net_handler_bitcoin::create_multisig_address_psbt(const std::vector<std::string> &son_pubkeys)
{
if (!wallet_password.empty()) {
bitcoin_client->walletpassphrase(wallet_password, 5);
}
uint32_t nrequired = son_pubkeys.size() * 2 / 3 + 1;
string reply_str = bitcoin_client->addmultisigaddress(nrequired, son_pubkeys);
std::stringstream active_pw_ss(reply_str);
boost::property_tree::ptree active_pw_pt;
boost::property_tree::read_json(active_pw_ss, active_pw_pt);
if (!active_pw_pt.count("error") || !active_pw_pt.get_child("error").empty()) {
elog("Failed to recreate primary wallet");
return "";
}
std::stringstream res;
boost::property_tree::json_parser::write_json(res, active_pw_pt.get_child("result"));
return res.str();
}
std::string sidechain_net_handler_bitcoin::create_multisig_address_standalone(const std::vector<std::string> &son_pubkeys, const std::vector<uint64_t> &son_votes)
{
FC_ASSERT(son_pubkeys.size() == son_votes.size());
vector<pair<std::string, uint64_t>> son_data;
for(unsigned idx = 0; idx < son_pubkeys.size(); ++idx)
son_data.push_back(make_pair(son_pubkeys[idx], son_votes[idx]));
ilog("keys data123: ${kd}", ("kd", son_data));
std::string address = get_weighted_multisig_address(son_data);
bytes redeem_script = get_weighted_multisig_redeem_script(son_data);
return "{\"address\":\"" + address + "\",\"redeemScript\":\"" +
fc::to_hex((char*)&redeem_script[0], redeem_script.size()) +
"\"}";
}
std::string sidechain_net_handler_bitcoin::create_transaction_raw(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs) {
@ -1170,65 +1302,25 @@ std::string sidechain_net_handler_bitcoin::create_transaction_psbt(const std::ve
return bitcoin_client->createpsbt(inputs, outputs);
}
std::string sidechain_net_handler_bitcoin::create_transaction_standalone(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs) {
// Examples
// Transaction with no inputs and outputs
//bitcoin-core.cli -rpcuser=1 -rpcpassword=1 -rpcwallet="" createrawtransaction '[]' '[]'
//02000000000000000000
//bitcoin-core.cli -rpcuser=1 -rpcpassword=1 -rpcwallet="" decoderawtransaction 02000000000000000000
//{
// "txid": "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a",
// "hash": "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a",
// "version": 2,
// "size": 10,
// "vsize": 10,
// "weight": 40,
// "locktime": 0,
// "vin": [
// ],
// "vout": [
// ]
//}
// Transaction with input and output
//{
// "txid": "ff60f48f767bbf70d79efc1347b5554b481f14fda68709839091286e035e669b",
// "hash": "ff60f48f767bbf70d79efc1347b5554b481f14fda68709839091286e035e669b",
// "version": 2,
// "size": 83,
// "vsize": 83,
// "weight": 332,
// "locktime": 0,
// "vin": [
// {
// "txid": "3d322dc2640239a2e68e182b254d19c88e5172a61947f94a105c3f57618092ff",
// "vout": 0,
// "scriptSig": {
// "asm": "",
// "hex": ""
// },
// "sequence": 4294967295
// }
// ],
// "vout": [
// {
// "value": 1.00000000,
// "n": 0,
// "scriptPubKey": {
// "asm": "OP_HASH160 b87c323018cae236eb03a1f63000c85b672270f6 OP_EQUAL",
// "hex": "a914b87c323018cae236eb03a1f63000c85b672270f687",
// "reqSigs": 1,
// "type": "scripthash",
// "addresses": [
// "2NA4h6sc9oZ4ogfNKU9Wp6fkqPZLZPqqpgf"
// ]
// }
// }
// ]
//}
return "";
std::string sidechain_net_handler_bitcoin::create_transaction_standalone(const std::vector<btc_txout> &inputs, const fc::flat_map<std::string, double> outputs, const std::string& redeem_script) {
btc_tx tx;
tx.nVersion = 2;
tx.nLockTime = 0;
tx.hasWitness = true;
vector<uint64_t> in_amounts;
for(const auto& in: inputs)
{
tx.vin.push_back(btc_in(in.txid_, in.out_num_));
in_amounts.push_back(bitcoin_client->outputamount(in.txid_, in.out_num_));
}
for(const auto& out: outputs)
tx.vout.push_back(btc_out(out.first, out.second * 100000000.0));
bytes buf;
tx.to_bytes(buf);
bytes redeem_script_bin;
redeem_script_bin.resize(redeem_script.size() / 2);
fc::from_hex(redeem_script, (char*)&redeem_script_bin[0], redeem_script_bin.size());
return save_tx_data_to_string(buf, in_amounts, redeem_script_bin);
}
std::string sidechain_net_handler_bitcoin::sign_transaction_raw(const sidechain_transaction_object &sto, bool &complete) {
@ -1328,10 +1420,37 @@ 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;
if(complete)
return sto.transaction;
complete = true;
return "";
uint64_t total_votes = 0;
uint64_t already_signed = 0;
for (unsigned idx = 0; idx < sto.signatures.size(); ++idx) {
son_object son = plugin.get_son_object(sto.signatures[idx].first);
total_votes += son.total_votes;
if(!sto.signatures[idx].second.empty())
already_signed += son.total_votes;
}
set<son_id_type> owned_sons = plugin.get_sons();
bytes unsigned_tx;
vector<uint64_t> in_amounts;
bytes redeem_script;
read_tx_data_from_string(sto.transaction, unsigned_tx, in_amounts, redeem_script);
son_object active_son = plugin.get_current_son_object();
vector<bytes> signatures;
for (unsigned idx = 0; idx < sto.signatures.size(); ++idx) {
if(!sto.signatures[idx].second.empty())
continue;
if(sto.signatures[idx].first != active_son.id)
continue;
std::string btc_public_key = active_son.sidechain_public_keys[sidechain_type::bitcoin];
fc::optional<fc::ecc::private_key> btc_private_key = graphene::utilities::wif_to_key(private_keys[btc_public_key]);
already_signed += active_son.total_votes;
signatures = signatures_for_raw_transaction(unsigned_tx, in_amounts, redeem_script, *btc_private_key);
}
complete = (3 * already_signed > 2 * total_votes);
return write_bytes_array_to_string(signatures);
}
bool sidechain_net_handler_bitcoin::send_transaction_raw(const sidechain_transaction_object &sto, std::string &sidechain_transaction) {
@ -1382,7 +1501,29 @@ 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 = "";
bytes unsigned_tx;
vector<uint64_t> in_amounts;
bytes redeem_script;
read_tx_data_from_string(sto.transaction, unsigned_tx, in_amounts, redeem_script);
uint32_t inputs_number = in_amounts.size();
vector<bytes> dummy;
dummy.resize(inputs_number);
vector<vector<bytes>> signatures;
for (unsigned idx = 0; idx < sto.signatures.size(); ++idx) {
if(sto.signatures[idx].second.empty())
signatures.push_back(dummy);
else
signatures.push_back(read_bytes_array_from_string(sto.signatures[idx].second));
}
bytes signed_tx = add_signatures_to_unsigned_tx(unsigned_tx, signatures, redeem_script);
return bitcoin_client->sendrawtransaction(fc::to_hex((char*)&signed_tx[0], signed_tx.size()));
}
void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) {
std::string block = bitcoin_client->getblock(event_data);

View file

@ -333,7 +333,7 @@
"scale": 10000
},
"block_interval": 3,
"maintenance_interval": 86400,
"maintenance_interval": 120,
"maintenance_skip_slots": 3,
"committee_proposal_review_period": 1209600,
"maximum_transaction_size": 2048,

File diff suppressed because one or more lines are too long