SON261 - Add PW->PW Transfer and Code reorg

This commit is contained in:
satyakoneru 2020-02-21 09:09:33 +00:00
parent d2fdca7dad
commit c953864c39
8 changed files with 484 additions and 62 deletions

View file

@ -13,7 +13,6 @@ namespace graphene { namespace chain {
account_id_type payer;
// TODO: BTC Transaction Structs go here
std::string tx_hex;
fc::flat_map<son_id_type, std::vector<peerplays_sidechain::bytes>> signatures;
account_id_type fee_payer()const { return payer; }
@ -28,7 +27,6 @@ namespace graphene { namespace chain {
asset fee;
account_id_type payer;
proposal_id_type proposal_id;
std::string signed_tx_hex;
std::vector<peerplays_sidechain::bytes> signatures;
account_id_type fee_payer()const { return payer; }
@ -53,10 +51,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)(tx_hex)(signatures) )
FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation, (fee)(payer)(signatures) )
FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation, (fee)(payer)(proposal_id)(signed_tx_hex)(signatures) )
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) )

View file

@ -17,7 +17,6 @@ namespace graphene { namespace chain {
static const uint8_t type_id = bitcoin_transaction_object_type;
// Bitcoin structs go here.
bool processed = false;
fc::flat_map<son_id_type, std::string> tx_signatures;
fc::flat_map<son_id_type, std::vector<peerplays_sidechain::bytes>> signatures;
};
@ -37,4 +36,4 @@ namespace graphene { namespace chain {
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::bitcoin_transaction_object, (graphene::db::object),
(processed)(tx_signatures)(signatures) )
(processed)(signatures) )

View file

@ -133,4 +133,4 @@ object_id_type bitcoin_send_transaction_process_evaluator::do_apply(const bitcoi
}
} // namespace chain
} // namespace graphene
} // namespace graphene

View file

@ -32,6 +32,7 @@ protected:
virtual std::string sign_transaction( const std::string& transaction ) = 0;
virtual std::string send_transaction( const std::string& transaction ) = 0;
virtual std::string transfer_deposit_to_primary_wallet (const sidechain_event_data& sed) = 0;
virtual std::string transfer_withdrawal_from_primary_wallet(const std::string& user_address, double sidechain_amount) = 0;
virtual void handle_event( const std::string& event_data ) = 0;

View file

@ -83,8 +83,11 @@ public:
std::string transfer( const std::string& from, const std::string& to, const uint64_t amount );
std::string sign_transaction( const std::string& transaction );
std::string send_transaction( const std::string& transaction );
std::string sign_and_send_transaction_with_wallet ( const std::string& tx_json );
std::string transfer_all_btc(const std::string& from_address, const std::string& to_address);
std::string transfer_deposit_to_primary_wallet (const sidechain_event_data& sed);
std::string transfer_withdrawal_from_primary_wallet(const std::string& user_address, int64_t sidechain_amount);
std::string transfer_withdrawal_from_primary_wallet(const std::string& user_address, double sidechain_amount);
private:
std::string ip;

View file

