Compare commits
34 commits
master
...
feature/SO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efe7a0c4ae | ||
|
|
cf36f00c25 | ||
|
|
eb59bb40b5 | ||
|
|
00a453fd77 | ||
|
|
38831b4742 | ||
|
|
a1adcc8507 | ||
|
|
2528c349d4 | ||
|
|
06a9b1f75e | ||
|
|
819e2e7ee4 | ||
|
|
5231f36976 | ||
|
|
e28a19a9d3 | ||
|
|
3c5a985f92 | ||
|
|
43bc782469 | ||
|
|
4736e7f883 | ||
|
|
b4575fb32e | ||
|
|
f457ae9725 | ||
|
|
2b6c700644 | ||
|
|
b1fbd26bcf | ||
|
|
471738e851 | ||
|
|
59d98db82a | ||
|
|
6eb4f993bb | ||
|
|
f2a38419ce | ||
|
|
ed19ccee25 | ||
|
|
ab9a7f2fca | ||
|
|
682f64c8fc | ||
|
|
d710b320c0 | ||
|
|
a06af5bf17 | ||
|
|
610426836b | ||
|
|
2f03465991 | ||
|
|
d93f5e66b1 | ||
|
|
f3e8dabaa4 | ||
|
|
6a6829a2c2 | ||
|
|
6b47ed4a8c | ||
|
|
0d1adde0a6 |
20 changed files with 2301 additions and 188 deletions
|
|
@ -12,7 +12,9 @@ namespace graphene { namespace chain {
|
|||
asset fee;
|
||||
account_id_type payer;
|
||||
|
||||
// TODO: BTC Transaction Structs go here
|
||||
peerplays_sidechain::bytes unsigned_tx;
|
||||
peerplays_sidechain::bytes redeem_script;
|
||||
std::vector<uint64_t> in_amounts;
|
||||
fc::flat_map<son_id_type, std::vector<peerplays_sidechain::bytes>> signatures;
|
||||
|
||||
account_id_type fee_payer()const { return payer; }
|
||||
|
|
@ -51,10 +53,10 @@ namespace graphene { namespace chain {
|
|||
} } // 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_send_operation, (fee)(payer)(unsigned_tx)(redeem_script)(in_amounts)(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) )
|
||||
FC_REFLECT( graphene::chain::bitcoin_send_transaction_process_operation, (fee)(payer)(bitcoin_transaction_id) )
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public:
|
|||
|
||||
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 );
|
||||
void update_proposal(const bitcoin_transaction_sign_operation& o);
|
||||
};
|
||||
|
||||
class bitcoin_send_transaction_process_evaluator : public evaluator<bitcoin_send_transaction_process_evaluator>
|
||||
|
|
@ -32,4 +32,4 @@ public:
|
|||
object_id_type do_apply(const bitcoin_send_transaction_process_operation& o);
|
||||
};
|
||||
|
||||
} } // namespace graphene::chain
|
||||
} } // namespace graphene::chain
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ namespace graphene { namespace chain {
|
|||
static const uint8_t type_id = bitcoin_transaction_object_type;
|
||||
// Bitcoin structs go here.
|
||||
bool processed = false;
|
||||
peerplays_sidechain::bytes unsigned_tx;
|
||||
peerplays_sidechain::bytes redeem_script;
|
||||
std::vector<uint64_t> in_amounts;
|
||||
fc::flat_map<son_id_type, std::vector<peerplays_sidechain::bytes>> signatures;
|
||||
};
|
||||
|
||||
|
|
@ -36,4 +39,4 @@ namespace graphene { namespace chain {
|
|||
} } // graphene::chain
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::bitcoin_transaction_object, (graphene::db::object),
|
||||
(processed)(signatures) )
|
||||
(processed)(unsigned_tx)(redeem_script)(in_amounts)(signatures) )
|
||||
|
|
|
|||
|
|
@ -26,8 +26,23 @@ object_id_type bitcoin_transaction_send_evaluator::do_apply(const bitcoin_transa
|
|||
{
|
||||
try
|
||||
{
|
||||
const auto &new_bitcoin_transaction_object = db().create<bitcoin_transaction_object>([&](bitcoin_transaction_object &obj) {
|
||||
database& database = db();
|
||||
// count initial signatures for a statistics
|
||||
for(const auto& p: op.signatures)
|
||||
{
|
||||
if(p.second.empty())
|
||||
continue;
|
||||
son_object so = p.first(database);
|
||||
database.modify( so.statistics(database), [&]( son_statistics_object& sso ) {
|
||||
sso.txs_signed += 1;
|
||||
} );
|
||||
}
|
||||
// create bitcoin transaction object
|
||||
const auto &new_bitcoin_transaction_object = database.create<bitcoin_transaction_object>([&](bitcoin_transaction_object &obj) {
|
||||
obj.processed = false;
|
||||
obj.unsigned_tx = op.unsigned_tx;
|
||||
obj.redeem_script = op.redeem_script;
|
||||
obj.in_amounts = op.in_amounts;
|
||||
obj.signatures = op.signatures;
|
||||
});
|
||||
return new_bitcoin_transaction_object.id;
|
||||
|
|
@ -44,18 +59,20 @@ void_result bitcoin_transaction_sign_evaluator::do_evaluate(const bitcoin_transa
|
|||
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<son_index>().indices().get<graphene::chain::by_account>();
|
||||
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;
|
||||
};
|
||||
const auto &sidx = db().get_index_type<son_index>().indices().get<graphene::chain::by_account>();
|
||||
auto son_obj = sidx.find(op.payer);
|
||||
FC_ASSERT(son_obj != sidx.end(), "Unknown SON as payer");
|
||||
// Get bitcoin tx from proposal
|
||||
FC_ASSERT(proposal_itr->proposed_transaction.operations.size() == 1, "Invalid proposal");
|
||||
auto op = proposal_itr->proposed_transaction.operations[0];
|
||||
FC_ASSERT(op.which() == chain::operation::tag<chain::bitcoin_transaction_send_operation>::value, "Invalid proposal");
|
||||
bitcoin_transaction_send_operation btx_op = op.get<bitcoin_transaction_send_operation>();
|
||||
// Find the SON among the tx signers
|
||||
auto it = btx_op.signatures.find(son_obj->id);
|
||||
FC_ASSERT(it != btx_op.signatures.end(), "SON is not tx signer");
|
||||
// tx is not signed with this son already
|
||||
FC_ASSERT(it->second.empty(), "This SON has already signed this tx");
|
||||
|
||||
FC_ASSERT(can_this_son_approve_this_proposal(), "Invalid approval received");
|
||||
return void_result();
|
||||
}
|
||||
FC_CAPTURE_AND_RETHROW((op))
|
||||
|
|
@ -80,6 +97,7 @@ object_id_type bitcoin_transaction_sign_evaluator::do_apply(const bitcoin_transa
|
|||
} );
|
||||
|
||||
update_proposal(op);
|
||||
return op.proposal_id;
|
||||
}
|
||||
FC_CAPTURE_AND_RETHROW((op))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#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>
|
||||
|
|
@ -351,7 +352,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,7 +367,7 @@ 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);
|
||||
return result;
|
||||
|
|
@ -375,6 +376,17 @@ bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, int>> ke
|
|||
/** 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 +421,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 +448,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 +469,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 +526,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 +551,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 +761,26 @@ 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);
|
||||
}
|
||||
|
||||
}} // namespace graphene::peerplays_sidechain
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -73,6 +77,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 +100,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;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ public:
|
|||
virtual void recreate_primary_wallet() = 0;
|
||||
virtual void process_deposit(const son_wallet_deposit_object &swdo) = 0;
|
||||
virtual void process_withdrawal(const son_wallet_withdraw_object &swwo) = 0;
|
||||
virtual void process_signing() = 0;
|
||||
virtual void complete_signing() = 0;
|
||||
|
||||
protected:
|
||||
peerplays_sidechain_plugin &plugin;
|
||||
|
|
@ -35,11 +37,6 @@ protected:
|
|||
|
||||
std::map<std::string, std::string> private_keys;
|
||||
|
||||
virtual std::string create_multisignature_wallet(const std::vector<std::string> public_keys) = 0;
|
||||
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;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ public:
|
|||
class bitcoin_rpc_client {
|
||||
public:
|
||||
bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::string _user, std::string _password, std::string _wallet, std::string _wallet_password);
|
||||
bool connection_is_not_defined() const;
|
||||
|
||||
std::string addmultisigaddress(const std::vector<std::string> public_keys);
|
||||
std::string createrawtransaction(const std::vector<btc_txout> &ins, const fc::flat_map<std::string, double> outs);
|
||||
|
|
@ -59,9 +58,6 @@ private:
|
|||
class zmq_listener {
|
||||
public:
|
||||
zmq_listener(std::string _ip, uint32_t _zmq);
|
||||
bool connection_is_not_defined() const {
|
||||
return zmq_port == 0;
|
||||
}
|
||||
|
||||
fc::signal<void(const std::string &)> event_received;
|
||||
|
||||
|
|
@ -83,9 +79,11 @@ public:
|
|||
sidechain_net_handler_bitcoin(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options);
|
||||
virtual ~sidechain_net_handler_bitcoin();
|
||||
|
||||
void recreate_primary_wallet();
|
||||
void process_deposit(const son_wallet_deposit_object &swdo);
|
||||
void process_withdrawal(const son_wallet_withdraw_object &swwo);
|
||||
void recreate_primary_wallet() override;
|
||||
void process_deposit(const son_wallet_deposit_object &swdo) override;
|
||||
void process_withdrawal(const son_wallet_withdraw_object &swwo) override;
|
||||
void process_signing() override;
|
||||
void complete_signing() override;
|
||||
|
||||
private:
|
||||
std::string ip;
|
||||
|
|
@ -99,14 +97,11 @@ private:
|
|||
std::unique_ptr<bitcoin_rpc_client> bitcoin_client;
|
||||
std::unique_ptr<zmq_listener> listener;
|
||||
|
||||
std::string create_multisignature_wallet(const std::vector<std::string> public_keys);
|
||||
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);
|
||||
void transfer_all_btc(const std::string &from_address, const vector<son_info> &from_sons, const std::string &to_address);
|
||||
std::string transfer_deposit_to_primary_wallet(const son_wallet_deposit_object &swdo);
|
||||
std::string transfer_withdrawal_from_primary_wallet(const son_wallet_withdraw_object &swwo);
|
||||
void publish_btc_tx(const bitcoin_transaction_object &tx_object);
|
||||
|
||||
void handle_event(const std::string &event_data);
|
||||
std::vector<info_for_vin> extract_info_from_block(const std::string &_block);
|
||||
|
|
|
|||
|
|
@ -13,16 +13,13 @@ public:
|
|||
sidechain_net_handler_peerplays(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options);
|
||||
virtual ~sidechain_net_handler_peerplays();
|
||||
|
||||
void recreate_primary_wallet();
|
||||
void process_deposit(const son_wallet_deposit_object &swdo);
|
||||
void process_withdrawal(const son_wallet_withdraw_object &swwo);
|
||||
void recreate_primary_wallet() override;
|
||||
void process_deposit(const son_wallet_deposit_object &swdo) override;
|
||||
void process_withdrawal(const son_wallet_withdraw_object &swwo) override;
|
||||
void process_signing() override;
|
||||
void complete_signing() override;
|
||||
|
||||
private:
|
||||
std::string create_multisignature_wallet(const std::vector<std::string> public_keys);
|
||||
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);
|
||||
|
||||
void on_applied_block(const signed_block &b);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ public:
|
|||
void recreate_primary_wallet();
|
||||
void process_deposits();
|
||||
void process_withdrawals();
|
||||
void process_signing();
|
||||
void complete_signing();
|
||||
|
||||
private:
|
||||
peerplays_sidechain_plugin &plugin;
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@
|
|||
#include <graphene/chain/proposal_object.hpp>
|
||||
#include <graphene/chain/protocol/transfer.hpp>
|
||||
#include <graphene/chain/sidechain_address_object.hpp>
|
||||
#include <graphene/chain/sidechain_transaction_object.hpp>
|
||||
#include <graphene/chain/son_wallet_object.hpp>
|
||||
#include <graphene/chain/son_wallet_withdraw_object.hpp>
|
||||
#include <graphene/peerplays_sidechain/bitcoin_utils.hpp>
|
||||
#include <graphene/peerplays_sidechain/sidechain_net_manager.hpp>
|
||||
#include <graphene/utilities/key_conversion.hpp>
|
||||
|
||||
|
|
@ -49,6 +51,8 @@ public:
|
|||
void recreate_primary_wallet();
|
||||
void process_deposits();
|
||||
void process_withdrawals();
|
||||
void process_signing();
|
||||
void complete_signing();
|
||||
|
||||
private:
|
||||
peerplays_sidechain_plugin &plugin;
|
||||
|
|
@ -285,7 +289,7 @@ void peerplays_sidechain_plugin_impl::heartbeat_loop() {
|
|||
for (son_id_type son_id : sons) {
|
||||
if (is_active_son(son_id) || get_son_object(son_id).status == chain::son_status::in_maintenance) {
|
||||
|
||||
ilog("peerplays_sidechain_plugin: sending heartbeat for SON ${son}", ("son", son_id));
|
||||
ilog("sending heartbeat for SON ${son}", ("son", son_id));
|
||||
chain::son_heartbeat_operation op;
|
||||
op.owner_account = get_son_object(son_id).son_account;
|
||||
op.son_id = son_id;
|
||||
|
|
@ -298,7 +302,7 @@ void peerplays_sidechain_plugin_impl::heartbeat_loop() {
|
|||
plugin.app().p2p_node()->broadcast(net::trx_message(trx));
|
||||
return true;
|
||||
} catch (fc::exception e) {
|
||||
ilog("peerplays_sidechain_plugin_impl: sending heartbeat failed with exception ${e}", ("e", e.what()));
|
||||
ilog("sending heartbeat failed with exception ${e}", ("e", e.what()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
|
@ -325,11 +329,12 @@ void peerplays_sidechain_plugin_impl::son_processing() {
|
|||
}
|
||||
|
||||
chain::son_id_type next_son_id = plugin.database().get_scheduled_son(1);
|
||||
ilog("peerplays_sidechain_plugin_impl: Scheduled SON ${son}", ("son", next_son_id));
|
||||
ilog("Scheduled SON ${son}", ("son", next_son_id));
|
||||
|
||||
// Tasks that are executed by all active SONs, no matter if scheduled
|
||||
// E.g. sending approvals and signing
|
||||
approve_proposals();
|
||||
process_signing();
|
||||
|
||||
// Tasks that are executed by scheduled and active SON
|
||||
if (sons.find(next_son_id) != sons.end()) {
|
||||
|
|
@ -345,6 +350,8 @@ void peerplays_sidechain_plugin_impl::son_processing() {
|
|||
process_deposits();
|
||||
|
||||
process_withdrawals();
|
||||
|
||||
complete_signing();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -364,7 +371,7 @@ void peerplays_sidechain_plugin_impl::approve_proposals() {
|
|||
plugin.app().p2p_node()->broadcast(net::trx_message(trx));
|
||||
return true;
|
||||
} catch (fc::exception e) {
|
||||
ilog("peerplays_sidechain_plugin_impl: sending approval failed with exception ${e}", ("e", e.what()));
|
||||
ilog("sending approval failed with exception ${e}", ("e", e.what()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
|
@ -428,6 +435,11 @@ void peerplays_sidechain_plugin_impl::approve_proposals() {
|
|||
approve_proposal(son_id, proposal.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (proposal.proposed_transaction.operations.size() == 1 && proposal.proposed_transaction.operations[0].which() == chain::operation::tag<chain::bitcoin_send_transaction_process_operation>::value) {
|
||||
approve_proposal(son_id, proposal.id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -477,7 +489,7 @@ void peerplays_sidechain_plugin_impl::create_son_down_proposals() {
|
|||
plugin.app().p2p_node()->broadcast(net::trx_message(trx));
|
||||
return true;
|
||||
} catch (fc::exception e) {
|
||||
ilog("peerplays_sidechain_plugin_impl: sending son down proposal failed with exception ${e}", ("e", e.what()));
|
||||
ilog("sending son down proposal failed with exception ${e}", ("e", e.what()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
|
@ -511,7 +523,7 @@ void peerplays_sidechain_plugin_impl::create_son_deregister_proposals() {
|
|||
plugin.app().p2p_node()->broadcast(net::trx_message(trx));
|
||||
return true;
|
||||
} catch (fc::exception e) {
|
||||
ilog("peerplays_sidechain_plugin_impl: sending son dereg proposal failed with exception ${e}", ("e", e.what()));
|
||||
ilog("sending son dereg proposal failed with exception ${e}", ("e", e.what()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
|
@ -534,6 +546,14 @@ void peerplays_sidechain_plugin_impl::process_withdrawals() {
|
|||
net_manager->process_withdrawals();
|
||||
}
|
||||
|
||||
void peerplays_sidechain_plugin_impl::process_signing() {
|
||||
net_manager->process_signing();
|
||||
}
|
||||
|
||||
void peerplays_sidechain_plugin_impl::complete_signing() {
|
||||
net_manager->complete_signing();
|
||||
}
|
||||
|
||||
void peerplays_sidechain_plugin_impl::on_applied_block(const signed_block &b) {
|
||||
schedule_son_processing();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include <graphene/peerplays_sidechain/bitcoin_utils.hpp>
|
||||
#include <graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
|
@ -8,15 +9,20 @@
|
|||
#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>
|
||||
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/proposal_object.hpp>
|
||||
#include <graphene/chain/protocol/son_wallet.hpp>
|
||||
#include <graphene/chain/sidechain_address_object.hpp>
|
||||
#include <graphene/chain/sidechain_transaction_object.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 {
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -32,10 +38,6 @@ bitcoin_rpc_client::bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::stri
|
|||
authorization.val = "Basic " + fc::base64_encode(user + ":" + password);
|
||||
}
|
||||
|
||||
bool bitcoin_rpc_client::connection_is_not_defined() const {
|
||||
return ip.empty() || rpc_port == 0 || user.empty() || password.empty();
|
||||
}
|
||||
|
||||
std::string bitcoin_rpc_client::addmultisigaddress(const std::vector<std::string> public_keys) {
|
||||
std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"addmultisigaddress\", "
|
||||
"\"method\": \"addmultisigaddress\", \"params\": [");
|
||||
|
|
@ -283,7 +285,7 @@ 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);
|
||||
|
|
@ -549,7 +551,7 @@ void zmq_listener::handle_zmq() {
|
|||
const auto header = std::string(static_cast<char *>(msg[0].data()), msg[0].size());
|
||||
const auto block_hash = boost::algorithm::hex(std::string(static_cast<char *>(msg[1].data()), msg[1].size()));
|
||||
event_received(block_hash);
|
||||
} catch (zmq::error_t& e) {
|
||||
} catch (zmq::error_t &e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -619,53 +621,47 @@ void sidechain_net_handler_bitcoin::recreate_primary_wallet() {
|
|||
const chain::global_property_object &gpo = database.get_global_properties();
|
||||
|
||||
auto active_sons = gpo.active_sons;
|
||||
vector<string> son_pubkeys_bitcoin;
|
||||
vector<pair<string, uint64_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.total_votes));
|
||||
}
|
||||
string reply_str = create_multisignature_wallet(son_pubkeys_bitcoin);
|
||||
|
||||
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()) {
|
||||
string address = get_weighted_multisig_address(son_pubkeys_bitcoin);
|
||||
bitcoin_client->importaddress(address);
|
||||
|
||||
std::stringstream res;
|
||||
boost::property_tree::json_parser::write_json(res, active_pw_pt.get_child("result"));
|
||||
ilog(address);
|
||||
|
||||
son_wallet_update_operation op;
|
||||
op.payer = GRAPHENE_SON_ACCOUNT;
|
||||
op.son_wallet_id = (*active_sw).id;
|
||||
op.sidechain = sidechain_type::bitcoin;
|
||||
op.address = res.str();
|
||||
son_wallet_update_operation op;
|
||||
op.payer = GRAPHENE_SON_ACCOUNT;
|
||||
op.son_wallet_id = (*active_sw).id;
|
||||
op.sidechain = sidechain_type::bitcoin;
|
||||
op.address = address;
|
||||
|
||||
proposal_create_operation proposal_op;
|
||||
proposal_op.fee_paying_account = plugin.get_son_object(plugin.get_current_son_id()).son_account;
|
||||
proposal_op.proposed_ops.emplace_back(op_wrapper(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_son_object(plugin.get_current_son_id()).son_account;
|
||||
proposal_op.proposed_ops.emplace_back(op_wrapper(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) {
|
||||
ilog("sidechain_net_handler: 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) {
|
||||
ilog("sidechain_net_handler: sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what()));
|
||||
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);
|
||||
|
||||
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");
|
||||
|
||||
transfer_all_btc(prev_pw_address, active_pw_address);
|
||||
}
|
||||
const auto &prev_sw = std::next(active_sw);
|
||||
if (prev_sw != swi.rend()) {
|
||||
std::string prev_pw_address = prev_sw->addresses.at(sidechain_type::bitcoin);
|
||||
std::string active_pw_address = address;
|
||||
vector<son_info> sons = prev_sw->sons;
|
||||
transfer_all_btc(prev_pw_address, sons, active_pw_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -679,40 +675,93 @@ void sidechain_net_handler_bitcoin::process_withdrawal(const son_wallet_withdraw
|
|||
transfer_withdrawal_from_primary_wallet(swwo);
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_bitcoin::create_multisignature_wallet(const std::vector<std::string> public_keys) {
|
||||
return bitcoin_client->addmultisigaddress(public_keys);
|
||||
static bool has_enough_signatures(const bitcoin_transaction_object &tx_object) {
|
||||
// TODO: Verify with weights calculation
|
||||
bool has_empty = false;
|
||||
for (auto s : tx_object.signatures)
|
||||
has_empty |= s.second.empty();
|
||||
return !has_empty;
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_bitcoin::transfer(const std::string &from, const std::string &to, const uint64_t amount) {
|
||||
return "";
|
||||
void sidechain_net_handler_bitcoin::process_signing() {
|
||||
const auto &idx = plugin.database().get_index_type<proposal_index>().indices().get<by_id>();
|
||||
vector<proposal_id_type> proposals;
|
||||
for (const auto &proposal : idx) {
|
||||
if (proposal.proposed_transaction.operations.size() != 1)
|
||||
continue;
|
||||
if (proposal.proposed_transaction.operations[0].which() != chain::operation::tag<chain::bitcoin_transaction_send_operation>::value) {
|
||||
continue;
|
||||
}
|
||||
bitcoin_transaction_send_operation tx_object = proposal.proposed_transaction.operations[0].get<bitcoin_transaction_send_operation>();
|
||||
// collect signatures
|
||||
auto sons = plugin.get_sons();
|
||||
for (son_id_type son_id : sons) {
|
||||
auto it = tx_object.signatures.find(son_id);
|
||||
if (it == tx_object.signatures.end())
|
||||
continue;
|
||||
if (it->second.empty()) {
|
||||
bitcoin_transaction_sign_operation op;
|
||||
son_object s_obj = plugin.get_son_object(son_id);
|
||||
op.payer = s_obj.son_account;
|
||||
op.proposal_id = proposal.id;
|
||||
fc::ecc::private_key k = plugin.get_private_key(son_id);
|
||||
op.signatures = signatures_for_raw_transaction(tx_object.unsigned_tx, tx_object.in_amounts, tx_object.redeem_script, k);
|
||||
|
||||
signed_transaction trx = plugin.database().create_signed_transaction(k, op);
|
||||
try {
|
||||
plugin.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) {
|
||||
ilog("sidechain_net_handler: bitcoin transaction signing failed with exception ${e}", ("e", e.what()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_bitcoin::sign_transaction(const std::string &transaction) {
|
||||
return "";
|
||||
}
|
||||
void sidechain_net_handler_bitcoin::complete_signing() {
|
||||
const auto &idx = plugin.database().get_index_type<bitcoin_transaction_index>().indices().get<by_processed>();
|
||||
const auto &idx_range = idx.equal_range(false);
|
||||
std::for_each(idx_range.first, idx_range.second,
|
||||
[&](const bitcoin_transaction_object &tx_object) {
|
||||
// check if all signatures collected
|
||||
if (has_enough_signatures(tx_object)) {
|
||||
publish_btc_tx(tx_object);
|
||||
bitcoin_send_transaction_process_operation op;
|
||||
op.payer = GRAPHENE_SON_ACCOUNT;
|
||||
op.bitcoin_transaction_id = tx_object.id;
|
||||
|
||||
std::string sidechain_net_handler_bitcoin::send_transaction(const std::string &transaction) {
|
||||
return "";
|
||||
const chain::global_property_object &gpo = database.get_global_properties();
|
||||
proposal_create_operation proposal_op;
|
||||
proposal_op.fee_paying_account = plugin.get_son_object(plugin.get_current_son_id()).son_account;
|
||||
proposal_op.proposed_ops.emplace_back(op_wrapper(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) {
|
||||
ilog("sidechain_net_handler_bitcoin: sending proposal for bitcoin send operation failed with exception ${e}", ("e", e.what()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_bitcoin::sign_and_send_transaction_with_wallet(const std::string &tx_json) {
|
||||
std::string reply_str = tx_json;
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
if (!wallet_password.empty()) {
|
||||
bitcoin_client->walletpassphrase(wallet_password, 60);
|
||||
}
|
||||
|
||||
std::string unsigned_tx_hex = pt.get<std::string>("result");
|
||||
std::string unsigned_tx_hex = tx_json;
|
||||
|
||||
reply_str = bitcoin_client->signrawtransactionwithwallet(unsigned_tx_hex);
|
||||
std::string reply_str = bitcoin_client->signrawtransactionwithwallet(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);
|
||||
|
|
@ -732,7 +781,7 @@ std::string sidechain_net_handler_bitcoin::sign_and_send_transaction_with_wallet
|
|||
return reply_str;
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_bitcoin::transfer_all_btc(const std::string &from_address, const std::string &to_address) {
|
||||
void sidechain_net_handler_bitcoin::transfer_all_btc(const std::string &from_address, const vector<son_info> &from_sons, const std::string &to_address) {
|
||||
uint64_t fee_rate = bitcoin_client->estimatesmartfee();
|
||||
uint64_t min_fee_rate = 1000;
|
||||
fee_rate = std::max(fee_rate, min_fee_rate);
|
||||
|
|
@ -743,7 +792,7 @@ std::string sidechain_net_handler_bitcoin::transfer_all_btc(const std::string &f
|
|||
|
||||
if (unspent_utxo.size() == 0) {
|
||||
wlog("Failed to find UTXOs to spend for ${pw}", ("pw", from_address));
|
||||
return "";
|
||||
return;
|
||||
} else {
|
||||
for (const auto &utx : unspent_utxo) {
|
||||
total_amount += utx.amount_;
|
||||
|
|
@ -751,15 +800,69 @@ std::string sidechain_net_handler_bitcoin::transfer_all_btc(const std::string &f
|
|||
|
||||
if (min_amount >= total_amount) {
|
||||
wlog("Failed not enough BTC to transfer from ${fa}", ("fa", from_address));
|
||||
return "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fc::flat_map<std::string, double> outs;
|
||||
outs[to_address] = total_amount - min_amount;
|
||||
btc_tx tx;
|
||||
tx.hasWitness = true;
|
||||
tx.nVersion = 2;
|
||||
tx.nLockTime = 0;
|
||||
std::vector<uint64_t> amounts;
|
||||
for (const auto &utx : unspent_utxo) {
|
||||
tx.vin.push_back(btc_in(utx.txid_, utx.out_num_));
|
||||
amounts.push_back(uint64_t(utx.amount_ * 100000000.0));
|
||||
}
|
||||
tx.vout.push_back(btc_out(to_address, uint64_t((total_amount - min_amount) * 100000000.0)));
|
||||
|
||||
std::string reply_str = bitcoin_client->createrawtransaction(unspent_utxo, outs);
|
||||
return sign_and_send_transaction_with_wallet(reply_str);
|
||||
std::vector<std::pair<std::string, uint64_t>> key_data;
|
||||
for (auto si : from_sons) {
|
||||
std::string pk = si.sidechain_public_keys.at(sidechain_type::bitcoin);
|
||||
key_data.push_back(std::make_pair(pk, si.total_votes));
|
||||
}
|
||||
std::sort(key_data.begin(), key_data.end(),
|
||||
[](std::pair<std::string, uint64_t> p1, std::pair<std::string, uint64_t> p2) {
|
||||
return (p1.second > p2.second);
|
||||
});
|
||||
bytes from_redeem_script = get_weighted_multisig_redeem_script(key_data);
|
||||
|
||||
bitcoin_transaction_send_operation op;
|
||||
op.payer = GRAPHENE_SON_ACCOUNT;
|
||||
op.redeem_script = from_redeem_script;
|
||||
op.in_amounts = amounts;
|
||||
tx.to_bytes(op.unsigned_tx);
|
||||
// add signatures
|
||||
std::set<son_id_type> plugin_sons = plugin.get_sons();
|
||||
for (auto si : from_sons) {
|
||||
if (plugin_sons.find(si.son_id) != plugin_sons.end()) {
|
||||
std::string son_btc_pub_key = si.sidechain_public_keys[peerplays_sidechain::sidechain_type::bitcoin];
|
||||
std::string str_priv_key = get_private_key(son_btc_pub_key);
|
||||
fc::optional<fc::ecc::private_key> k = graphene::utilities::wif_to_key(str_priv_key);
|
||||
if (!k)
|
||||
FC_THROW("Invalid bitcoi private key");
|
||||
std::vector<bytes> signatures = signatures_for_raw_transaction(op.unsigned_tx, amounts, from_redeem_script, *k);
|
||||
op.signatures[si.son_id] = signatures;
|
||||
} else {
|
||||
op.signatures[si.son_id];
|
||||
}
|
||||
}
|
||||
|
||||
const chain::global_property_object &gpo = database.get_global_properties();
|
||||
proposal_create_operation proposal_op;
|
||||
proposal_op.fee_paying_account = plugin.get_son_object(plugin.get_current_son_id()).son_account;
|
||||
proposal_op.proposed_ops.emplace_back(op_wrapper(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) {
|
||||
ilog("sidechain_net_handler: sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_bitcoin::transfer_deposit_to_primary_wallet(const son_wallet_deposit_object &swdo) {
|
||||
|
|
@ -769,13 +872,7 @@ std::string sidechain_net_handler_bitcoin::transfer_deposit_to_primary_wallet(co
|
|||
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<std::string>("address");
|
||||
std::string pw_address = obj->addresses.find(sidechain_type::bitcoin)->second;
|
||||
|
||||
std::string txid = swdo.sidechain_transaction_id;
|
||||
std::string suid = swdo.sidechain_uid;
|
||||
|
|
@ -785,20 +882,18 @@ std::string sidechain_net_handler_bitcoin::transfer_deposit_to_primary_wallet(co
|
|||
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<btc_txout> ins;
|
||||
fc::flat_map<std::string, double> outs;
|
||||
btc_tx tx;
|
||||
tx.nVersion = 2;
|
||||
tx.nLockTime = 0;
|
||||
tx.hasWitness = true;
|
||||
tx.vin.push_back(btc_in(txid, std::stoul(nvout)));
|
||||
tx.vout.push_back(btc_out(pw_address, deposit_amount));
|
||||
|
||||
btc_txout utxo;
|
||||
utxo.txid_ = txid;
|
||||
utxo.out_num_ = std::stoul(nvout);
|
||||
bytes unsigned_tx;
|
||||
tx.to_bytes(unsigned_tx);
|
||||
|
||||
ins.push_back(utxo);
|
||||
|
||||
outs[pw_address] = transfer_amount;
|
||||
|
||||
std::string reply_str = bitcoin_client->createrawtransaction(ins, outs);
|
||||
std::string reply_str = fc::to_hex((char *)&unsigned_tx[0], unsigned_tx.size());
|
||||
return sign_and_send_transaction_with_wallet(reply_str);
|
||||
}
|
||||
|
||||
|
|
@ -809,13 +904,7 @@ std::string sidechain_net_handler_bitcoin::transfer_withdrawal_from_primary_wall
|
|||
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<std::string>("address");
|
||||
std::string pw_address = obj->addresses.find(sidechain_type::bitcoin)->second;
|
||||
|
||||
uint64_t fee_rate = bitcoin_client->estimatesmartfee();
|
||||
uint64_t min_fee_rate = 1000;
|
||||
|
|
@ -839,14 +928,82 @@ std::string sidechain_net_handler_bitcoin::transfer_withdrawal_from_primary_wall
|
|||
}
|
||||
}
|
||||
|
||||
fc::flat_map<std::string, double> outs;
|
||||
outs[swwo.withdraw_address] = swwo.withdraw_amount.value / 100000000.0;
|
||||
btc_tx tx;
|
||||
tx.nVersion = 2;
|
||||
tx.nLockTime = 0;
|
||||
tx.hasWitness = true;
|
||||
std::vector<uint64_t> amounts;
|
||||
for (const auto &utxo : unspent_utxo) {
|
||||
tx.vin.push_back(btc_in(utxo.txid_, utxo.amount_));
|
||||
amounts.push_back(uint64_t(utxo.amount_ * 100000000.0));
|
||||
}
|
||||
tx.vout.push_back(btc_out(swwo.withdraw_address, swwo.withdraw_amount.value));
|
||||
if ((total_amount - min_amount) > 0.0) {
|
||||
outs[pw_address] = total_amount - min_amount;
|
||||
tx.vout.push_back(btc_out(pw_address, (total_amount - min_amount) * 100000000.0));
|
||||
}
|
||||
|
||||
std::string reply_str = bitcoin_client->createrawtransaction(unspent_utxo, outs);
|
||||
return sign_and_send_transaction_with_wallet(reply_str);
|
||||
auto from_sons = obj->sons;
|
||||
|
||||
std::vector<std::pair<std::string, uint64_t>> key_data;
|
||||
for (auto si : from_sons) {
|
||||
std::string pk = si.sidechain_public_keys.at(sidechain_type::bitcoin);
|
||||
key_data.push_back(std::make_pair(pk, si.total_votes));
|
||||
}
|
||||
std::sort(key_data.begin(), key_data.end(),
|
||||
[](std::pair<std::string, uint64_t> p1, std::pair<std::string, uint64_t> p2) {
|
||||
return (p1.second > p2.second);
|
||||
});
|
||||
bytes from_redeem_script = get_weighted_multisig_redeem_script(key_data);
|
||||
|
||||
bitcoin_transaction_send_operation op;
|
||||
op.payer = GRAPHENE_SON_ACCOUNT;
|
||||
op.redeem_script = from_redeem_script;
|
||||
op.in_amounts = amounts;
|
||||
tx.to_bytes(op.unsigned_tx);
|
||||
// add signatures
|
||||
std::set<son_id_type> plugin_sons = plugin.get_sons();
|
||||
for (auto si : from_sons) {
|
||||
if (plugin_sons.find(si.son_id) != plugin_sons.end()) {
|
||||
std::string son_btc_pub_key = si.sidechain_public_keys[peerplays_sidechain::sidechain_type::bitcoin];
|
||||
std::string str_priv_key = get_private_key(son_btc_pub_key);
|
||||
fc::optional<fc::ecc::private_key> k = graphene::utilities::wif_to_key(str_priv_key);
|
||||
if (!k)
|
||||
FC_THROW("Invalid bitcoi private key");
|
||||
std::vector<bytes> signatures = signatures_for_raw_transaction(op.unsigned_tx, amounts, from_redeem_script, *k);
|
||||
op.signatures[si.son_id] = signatures;
|
||||
} else {
|
||||
op.signatures[si.son_id];
|
||||
}
|
||||
}
|
||||
|
||||
const chain::global_property_object &gpo = database.get_global_properties();
|
||||
proposal_create_operation proposal_op;
|
||||
proposal_op.fee_paying_account = plugin.get_son_object(plugin.get_current_son_id()).son_account;
|
||||
proposal_op.proposed_ops.emplace_back(op_wrapper(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) {
|
||||
ilog("sidechain_net_handler: sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what()));
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void sidechain_net_handler_bitcoin::publish_btc_tx(const bitcoin_transaction_object &tx_object) {
|
||||
std::vector<std::vector<bytes>> signatures;
|
||||
signatures.resize(tx_object.signatures.size());
|
||||
std::transform(tx_object.signatures.begin(), tx_object.signatures.end(),
|
||||
signatures.begin(), [](const std::pair<son_id_type, std::vector<bytes>> &p) {
|
||||
return p.second;
|
||||
});
|
||||
bytes signed_tx = add_signatures_to_unsigned_tx(tx_object.unsigned_tx, signatures, tx_object.redeem_script);
|
||||
bitcoin_client->sendrawtransaction(fc::to_hex((const char *)&signed_tx[0], signed_tx.size()));
|
||||
}
|
||||
|
||||
void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) {
|
||||
|
|
|
|||
|
|
@ -39,20 +39,10 @@ void sidechain_net_handler_peerplays::process_deposit(const son_wallet_deposit_o
|
|||
void sidechain_net_handler_peerplays::process_withdrawal(const son_wallet_withdraw_object &swwo) {
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_peerplays::create_multisignature_wallet(const std::vector<std::string> public_keys) {
|
||||
return "";
|
||||
void sidechain_net_handler_peerplays::process_signing() {
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_peerplays::transfer(const std::string &from, const std::string &to, const uint64_t amount) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_peerplays::sign_transaction(const std::string &transaction) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string sidechain_net_handler_peerplays::send_transaction(const std::string &transaction) {
|
||||
return "";
|
||||
void sidechain_net_handler_peerplays::complete_signing() {
|
||||
}
|
||||
|
||||
void sidechain_net_handler_peerplays::on_applied_block(const signed_block &b) {
|
||||
|
|
|
|||
|
|
@ -57,4 +57,16 @@ void sidechain_net_manager::process_withdrawals() {
|
|||
}
|
||||
}
|
||||
|
||||
void sidechain_net_manager::process_signing() {
|
||||
for (size_t i = 0; i < net_handlers.size(); i++) {
|
||||
net_handlers.at(i)->process_signing();
|
||||
}
|
||||
}
|
||||
|
||||
void sidechain_net_manager::complete_signing() {
|
||||
for (size_t i = 0; i < net_handlers.size(); i++) {
|
||||
net_handlers.at(i)->complete_signing();
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace graphene::peerplays_sidechain
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ add_executable( betting_test ${BETTING_TESTS} ${COMMON_SOURCES} )
|
|||
target_link_libraries( betting_test graphene_chain graphene_app graphene_account_history graphene_bookie graphene_egenesis_none fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} )
|
||||
|
||||
file(GLOB PEERPLAYS_SIDECHAIN_TESTS "peerplays_sidechain/*.cpp")
|
||||
add_executable( peerplays_sidechain_test ${PEERPLAYS_SIDECHAIN_TESTS} ${COMMON_SOURCES} )
|
||||
add_executable( peerplays_sidechain_test ${PEERPLAYS_SIDECHAIN_TESTS} )
|
||||
target_link_libraries( peerplays_sidechain_test graphene_chain graphene_app graphene_account_history graphene_bookie graphene_egenesis_none fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} )
|
||||
|
||||
file(GLOB TOURNAMENT_TESTS "tournament/*.cpp")
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE(pw_transfer)
|
|||
for(auto& key: priv_old)
|
||||
pub_old.push_back(key.get_public_key());
|
||||
// old key weights
|
||||
std::vector<std::pair<fc::ecc::public_key, int> > weights_old;
|
||||
std::vector<std::pair<fc::ecc::public_key, uint64_t> > weights_old;
|
||||
for(unsigned i = 0; i < 15; ++i)
|
||||
weights_old.push_back(std::make_pair(pub_old[i], i + 1));
|
||||
// redeem script for old PW
|
||||
|
|
@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE(pw_transfer)
|
|||
for(auto& key: priv_new)
|
||||
pub_new.push_back(key.get_public_key());
|
||||
// new key weights
|
||||
std::vector<std::pair<fc::ecc::public_key, int> > weights_new;
|
||||
std::vector<std::pair<fc::ecc::public_key, uint64_t> > weights_new;
|
||||
for(unsigned i = 0; i < 15; ++i)
|
||||
weights_new.push_back(std::make_pair(pub_new[i], 16 - i));
|
||||
// redeem script for new PW
|
||||
|
|
@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE(pw_separate_sign)
|
|||
for(auto& key: priv_old)
|
||||
pub_old.push_back(key.get_public_key());
|
||||
// old key weights
|
||||
std::vector<std::pair<fc::ecc::public_key, int> > weights_old;
|
||||
std::vector<std::pair<fc::ecc::public_key, uint64_t> > weights_old;
|
||||
for(unsigned i = 0; i < 15; ++i)
|
||||
weights_old.push_back(std::make_pair(pub_old[i], i + 1));
|
||||
// redeem script for old PW
|
||||
|
|
@ -170,7 +170,7 @@ BOOST_AUTO_TEST_CASE(pw_separate_sign)
|
|||
for(auto& key: priv_new)
|
||||
pub_new.push_back(key.get_public_key());
|
||||
// new key weights
|
||||
std::vector<std::pair<fc::ecc::public_key, int> > weights_new;
|
||||
std::vector<std::pair<fc::ecc::public_key, uint64_t> > weights_new;
|
||||
for(unsigned i = 0; i < 15; ++i)
|
||||
weights_new.push_back(std::make_pair(pub_new[i], 16 - i));
|
||||
// redeem script for new PW
|
||||
|
|
@ -243,7 +243,7 @@ BOOST_AUTO_TEST_CASE(pw_separate_sign2)
|
|||
for(auto& key: priv_old)
|
||||
pub_old.push_back(key.get_public_key());
|
||||
// old key weights
|
||||
std::vector<std::pair<fc::ecc::public_key, int> > weights_old;
|
||||
std::vector<std::pair<fc::ecc::public_key, uint64_t> > weights_old;
|
||||
for(unsigned i = 0; i < 15; ++i)
|
||||
weights_old.push_back(std::make_pair(pub_old[i], i + 1));
|
||||
// redeem script for old PW
|
||||
|
|
@ -268,7 +268,7 @@ BOOST_AUTO_TEST_CASE(pw_separate_sign2)
|
|||
for(auto& key: priv_new)
|
||||
pub_new.push_back(key.get_public_key());
|
||||
// new key weights
|
||||
std::vector<std::pair<fc::ecc::public_key, int> > weights_new;
|
||||
std::vector<std::pair<fc::ecc::public_key, uint64_t> > weights_new;
|
||||
for(unsigned i = 0; i < 15; ++i)
|
||||
weights_new.push_back(std::make_pair(pub_new[i], 16 - i));
|
||||
// redeem script for new PW
|
||||
|
|
@ -345,7 +345,7 @@ BOOST_AUTO_TEST_CASE(pw_partially_sign)
|
|||
for(auto& key: priv_old)
|
||||
pub_old.push_back(key.get_public_key());
|
||||
// old key weights
|
||||
std::vector<std::pair<fc::ecc::public_key, int> > weights_old;
|
||||
std::vector<std::pair<fc::ecc::public_key, uint64_t> > weights_old;
|
||||
for(unsigned i = 0; i < 15; ++i)
|
||||
weights_old.push_back(std::make_pair(pub_old[i], i + 1));
|
||||
// redeem script for old PW
|
||||
|
|
@ -370,7 +370,7 @@ BOOST_AUTO_TEST_CASE(pw_partially_sign)
|
|||
for(auto& key: priv_new)
|
||||
pub_new.push_back(key.get_public_key());
|
||||
// new key weights
|
||||
std::vector<std::pair<fc::ecc::public_key, int> > weights_new;
|
||||
std::vector<std::pair<fc::ecc::public_key, uint64_t> > weights_new;
|
||||
for(unsigned i = 0; i < 15; ++i)
|
||||
weights_new.push_back(std::make_pair(pub_new[i], 16 - i));
|
||||
// redeem script for new PW
|
||||
|
|
|
|||
72
tests/peerplays_sidechain/plugin_test.cpp
Normal file
72
tests/peerplays_sidechain/plugin_test.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "son_fixture.hpp"
|
||||
|
||||
#include <graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp>
|
||||
#include <graphene/utilities/key_conversion.hpp>
|
||||
|
||||
using namespace graphene;
|
||||
using namespace graphene::chain;
|
||||
using namespace graphene::chain::test;
|
||||
using namespace graphene::peerplays_sidechain;
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE( plugin, son_fixture )
|
||||
|
||||
BOOST_AUTO_TEST_CASE(init)
|
||||
{
|
||||
generate_blocks(HARDFORK_SON_TIME + 1);
|
||||
set_expiration(db, trx);
|
||||
|
||||
// create sons
|
||||
std::vector<fc::ecc::private_key> keys;
|
||||
std::vector<son_id_type> sons;
|
||||
std::string test_url = "https://son_test";
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
fc::ecc::private_key privkey = generate_private_key(fc::to_string(i));
|
||||
keys.push_back(privkey);
|
||||
fc::ecc::public_key pubkey = privkey.get_public_key();
|
||||
sons.push_back(create_son("son" + fc::to_string(i), test_url, privkey, pubkey));
|
||||
ilog("son created: ${i}", ("i", i));
|
||||
}
|
||||
|
||||
BOOST_CHECK(sons.size() == 16);
|
||||
|
||||
// start plugin
|
||||
peerplays_sidechain_plugin plugin;
|
||||
boost::program_options::variables_map cfg;
|
||||
{
|
||||
std::vector<std::string> data;
|
||||
for (const auto& son_id: sons)
|
||||
data.push_back("\"" + std::string(object_id_type(son_id)) + "\"");
|
||||
cfg.emplace("son-id", boost::program_options::variable_value(data, false));
|
||||
}
|
||||
{
|
||||
std::vector<std::string> data;
|
||||
for (const fc::ecc::private_key& key: keys) {
|
||||
chain::public_key_type pubkey = key.get_public_key();
|
||||
std::string value = "[\"" + std::string(pubkey) + "\", \"" + graphene::utilities::key_to_wif(key) + "\"]";
|
||||
data.push_back(value);
|
||||
}
|
||||
cfg.emplace("peerplays-private-key", boost::program_options::variable_value(data, false));
|
||||
}
|
||||
{
|
||||
cfg.emplace("bitcoin-node-ip", boost::program_options::variable_value(std::string("99.79.189.95"), false));
|
||||
cfg.emplace("bitcoin-node-zmq-port", boost::program_options::variable_value(uint32_t(11111), false));
|
||||
cfg.emplace("bitcoin-node-rpc-port", boost::program_options::variable_value(uint32_t(22222), false));
|
||||
cfg.emplace("bitcoin-node-rpc-user", boost::program_options::variable_value(std::string("1"), false));
|
||||
cfg.emplace("bitcoin-node-rpc-password", boost::program_options::variable_value(std::string("1"), false));
|
||||
std::vector<std::string> data;
|
||||
for (const fc::ecc::private_key& key: keys) {
|
||||
chain::public_key_type pubkey = key.get_public_key();
|
||||
std::string value = "[\"" + std::string(pubkey) + "\", \"" + graphene::utilities::key_to_wif(key) + "\"]";
|
||||
data.push_back(value);
|
||||
}
|
||||
cfg.emplace("bitcoin-private-key", boost::program_options::variable_value(data, false));
|
||||
}
|
||||
plugin.plugin_set_app(&app);
|
||||
BOOST_CHECK_NO_THROW(plugin.plugin_initialize(cfg));
|
||||
BOOST_CHECK_NO_THROW(plugin.plugin_startup());
|
||||
BOOST_CHECK_NO_THROW(plugin.plugin_shutdown());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
1387
tests/peerplays_sidechain/son_fixture.cpp
Normal file
1387
tests/peerplays_sidechain/son_fixture.cpp
Normal file
File diff suppressed because it is too large
Load diff
326
tests/peerplays_sidechain/son_fixture.hpp
Normal file
326
tests/peerplays_sidechain/son_fixture.hpp
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <graphene/app/application.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <fc/io/json.hpp>
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
|
||||
#include <graphene/chain/operation_history_object.hpp>
|
||||
|
||||
#include <boost/parameter.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace graphene::db;
|
||||
|
||||
extern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP;
|
||||
|
||||
#define PUSH_TX \
|
||||
graphene::chain::test::_push_transaction
|
||||
|
||||
#define PUSH_BLOCK \
|
||||
graphene::chain::test::_push_block
|
||||
|
||||
// See below
|
||||
#define REQUIRE_OP_VALIDATION_SUCCESS( op, field, value ) \
|
||||
{ \
|
||||
const auto temp = op.field; \
|
||||
op.field = value; \
|
||||
op.validate(); \
|
||||
op.field = temp; \
|
||||
}
|
||||
#define REQUIRE_OP_EVALUATION_SUCCESS( op, field, value ) \
|
||||
{ \
|
||||
const auto temp = op.field; \
|
||||
op.field = value; \
|
||||
trx.operations.back() = op; \
|
||||
op.field = temp; \
|
||||
db.push_transaction( trx, ~0 ); \
|
||||
}
|
||||
|
||||
#define GRAPHENE_REQUIRE_THROW( expr, exc_type ) \
|
||||
{ \
|
||||
std::string req_throw_info = fc::json::to_string( \
|
||||
fc::mutable_variant_object() \
|
||||
("source_file", __FILE__) \
|
||||
("source_lineno", __LINE__) \
|
||||
("expr", #expr) \
|
||||
("exc_type", #exc_type) \
|
||||
); \
|
||||
if( fc::enable_record_assert_trip ) \
|
||||
std::cout << "GRAPHENE_REQUIRE_THROW begin " \
|
||||
<< req_throw_info << std::endl; \
|
||||
BOOST_REQUIRE_THROW( expr, exc_type ); \
|
||||
if( fc::enable_record_assert_trip ) \
|
||||
std::cout << "GRAPHENE_REQUIRE_THROW end " \
|
||||
<< req_throw_info << std::endl; \
|
||||
}
|
||||
|
||||
#define GRAPHENE_CHECK_THROW( expr, exc_type ) \
|
||||
{ \
|
||||
std::string req_throw_info = fc::json::to_string( \
|
||||
fc::mutable_variant_object() \
|
||||
("source_file", __FILE__) \
|
||||
("source_lineno", __LINE__) \
|
||||
("expr", #expr) \
|
||||
("exc_type", #exc_type) \
|
||||
); \
|
||||
if( fc::enable_record_assert_trip ) \
|
||||
std::cout << "GRAPHENE_CHECK_THROW begin " \
|
||||
<< req_throw_info << std::endl; \
|
||||
BOOST_CHECK_THROW( expr, exc_type ); \
|
||||
if( fc::enable_record_assert_trip ) \
|
||||
std::cout << "GRAPHENE_CHECK_THROW end " \
|
||||
<< req_throw_info << std::endl; \
|
||||
}
|
||||
|
||||
#define REQUIRE_OP_VALIDATION_FAILURE_2( op, field, value, exc_type ) \
|
||||
{ \
|
||||
const auto temp = op.field; \
|
||||
op.field = value; \
|
||||
GRAPHENE_REQUIRE_THROW( op.validate(), exc_type ); \
|
||||
op.field = temp; \
|
||||
}
|
||||
#define REQUIRE_OP_VALIDATION_FAILURE( op, field, value ) \
|
||||
REQUIRE_OP_VALIDATION_FAILURE_2( op, field, value, fc::exception )
|
||||
|
||||
#define REQUIRE_THROW_WITH_VALUE_2(op, field, value, exc_type) \
|
||||
{ \
|
||||
auto bak = op.field; \
|
||||
op.field = value; \
|
||||
trx.operations.back() = op; \
|
||||
op.field = bak; \
|
||||
GRAPHENE_REQUIRE_THROW(db.push_transaction(trx, ~0), exc_type); \
|
||||
}
|
||||
|
||||
#define REQUIRE_THROW_WITH_VALUE( op, field, value ) \
|
||||
REQUIRE_THROW_WITH_VALUE_2( op, field, value, fc::exception )
|
||||
|
||||
///This simply resets v back to its default-constructed value. Requires v to have a working assingment operator and
|
||||
/// default constructor.
|
||||
#define RESET(v) v = decltype(v)()
|
||||
///This allows me to build consecutive test cases. It's pretty ugly, but it works well enough for unit tests.
|
||||
/// i.e. This allows a test on update_account to begin with the database at the end state of create_account.
|
||||
#define INVOKE(test) ((struct test*)this)->test_method(); trx.clear()
|
||||
|
||||
#define PREP_ACTOR(name) \
|
||||
fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name)); \
|
||||
public_key_type name ## _public_key = name ## _private_key.get_public_key();
|
||||
|
||||
#define ACTOR(name) \
|
||||
PREP_ACTOR(name) \
|
||||
const auto& name = create_account(BOOST_PP_STRINGIZE(name), name ## _public_key); \
|
||||
account_id_type name ## _id = name.id; (void)name ## _id;
|
||||
|
||||
#define GET_ACTOR(name) \
|
||||
fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name)); \
|
||||
const account_object& name = get_account(BOOST_PP_STRINGIZE(name)); \
|
||||
account_id_type name ## _id = name.id; \
|
||||
(void)name ##_id
|
||||
|
||||
#define ACTORS_IMPL(r, data, elem) ACTOR(elem)
|
||||
#define ACTORS(names) BOOST_PP_SEQ_FOR_EACH(ACTORS_IMPL, ~, names)
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
namespace keywords {
|
||||
BOOST_PARAMETER_NAME(event_id)
|
||||
BOOST_PARAMETER_NAME(event_group_id)
|
||||
BOOST_PARAMETER_NAME(name)
|
||||
BOOST_PARAMETER_NAME(season)
|
||||
BOOST_PARAMETER_NAME(status)
|
||||
BOOST_PARAMETER_NAME(force)
|
||||
BOOST_PARAMETER_NAME(betting_market_group_id)
|
||||
BOOST_PARAMETER_NAME(description)
|
||||
BOOST_PARAMETER_NAME(rules_id)
|
||||
}
|
||||
|
||||
struct son_fixture {
|
||||
// the reason we use an app is to exercise the indexes of built-in
|
||||
// plugins
|
||||
graphene::app::application app;
|
||||
genesis_state_type genesis_state;
|
||||
chain::database &db;
|
||||
signed_transaction trx;
|
||||
public_key_type committee_key;
|
||||
account_id_type committee_account;
|
||||
fc::ecc::private_key private_key = fc::ecc::private_key::generate();
|
||||
fc::ecc::private_key init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key")) );
|
||||
public_key_type init_account_pub_key;
|
||||
|
||||
optional<fc::temp_directory> data_dir;
|
||||
bool skip_key_index_test = false;
|
||||
uint32_t anon_acct_count;
|
||||
|
||||
son_fixture();
|
||||
~son_fixture();
|
||||
|
||||
static fc::ecc::private_key generate_private_key(string seed);
|
||||
string generate_anon_acct_name();
|
||||
static void verify_asset_supplies( const database& db );
|
||||
void verify_account_history_plugin_index( )const;
|
||||
void open_database();
|
||||
signed_block generate_block(uint32_t skip = ~0,
|
||||
const fc::ecc::private_key& key = generate_private_key("null_key"),
|
||||
int miss_blocks = 0);
|
||||
|
||||
/**
|
||||
* @brief Generates block_count blocks
|
||||
* @param block_count number of blocks to generate
|
||||
*/
|
||||
void generate_blocks(uint32_t block_count);
|
||||
|
||||
/**
|
||||
* @brief Generates blocks until the head block time matches or exceeds timestamp
|
||||
* @param timestamp target time to generate blocks until
|
||||
*/
|
||||
void generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks = true, uint32_t skip = ~0);
|
||||
|
||||
///////////
|
||||
/// @brief Skip intermediate blocks, and generate a maintenance block
|
||||
/// @returns true on success
|
||||
///////////
|
||||
bool generate_maintenance_block();
|
||||
|
||||
account_create_operation make_account(
|
||||
const std::string& name = "nathan",
|
||||
public_key_type = public_key_type()
|
||||
);
|
||||
|
||||
account_create_operation make_account(
|
||||
const std::string& name,
|
||||
const account_object& registrar,
|
||||
const account_object& referrer,
|
||||
uint8_t referrer_percent = 100,
|
||||
public_key_type key = public_key_type()
|
||||
);
|
||||
|
||||
void force_global_settle(const asset_object& what, const price& p);
|
||||
operation_result force_settle(account_id_type who, asset what)
|
||||
{ return force_settle(who(db), what); }
|
||||
operation_result force_settle(const account_object& who, asset what);
|
||||
void update_feed_producers(asset_id_type mia, flat_set<account_id_type> producers)
|
||||
{ update_feed_producers(mia(db), producers); }
|
||||
void update_feed_producers(const asset_object& mia, flat_set<account_id_type> producers);
|
||||
void publish_feed(asset_id_type mia, account_id_type by, const price_feed& f)
|
||||
{ publish_feed(mia(db), by(db), f); }
|
||||
void publish_feed(const asset_object& mia, const account_object& by, const price_feed& f);
|
||||
const call_order_object* borrow(account_id_type who, asset what, asset collateral)
|
||||
{ return borrow(who(db), what, collateral); }
|
||||
const call_order_object* borrow(const account_object& who, asset what, asset collateral);
|
||||
void cover(account_id_type who, asset what, asset collateral_freed)
|
||||
{ cover(who(db), what, collateral_freed); }
|
||||
void cover(const account_object& who, asset what, asset collateral_freed);
|
||||
|
||||
const asset_object& get_asset( const string& symbol )const;
|
||||
const account_object& get_account( const string& name )const;
|
||||
const asset_object& create_bitasset(const string& name,
|
||||
account_id_type issuer = GRAPHENE_WITNESS_ACCOUNT,
|
||||
uint16_t market_fee_percent = 100 /*1%*/,
|
||||
uint16_t flags = charge_market_fee);
|
||||
const asset_object& create_prediction_market(const string& name,
|
||||
account_id_type issuer = GRAPHENE_WITNESS_ACCOUNT,
|
||||
uint16_t market_fee_percent = 100 /*1%*/,
|
||||
uint16_t flags = charge_market_fee);
|
||||
const asset_object& create_user_issued_asset( const string& name );
|
||||
const asset_object& create_user_issued_asset( const string& name,
|
||||
const account_object& issuer,
|
||||
uint16_t flags );
|
||||
void issue_uia( const account_object& recipient, asset amount );
|
||||
void issue_uia( account_id_type recipient_id, asset amount );
|
||||
|
||||
const account_object& create_account(
|
||||
const string& name,
|
||||
const public_key_type& key = public_key_type()
|
||||
);
|
||||
|
||||
const account_object& create_account(
|
||||
const string& name,
|
||||
const account_object& registrar,
|
||||
const account_object& referrer,
|
||||
uint8_t referrer_percent = 100,
|
||||
const public_key_type& key = public_key_type()
|
||||
);
|
||||
|
||||
const account_object& create_account(
|
||||
const string& name,
|
||||
const private_key_type& key,
|
||||
const account_id_type& registrar_id = account_id_type(),
|
||||
const account_id_type& referrer_id = account_id_type(),
|
||||
uint8_t referrer_percent = 100
|
||||
);
|
||||
|
||||
const committee_member_object& create_committee_member( const account_object& owner );
|
||||
const witness_object& create_witness(account_id_type owner,
|
||||
const fc::ecc::private_key& signing_private_key = generate_private_key("null_key"));
|
||||
const witness_object& create_witness(const account_object& owner,
|
||||
const fc::ecc::private_key& signing_private_key = generate_private_key("null_key"));
|
||||
vesting_balance_id_type create_vesting(account_id_type user, unsigned int amount, vesting_balance_type vesting_type);
|
||||
son_id_type create_son(const std::string& name, const std::string& url, const fc::ecc::private_key& signing_key, const fc::ecc::public_key& btc_key);
|
||||
uint64_t fund( const account_object& account, const asset& amount = asset(500000) );
|
||||
digest_type digest( const transaction& tx );
|
||||
void sign( signed_transaction& trx, const fc::ecc::private_key& key );
|
||||
const limit_order_object* create_sell_order( account_id_type user, const asset& amount, const asset& recv );
|
||||
const limit_order_object* create_sell_order( const account_object& user, const asset& amount, const asset& recv );
|
||||
asset cancel_limit_order( const limit_order_object& order );
|
||||
void transfer( account_id_type from, account_id_type to, const asset& amount, const asset& fee = asset() );
|
||||
void transfer( const account_object& from, const account_object& to, const asset& amount, const asset& fee = asset() );
|
||||
void fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount );
|
||||
void enable_fees();
|
||||
void change_fees( const flat_set< fee_parameters >& new_params, uint32_t new_scale = 0 );
|
||||
void upgrade_to_lifetime_member( account_id_type account );
|
||||
void upgrade_to_lifetime_member( const account_object& account );
|
||||
void upgrade_to_annual_member( account_id_type account );
|
||||
void upgrade_to_annual_member( const account_object& account );
|
||||
void print_market( const string& syma, const string& symb )const;
|
||||
string pretty( const asset& a )const;
|
||||
void print_limit_order( const limit_order_object& cur )const;
|
||||
void print_call_orders( )const;
|
||||
void print_joint_market( const string& syma, const string& symb )const;
|
||||
int64_t get_balance( account_id_type account, asset_id_type a )const;
|
||||
int64_t get_balance( const account_object& account, const asset_object& a )const;
|
||||
int64_t get_dividend_pending_payout_balance(asset_id_type dividend_holder_asset_type,
|
||||
account_id_type dividend_holder_account_id,
|
||||
asset_id_type dividend_payout_asset_type) const;
|
||||
vector< operation_history_object > get_operation_history( account_id_type account_id )const;
|
||||
void process_operation_by_witnesses(operation op);
|
||||
void process_operation_by_committee(operation op);
|
||||
void force_operation_by_witnesses(operation op);
|
||||
void set_is_proposed_trx(operation op);
|
||||
|
||||
proposal_id_type propose_operation(operation op);
|
||||
void process_proposal_by_witnesses(const std::vector<witness_id_type>& witnesses, proposal_id_type proposal_id, bool remove = false);
|
||||
};
|
||||
|
||||
namespace test {
|
||||
/// set a reasonable expiration time for the transaction
|
||||
void set_expiration( const database& db, transaction& tx );
|
||||
|
||||
bool _push_block( database& db, const signed_block& b, uint32_t skip_flags = 0 );
|
||||
processed_transaction _push_transaction( database& db, const signed_transaction& tx, uint32_t skip_flags = 0 );
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
@ -190,10 +190,19 @@ BOOST_AUTO_TEST_CASE(bitcoin_transaction_send_test)
|
|||
|
||||
const global_property_object &gpo = db.get_global_properties();
|
||||
|
||||
const auto& sidx = db.get_index_type<son_index>().indices().get<graphene::chain::by_account>();
|
||||
const auto son_obj1 = sidx.find( alice_id );
|
||||
BOOST_REQUIRE(son_obj1 != sidx.end());
|
||||
const auto son_obj2 = sidx.find( bob_id );
|
||||
BOOST_REQUIRE(son_obj2 != sidx.end());
|
||||
|
||||
|
||||
{
|
||||
BOOST_TEST_MESSAGE("Send bitcoin_transaction_send_operation");
|
||||
|
||||
bitcoin_transaction_send_operation send_op;
|
||||
send_op.signatures[son_obj1->id];
|
||||
send_op.signatures[son_obj2->id];
|
||||
|
||||
send_op.payer = GRAPHENE_SON_ACCOUNT;
|
||||
|
||||
|
|
@ -250,12 +259,8 @@ BOOST_AUTO_TEST_CASE(bitcoin_transaction_send_test)
|
|||
auto pobj = idx.find(proposal_id_type(0));
|
||||
BOOST_REQUIRE(pobj != idx.end());
|
||||
|
||||
const auto& sidx = db.get_index_type<son_index>().indices().get<graphene::chain::by_account>();
|
||||
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<bitcoin_transaction_send_operation>();
|
||||
BOOST_REQUIRE(bitcoin_transaction_send_op.signatures.size() == 1);
|
||||
BOOST_REQUIRE(bitcoin_transaction_send_op.signatures.size() == 2);
|
||||
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);
|
||||
|
|
@ -286,9 +291,6 @@ BOOST_AUTO_TEST_CASE(bitcoin_transaction_send_test)
|
|||
|
||||
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));
|
||||
|
|
|
|||
Loading…
Reference in a new issue