Compare commits

...

34 commits

Author SHA1 Message Date
gladcow
efe7a0c4ae use sidechain keys for redeem script generation 2020-03-19 11:42:38 +03:00
gladcow
cf36f00c25 fix listunspent implementation to have info about unconfirmed txes 2020-03-19 09:03:59 +03:00
gladcow
eb59bb40b5 make regtest default bitcoin network 2020-03-18 18:39:40 +03:00
gladcow
00a453fd77 correct btc regtest bech32 address prefix 2020-03-18 18:39:40 +03:00
gladcow
38831b4742 use existing implementation for hex conversion 2020-03-17 09:41:19 +03:00
gladcow
a1adcc8507 make testnet default bitcoin network 2020-03-17 09:28:24 +03:00
Srdjan Obucina
2528c349d4 Fix public key processing, its Hex not Base58 encoded 2020-03-16 23:47:25 +01:00
gladcow
06a9b1f75e Merge branch 'feature/SON-134' of github.com:peerplays-network/peerplays into feature/SON-134 2020-03-16 12:36:28 +03:00
gladcow
819e2e7ee4 use sidechain private keys to sign txes 2020-03-16 12:24:58 +03:00
Srdjan Obucina
5231f36976 Some logs removed, dead code removed 2020-03-16 00:30:59 +01:00
Srdjan Obucina
e28a19a9d3 Code formatting 2020-03-15 23:40:27 +03:00
sierra19XX
3c5a985f92 SON276 - Fix SON proposal exceptions - I (#307)
Co-authored-by: satyakoneru <15652887+satyakoneru@users.noreply.github.com>
2020-03-15 23:40:27 +03:00
sierra19XX
43bc782469 SON275 - ZMQ Crash on application exit (#306)
* SON275 - ZMQ Crash on application exit

* SON275 - Fix Indentation

Co-authored-by: satyakoneru <15652887+satyakoneru@users.noreply.github.com>
2020-03-15 23:40:27 +03:00
Srdjan Obucina
4736e7f883 Quickfix, remove dead code, return result from wallet withdraw do_evaluate 2020-03-15 23:40:27 +03:00
gladcow
b4575fb32e init test 2020-03-15 23:40:27 +03:00
Srdjan Obucina
f457ae9725 Remove dead code 2020-03-14 03:04:53 +01:00
Srdjan Obucina
2b6c700644 Code formatting 2020-03-14 02:18:03 +01:00
Srdjan Obucina
b1fbd26bcf Merge branch 'feature/SONs-base' into feature/SON-134 2020-03-14 02:16:19 +01:00
gladcow
471738e851 son_fixture 2020-03-13 13:30:58 +03:00
gladcow
59d98db82a count initial tx signatures in statistics 2020-03-11 13:26:38 +03:00
gladcow
6eb4f993bb fix broken tests 2020-03-06 17:04:32 +03:00
gladcow
f2a38419ce revert to combination of signing and approval 2020-03-06 17:04:32 +03:00
gladcow
ed19ccee25 signature collection for withdraw operations 2020-03-06 17:00:10 +03:00
gladcow
ab9a7f2fca publish signed tx 2020-03-06 17:00:10 +03:00
gladcow
682f64c8fc process tx signing 2020-03-06 17:00:10 +03:00
gladcow
d710b320c0 remove bitcoin-specific fields from son wallet 2020-03-06 17:00:10 +03:00
gladcow
a06af5bf17 change proposal to transaction object in bitcoin_transaction_sign_operation 2020-03-06 17:00:10 +03:00
gladcow
610426836b send signatures for bitcoin transaction 2020-03-06 16:58:23 +03:00
gladcow
2f03465991 fix initial signatures in bitcoin_send_operation 2020-03-06 16:58:23 +03:00
gladcow
d93f5e66b1 Add redeem_script to son_wallet_object 2020-03-06 16:58:23 +03:00
gladcow
f3e8dabaa4 add initial signatures to bitcoin_transaction_send_operation 2020-03-06 16:58:23 +03:00
gladcow
6a6829a2c2 initiate bitcoin_transaction_send_operation in PW recreation 2020-03-06 16:58:23 +03:00
gladcow
6b47ed4a8c replace createrawtransaction call 2020-03-06 16:58:23 +03:00
gladcow
0d1adde0a6 Create weighted multisig address 2020-03-06 16:58:23 +03:00
20 changed files with 2301 additions and 188 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

@ -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:
};

View file

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

View file

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

View file

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

View file

@ -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();
}

View file

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

View file

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

View file

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

View file

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

View file

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

View 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()

File diff suppressed because it is too large Load diff

View 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 );
}
} }

View file

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