@ -106,7 +106,7 @@ void peerplays_sidechain_plugin_impl::plugin_set_program_options(
("bitcoin-address", bpo::value<string>()->default_value("2N911a7smwDzUGARg8s7Q1ViizFCw6gWcbR"), "Bitcoin address")
("bitcoin-public-key", bpo::value<string>()->default_value("02d0f137e717fb3aab7aff99904001d49a0a636c5e1342f8927a4ba2eaee8e9772"), "Bitcoin public key")
("bitcoin-private-key", bpo::value<string>()->default_value("cVN31uC9sTEr392DLVUEjrtMgLA8Yb3fpYmTRj7bomTm6nn2ANPr"), "Bitcoin private key")
("bitcoin-private-keys", bpo::value<vector<string>>()->composing()->multitoken()->
("bitcoin-private-keys", bpo::value<vector<string>>()->composing()->multitoken()->
DEFAULT_VALUE_VECTOR(std::make_pair("02d0f137e717fb3aab7aff99904001d49a0a636c5e1342f8927a4ba2eaee8e9772", "cVN31uC9sTEr392DLVUEjrtMgLA8Yb3fpYmTRj7bomTm6nn2ANPr")),
"Tuple of [Bitcoin PublicKey, Bitcoin Private key] (may specify multiple times)")
;
@ -123,7 +123,7 @@ void peerplays_sidechain_plugin_impl::plugin_initialize(const boost::program_opt
config_ready_son = config_ready_son && !_sons.empty();
#ifndef SUPPORT_MULTIPLE_SONS
//FC_ASSERT( _sons.size() == 1, "Multiple SONs not supported" );
FC_ASSERT( _sons.size() == 1, "Multiple SONs not supported" );
#endif
if( options.count("peerplays-private-key") )

View file

@ -575,41 +575,9 @@ std::string sidechain_net_handler_bitcoin::send_transaction( const std::string&
return "";
}
std::string sidechain_net_handler_bitcoin::transfer_deposit_to_primary_wallet ( const sidechain_event_data& sed )
std::string sidechain_net_handler_bitcoin::sign_and_send_transaction_with_wallet ( const std::string& tx_json )
{
const auto& idx = database.get_index_type<son_wallet_index>().indices().get<by_id>();
auto obj = idx.rbegin();
if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) {
return "";
}
std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second;
std::stringstream ss(pw_address_json);
boost::property_tree::ptree json;
boost::property_tree::read_json( ss, json );
std::string pw_address = json.get<std::string>("address");
std::string txid = sed.sidechain_transaction_id;
std::string suid = sed.sidechain_uid;
std::string nvout = suid.substr(suid.find_last_of("-")+1);
int64_t deposit_amount = sed.sidechain_amount;
deposit_amount -= 1000; // 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_txout utxo;
utxo.txid_ = txid;
utxo.out_num_ = std::stoul(nvout);
ins.push_back(utxo);
outs[pw_address] = transfer_amount;
std::string reply_str = bitcoin_client->prepare_tx(ins, outs);
std::string reply_str = tx_json;
ilog(reply_str);
@ -640,7 +608,84 @@ std::string sidechain_net_handler_bitcoin::transfer_deposit_to_primary_wallet (
return reply_str;
}
std::string sidechain_net_handler_bitcoin::transfer_withdrawal_from_primary_wallet(const std::string& user_address, int64_t sidechain_amount) {
std::string sidechain_net_handler_bitcoin::transfer_all_btc(const std::string& from_address, const std::string& to_address)
{
uint64_t fee_rate = bitcoin_client->receive_estimated_fee();
uint64_t min_fee_rate = 1000;
fee_rate = std::max(fee_rate, min_fee_rate);
double min_amount = ((double)fee_rate/100000000.0); // Account only for relay fee for now
double total_amount = 0.0;
std::vector<btc_txout> unspent_utxo= bitcoin_client->list_unspent_by_address_and_amount(from_address, 0);
if(unspent_utxo.size() == 0)
{
wlog("Failed to find UTXOs to spend for ${pw}",("pw", from_address));
return "";
}
else
{
for(const auto& utx: unspent_utxo)
{
total_amount += utx.amount_;
}
if(min_amount >= total_amount)
{
wlog("Failed not enough BTC to transfer from ${fa}",("fa", from_address));
return "";
}
}
fc::flat_map<std::string, double> outs;
outs[to_address] = total_amount - min_amount;
std::string reply_str = bitcoin_client->prepare_tx(unspent_utxo, outs);
return sign_and_send_transaction_with_wallet(reply_str);
}
std::string sidechain_net_handler_bitcoin::transfer_deposit_to_primary_wallet ( const sidechain_event_data& sed )
{
const auto& idx = database.get_index_type<son_wallet_index>().indices().get<by_id>();
auto obj = idx.rbegin();
if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) {
return "";
}
std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second;
std::stringstream ss(pw_address_json);
boost::property_tree::ptree json;
boost::property_tree::read_json( ss, json );
std::string pw_address = json.get<std::string>("address");
std::string txid = sed.sidechain_transaction_id;
std::string suid = sed.sidechain_uid;
std::string nvout = suid.substr(suid.find_last_of("-")+1);
uint64_t deposit_amount = sed.sidechain_amount;
uint64_t fee_rate = bitcoin_client->receive_estimated_fee();
uint64_t min_fee_rate = 1000;
fee_rate = std::max(fee_rate, min_fee_rate);
deposit_amount -= fee_rate; // Deduct minimum relay fee
double transfer_amount = (double)deposit_amount/100000000.0;
std::vector<btc_txout> ins;
fc::flat_map<std::string, double> outs;
btc_txout utxo;
utxo.txid_ = txid;
utxo.out_num_ = std::stoul(nvout);
ins.push_back(utxo);
outs[pw_address] = transfer_amount;
std::string reply_str = bitcoin_client->prepare_tx(ins, outs);
return sign_and_send_transaction_with_wallet(reply_str);
}
std::string sidechain_net_handler_bitcoin::transfer_withdrawal_from_primary_wallet(const std::string& user_address, double sidechain_amount) {
const auto& idx = database.get_index_type<son_wallet_index>().indices().get<by_id>();
auto obj = idx.rbegin();
if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end())
@ -656,32 +701,42 @@ std::string sidechain_net_handler_bitcoin::transfer_withdrawal_from_primary_wall
std::string pw_address = json.get<std::string>("address");
double total_amount = (((double)sidechain_amount*1000.0)+1000.0)/100000000.0; // Account only for relay fee for now
double transfer_amount = ((double)sidechain_amount*1000.0)/100000000.0;
std::vector<btc_txout> unspent_utxo= bitcoin_client->list_unspent_by_address_and_amount(pw_address, total_amount);
uint64_t fee_rate = bitcoin_client->receive_estimated_fee();
uint64_t min_fee_rate = 1000;
fee_rate = std::max(fee_rate, min_fee_rate);
double min_amount = sidechain_amount + ((double)fee_rate/100000000.0); // Account only for relay fee for now
double total_amount = 0.0;
std::vector<btc_txout> unspent_utxo= bitcoin_client->list_unspent_by_address_and_amount(pw_address, 0);
if(unspent_utxo.size() == 0)
{
wlog("Failed to find UTXOs to spend for ${pw}",("pw", pw_address));
return "";
}
else
{
for(const auto& utx: unspent_utxo)
{
total_amount += utx.amount_;
}
btc_txout utxo = unspent_utxo[0];
std::string reply_str = bitcoin_client->create_raw_transaction(utxo.txid_, std::to_string(utxo.out_num_), user_address, transfer_amount);
ilog(reply_str);
std::stringstream ss_utx(reply_str);
boost::property_tree::ptree pt;
boost::property_tree::read_json( ss_utx, pt );
if( !(pt.count( "error" ) && pt.get_child( "error" ).empty()) || !pt.count("result") ) {
return "";
if(min_amount > total_amount)
{
wlog("Failed not enough BTC to spend for ${pw}",("pw", pw_address));
return "";
}
}
std::string unsigned_tx_hex = pt.get<std::string>("result");
fc::flat_map<std::string, double> outs;
outs[user_address] = sidechain_amount;
if((total_amount - min_amount) > 0.0)
{
outs[pw_address] = total_amount - min_amount;
}
std::cout << unsigned_tx_hex << std::endl;
return unsigned_tx_hex;
std::string reply_str = bitcoin_client->prepare_tx(unspent_utxo, outs);
return sign_and_send_transaction_with_wallet(reply_str);
}
void sidechain_net_handler_bitcoin::handle_event( const std::string& event_data ) {

View file

@ -0,0 +1,366 @@
#include <boost/test/unit_test.hpp>
#include "../common/database_fixture.hpp"
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/sidechain_transaction_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/son_object.hpp>
#include <graphene/peerplays_sidechain/defs.hpp>
using namespace graphene;
using namespace graphene::chain;
using namespace graphene::chain::test;
BOOST_FIXTURE_TEST_SUITE(sidechain_transaction_tests, database_fixture)
BOOST_AUTO_TEST_CASE(bitcoin_transaction_send_test)
{
try
{
BOOST_TEST_MESSAGE("bitcoin_transaction_send_test");
generate_blocks(HARDFORK_SON_TIME);
generate_block();
set_expiration(db, trx);
ACTORS((alice)(bob));
upgrade_to_lifetime_member(alice);
upgrade_to_lifetime_member(bob);
transfer(committee_account, alice_id, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));
transfer(committee_account, bob_id, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));
generate_block();
set_expiration(db, trx);
std::string test_url = "https://create_son_test";
// create deposit vesting
vesting_balance_id_type deposit_alice;
{
vesting_balance_create_operation op;
op.creator = alice_id;
op.owner = alice_id;
op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION);
op.balance_type = vesting_balance_type::son;
op.policy = dormant_vesting_policy_initializer{};
trx.operations.push_back(op);
sign(trx, alice_private_key);
processed_transaction ptx = PUSH_TX(db, trx, ~0);
trx.clear();
deposit_alice = ptx.operation_results[0].get<object_id_type>();
}
generate_block();
set_expiration(db, trx);
// create payment normal vesting
vesting_balance_id_type payment_alice;
{
vesting_balance_create_operation op;
op.creator = alice_id;
op.owner = alice_id;
op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION);
op.balance_type = vesting_balance_type::normal;
trx.operations.push_back(op);
sign(trx, alice_private_key);
processed_transaction ptx = PUSH_TX(db, trx, ~0);
trx.clear();
payment_alice = ptx.operation_results[0].get<object_id_type>();
}
generate_block();
set_expiration(db, trx);
// alice becomes son
{
flat_map<graphene::peerplays_sidechain::sidechain_type, string> sidechain_public_keys;
sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin address";
son_create_operation op;
op.owner_account = alice_id;
op.url = test_url;
op.deposit = deposit_alice;
op.pay_vb = payment_alice;
op.signing_key = alice_public_key;
op.sidechain_public_keys = sidechain_public_keys;
trx.operations.push_back(op);
sign(trx, alice_private_key);
PUSH_TX(db, trx, ~0);
trx.clear();
}
generate_block();
set_expiration(db, trx);
// create deposit vesting
vesting_balance_id_type deposit_bob;
{
vesting_balance_create_operation op;
op.creator = bob_id;
op.owner = bob_id;
op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION);
op.balance_type = vesting_balance_type::son;
op.policy = dormant_vesting_policy_initializer{};
trx.operations.push_back(op);
sign(trx, bob_private_key);
processed_transaction ptx = PUSH_TX(db, trx, ~0);
trx.clear();
deposit_bob = ptx.operation_results[0].get<object_id_type>();
}
generate_block();
set_expiration(db, trx);
// create payment normal vesting
vesting_balance_id_type payment_bob;
{
vesting_balance_create_operation op;
op.creator = bob_id;
op.owner = bob_id;
op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION);
op.balance_type = vesting_balance_type::normal;
trx.operations.push_back(op);
sign(trx, bob_private_key);
processed_transaction ptx = PUSH_TX(db, trx, ~0);
trx.clear();
payment_bob = ptx.operation_results[0].get<object_id_type>();
}
generate_block();
set_expiration(db, trx);
// bob becomes son
{
flat_map<graphene::peerplays_sidechain::sidechain_type, string> sidechain_public_keys;
sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin address";
son_create_operation op;
op.owner_account = bob_id;
op.url = test_url;
op.deposit = deposit_bob;
op.pay_vb = payment_bob;
op.signing_key = bob_public_key;
op.sidechain_public_keys = sidechain_public_keys;
trx.operations.push_back(op);
sign(trx, alice_private_key);
PUSH_TX(db, trx, ~0);
trx.clear();
}
generate_block();
generate_block();
const auto &son_btc_account = db.create<account_object>([&](account_object &obj) {
obj.name = "son_btc_account";
obj.statistics = db.create<account_statistics_object>([&](account_statistics_object &acc_stat) { acc_stat.owner = obj.id; }).id;
obj.membership_expiration_date = time_point_sec::maximum();
obj.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;
obj.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;
obj.owner.add_authority(bob_id, 1);
obj.active.add_authority(bob_id, 1);
obj.owner.add_authority(alice_id, 1);
obj.active.add_authority(alice_id, 1);
obj.active.weight_threshold = 2;
obj.owner.weight_threshold = 2;
});
db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
{
_gpo.parameters.extensions.value.son_btc_account = son_btc_account.get_id();
if( _gpo.pending_parameters )
_gpo.pending_parameters->extensions.value.son_btc_account = son_btc_account.get_id();
});
generate_block();
const global_property_object &gpo = db.get_global_properties();
{
BOOST_TEST_MESSAGE("Send bitcoin_transaction_send_operation");
bitcoin_transaction_send_operation send_op;
send_op.payer = db.get_global_properties().parameters.get_son_btc_account_id();
proposal_create_operation proposal_op;
proposal_op.fee_paying_account = alice_id;
proposal_op.proposed_ops.push_back(op_wrapper(send_op));
uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3;
proposal_op.expiration_time = time_point_sec(db.head_block_time().sec_since_epoch() + lifetime);
trx.operations.push_back(proposal_op);
set_expiration(db, trx);
sign(trx, alice_private_key);
PUSH_TX(db, trx, ~0);
trx.clear();
}
generate_block();
BOOST_TEST_MESSAGE("Check proposal results");
const auto &idx = db.get_index_type<proposal_index>().indices().get<by_id>();
BOOST_REQUIRE(idx.size() == 1);
auto obj = idx.find(proposal_id_type(0));
BOOST_REQUIRE(obj != idx.end());
const auto& btidx = db.get_index_type<bitcoin_transaction_index>().indices().get<by_id>();
BOOST_REQUIRE(btidx.size() == 0);
std::vector<unsigned char> a1 = {'a', 'l', 'i', 'c', 'e', '1'};
std::vector<unsigned char> a2 = {'a', 'l', 'i', 'c', 'e', '2'};
std::vector<unsigned char> a3 = {'a', 'l', 'i', 'c', 'e', '3'};
{
BOOST_TEST_MESSAGE("Send bitcoin_transaction_sign_operation");
bitcoin_transaction_sign_operation sign_op;
sign_op.payer = alice_id;
sign_op.proposal_id = proposal_id_type(0);
sign_op.signatures.push_back(a1);
sign_op.signatures.push_back(a2);
sign_op.signatures.push_back(a3);
trx.operations.push_back(sign_op);
set_expiration(db, trx);
sign(trx, alice_private_key);
PUSH_TX(db, trx, ~0);
trx.clear();
}
generate_block();
BOOST_REQUIRE(idx.size() == 1);
BOOST_REQUIRE(btidx.size() == 0);
auto pobj = idx.find(proposal_id_type(0));
BOOST_REQUIRE(pobj != idx.end());
const auto& sidx = db.get_index_type<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[son_obj1->id][0] == a1);
BOOST_REQUIRE(bitcoin_transaction_send_op.signatures[son_obj1->id][1] == a2);
BOOST_REQUIRE(bitcoin_transaction_send_op.signatures[son_obj1->id][2] == a3);
std::vector<unsigned char> b1 = {'b', 'o', 'b', '1'};
std::vector<unsigned char> b2 = {'b', 'o', 'b', '2'};
std::vector<unsigned char> b3 = {'b', 'o', 'b', '3'};
{
BOOST_TEST_MESSAGE("Send bitcoin_transaction_sign_operation");
bitcoin_transaction_sign_operation sign_op;
sign_op.payer = bob_id;
sign_op.proposal_id = proposal_id_type(0);
sign_op.signatures.push_back(b1);
sign_op.signatures.push_back(b2);
sign_op.signatures.push_back(b3);
trx.operations.push_back(sign_op);
set_expiration(db, trx);
sign(trx, bob_private_key);
PUSH_TX(db, trx, ~0);
trx.clear();
}
generate_block();
BOOST_REQUIRE(idx.size() == 0);
const auto son_obj2 = sidx.find( bob_id );
BOOST_REQUIRE(son_obj2 != sidx.end());
BOOST_REQUIRE(btidx.size() == 1);
const auto btobj = btidx.find(bitcoin_transaction_id_type(0));
BOOST_REQUIRE(btobj != btidx.end());
BOOST_REQUIRE(btobj->processed == false);
auto sigs = btobj->signatures;
BOOST_REQUIRE(sigs[son_obj1->id][0] == a1);
BOOST_REQUIRE(sigs[son_obj1->id][1] == a2);
BOOST_REQUIRE(sigs[son_obj1->id][2] == a3);
BOOST_REQUIRE(sigs[son_obj2->id][0] == b1);
BOOST_REQUIRE(sigs[son_obj2->id][1] == b2);
BOOST_REQUIRE(sigs[son_obj2->id][2] == b3);
{
BOOST_TEST_MESSAGE("Send bitcoin_send_transaction_process_operation");
bitcoin_send_transaction_process_operation process_op;
process_op.bitcoin_transaction_id = bitcoin_transaction_id_type(0);
process_op.payer = db.get_global_properties().parameters.get_son_btc_account_id();
proposal_create_operation proposal_op;
proposal_op.fee_paying_account = alice_id;
proposal_op.proposed_ops.push_back(op_wrapper(process_op));
uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3;
proposal_op.expiration_time = time_point_sec(db.head_block_time().sec_since_epoch() + lifetime);
trx.operations.push_back(proposal_op);
set_expiration(db, trx);
sign(trx, alice_private_key);
PUSH_TX(db, trx, ~0);
trx.clear();
}
generate_block();
BOOST_REQUIRE(idx.size() == 1);
obj = idx.find(proposal_id_type(1));
BOOST_REQUIRE(obj != idx.end());
{
BOOST_TEST_MESSAGE("Send proposal_update_operation");
proposal_update_operation puo;
puo.fee_paying_account = bob_id;
puo.proposal = obj->id;
puo.active_approvals_to_add = { bob_id };
trx.operations.push_back(puo);
set_expiration(db, trx);
sign(trx, bob_private_key);
PUSH_TX(db, trx, ~0);
trx.clear();
}
generate_block();
BOOST_REQUIRE(idx.size() == 1);
obj = idx.find(proposal_id_type(1));
BOOST_REQUIRE(obj != idx.end());
BOOST_REQUIRE(btobj != btidx.end());
BOOST_REQUIRE(btobj->processed == false);
{
BOOST_TEST_MESSAGE("Send proposal_update_operation");
proposal_update_operation puo;
puo.fee_paying_account = alice_id;
puo.proposal = obj->id;
puo.active_approvals_to_add = { alice_id };
trx.operations.push_back(puo);
set_expiration(db, trx);
sign(trx, alice_private_key);
PUSH_TX(db, trx, ~0);
trx.clear();
}
generate_block();
BOOST_REQUIRE(idx.size() == 0);
BOOST_REQUIRE(btobj != btidx.end());
BOOST_REQUIRE(btobj->processed == true);
}
FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_SUITE_END()