From 26663509789b60390c02a977cd05dc3a96e57310 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 25 Feb 2016 03:24:16 -0500 Subject: [PATCH 01/34] Fix iteration logic in _handle_message_calls_in_progress shutdown loop to handle concurrent modification #598 --- libraries/net/node.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index db2369a0..6fb212c7 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -3952,12 +3952,20 @@ namespace graphene { namespace net { namespace detail { } unsigned handle_message_call_count = 0; - for (fc::future& handle_message_call : _handle_message_calls_in_progress) + while( true ) { + auto it = _handle_message_calls_in_progress.begin(); + if( it == _handle_message_calls_in_progress.end() ) + break; + if( it->ready() || it->error() || it->canceled() ) + { + _handle_message_calls_in_progress.erase( it ); + continue; + } ++handle_message_call_count; try { - handle_message_call.cancel_and_wait("node_impl::close()"); + it->cancel_and_wait("node_impl::close()"); dlog("handle_message call #${count} task terminated", ("count", handle_message_call_count)); } catch ( const fc::canceled_exception& ) @@ -3973,7 +3981,6 @@ namespace graphene { namespace net { namespace detail { wlog("Exception thrown while terminating handle_message call #${count} task, ignoring",("count", handle_message_call_count)); } } - _handle_message_calls_in_progress.clear(); try { From e8aa505e823435f5c1ac8eed8238445b5febddd8 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 25 Feb 2016 11:34:44 -0500 Subject: [PATCH 02/34] Bump fc --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index 8eec508b..83b4de06 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 8eec508b8cd418f0719a58f10a11d7850e87b992 +Subproject commit 83b4de067a6c99704a6d21d6dfc8b1e838ddcaf7 From 4b346579a8c3e2d189f38f751085d7dfe5c695c3 Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Thu, 25 Feb 2016 11:37:25 -0500 Subject: [PATCH 03/34] Fixed to follow QUOTE:BASE semantics. Cleanup and added in wallet calls that were missed in last merge. #592 --- libraries/app/database_api.cpp | 103 +++++---- .../app/include/graphene/app/database_api.hpp | 61 +++--- .../wallet/include/graphene/wallet/wallet.hpp | 50 +++++ libraries/wallet/wallet.cpp | 198 ++++++++++++++---- 4 files changed, 286 insertions(+), 126 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 0e57ef7f..734d68b2 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -31,6 +31,8 @@ #include #include +#include +#include #include @@ -1025,56 +1027,37 @@ market_ticker database_api_impl::get_ticker( const string& base, const string& q try { if( base_id > quote_id ) std::swap(base_id, quote_id); - const auto& bidx = _db.get_index_type(); - const auto& by_key_idx = bidx.indices().get(); - uint32_t bucket_size = 86400; + uint32_t day = 86400; auto now = fc::time_point_sec( fc::time_point::now() ); - - auto itr = by_key_idx.lower_bound( bucket_key( base_id, quote_id, bucket_size, - now - bucket_size ) ); - auto orders = get_order_book( base, quote, 1 ); + auto trades = get_trade_history( base, quote, now, fc::time_point_sec( now.sec_since_epoch() - day ), 100 ); - if( itr != by_key_idx.end() && itr->key.base == base_id && itr->key.quote == quote_id && itr->key.seconds == bucket_size ) + result.latest = trades[0].price; + + for ( market_trade t: trades ) { - auto trades = get_trade_history( base, quote, now, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 ); + result.base_volume += t.value; + result.quote_volume += t.amount; + } - if (assets[0]->id == base_id) - { - result.latest = trades[0].price; - result.percent_change = ( result.latest / ( price_to_real( itr->open_quote, assets[1]->precision ) / price_to_real( itr->open_base, assets[0]->precision ) ) - 1 ) * 100; - //result.lowest_ask = price_to_real( itr->low_quote, assets[1]->precision ) / price_to_real( itr->low_base, assets[0]->precision ); - //result.highest_bid = price_to_real( itr->high_quote, assets[1]->precision ) / price_to_real( itr->high_base, assets[0]->precision ); - result.lowest_ask = orders.asks[0].first; - result.highest_bid = orders.bids[0].first; - } - else - { - result.latest = trades[0].price; - result.percent_change = ( result.latest / ( price_to_real( itr->open_base, assets[1]->precision ) / price_to_real( itr->open_quote, assets[0]->precision ) ) - 1) * 100; - //result.lowest_ask = price_to_real( itr->low_base, assets[1]->precision ) / price_to_real( itr->low_quote, assets[0]->precision ); - //result.highest_bid = price_to_real( itr->high_base, assets[1]->precision ) / price_to_real( itr->high_quote, assets[0]->precision ); - result.lowest_ask = orders.bids[0].first; - result.highest_bid = orders.asks[0].first; - } + while (trades.size() == 100) + { + trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - day ), 100 ); for ( market_trade t: trades ) { - result.base_volume += t.amount; - result.quote_volume += t.value; + result.base_volume += t.value; + result.quote_volume += t.amount; } + } - while (trades.size() == 100) - { - for ( market_trade t: trades ) - { - result.base_volume += t.amount; - result.quote_volume += t.value; - } - - trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 ); - } + trades = get_trade_history( base, quote, trades.back().date, fc::time_point_sec(), 1 ); + result.percent_change = trades.size() > 0 ? ( ( result.latest / trades.back().price ) - 1 ) * 100 : 0; + //if (assets[0]->id == base_id) + { + result.lowest_ask = orders.asks[0].price; + result.highest_bid = orders.bids[0].price; } return result; @@ -1111,19 +1094,19 @@ market_volume database_api_impl::get_24_volume( const string& base, const string for ( market_trade t: trades ) { - result.base_volume += t.amount; - result.quote_volume += t.value; + result.base_volume += t.value; + result.quote_volume += t.amount; } while (trades.size() == 100) { + trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 ); + for ( market_trade t: trades ) { - result.base_volume += t.amount; - result.quote_volume += t.value; + result.base_volume += t.value; + result.quote_volume += t.amount; } - - trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 ); } return result; @@ -1137,6 +1120,7 @@ order_book database_api::get_order_book( const string& base, const string& quote order_book database_api_impl::get_order_book( const string& base, const string& quote, unsigned limit )const { + using boost::multiprecision::uint128_t; FC_ASSERT( limit <= 50 ); order_book result; @@ -1156,21 +1140,28 @@ order_book database_api_impl::get_order_book( const string& base, const string& auto price_to_real = [&]( const price& p ) { if( p.base.asset_id == base_id ) - return asset_to_real( p.quote, assets[1]->precision ) / asset_to_real( p.base, assets[0]->precision ); + return asset_to_real( p.base, assets[0]->precision ) / asset_to_real( p.quote, assets[1]->precision ); else - return asset_to_real( p.base, assets[1]->precision ) / asset_to_real( p.quote, assets[0]->precision ); + return asset_to_real( p.quote, assets[0]->precision ) / asset_to_real( p.base, assets[1]->precision ); }; - for( const auto& o : orders ) { + for( const auto& o : orders ) + { if( o.sell_price.base.asset_id == base_id ) { - result.asks.push_back( std::make_pair( price_to_real(o.sell_price), - asset_to_real(o.sell_price.base, assets[0]->precision)) ); + order ord; + ord.price = price_to_real( o.sell_price ); + ord.quote = asset_to_real( share_type( ( uint128_t( o.for_sale.value ) * o.sell_price.quote.amount.value ) / o.sell_price.base.amount.value ), assets[1]->precision ); + ord.base = asset_to_real( o.for_sale, assets[0]->precision ); + result.bids.push_back( ord ); } else { - result.bids.push_back( std::make_pair( price_to_real(o.sell_price), - asset_to_real(o.sell_price.quote, assets[0]->precision ) ) ); + order ord; + ord.price = price_to_real( o.sell_price ); + ord.quote = asset_to_real( o.for_sale, assets[1]->precision ); + ord.base = asset_to_real( share_type( ( uint64_t( o.for_sale.value ) * o.sell_price.quote.amount.value ) / o.sell_price.base.amount.value ), assets[0]->precision ); + result.asks.push_back( ord ); } } @@ -1225,13 +1216,13 @@ vector database_api_impl::get_trade_history( const string& base, if( assets[0]->id == itr->op.receives.asset_id ) { - trade.amount = price_to_real( itr->op.receives.amount, assets[0]->precision ); - trade.value = price_to_real( itr->op.pays.amount, assets[1]->precision ); + trade.amount = price_to_real( itr->op.pays.amount, assets[1]->precision ); + trade.value = price_to_real( itr->op.receives.amount, assets[0]->precision ); } else { - trade.amount = price_to_real( itr->op.pays.amount, assets[0]->precision ); - trade.value = price_to_real( itr->op.receives.amount, assets[1]->precision ); + trade.amount = price_to_real( itr->op.receives.amount, assets[1]->precision ); + trade.value = price_to_real( itr->op.pays.amount, assets[0]->precision ); } trade.date = itr->time; diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 456e0bae..95ce16d6 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -64,40 +64,47 @@ using namespace std; class database_api_impl; +struct order +{ + double price; + double quote; + double base; +}; + struct order_book { - string base; - string quote; - vector< pair > bids; - vector< pair > asks; + string base; + string quote; + vector< order > bids; + vector< order > asks; }; struct market_ticker { - string base; - string quote; - double latest; - double lowest_ask; - double highest_bid; - double percent_change; - double base_volume; - double quote_volume; + string base; + string quote; + double latest; + double lowest_ask; + double highest_bid; + double percent_change; + double base_volume; + double quote_volume; }; struct market_volume { - string base; - string quote; - double base_volume; - double quote_volume; + string base; + string quote; + double base_volume; + double quote_volume; }; struct market_trade { - fc::time_point_sec date; - double price; - double amount; - double value; + fc::time_point_sec date; + double price; + double amount; + double value; }; /** @@ -116,7 +123,7 @@ class database_api ///////////// // Objects // ///////////// - + /** * @brief Get the objects corresponding to the provided IDs * @param ids IDs of the objects to retrieve @@ -202,7 +209,7 @@ class database_api ////////// // Keys // ////////// - + vector> get_key_references( vector key )const; ////////////// @@ -465,9 +472,9 @@ class database_api */ map lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const; - - /// WORKERS - + + /// WORKERS + /** * Return the worker objects associated with this account. */ @@ -515,7 +522,7 @@ class database_api bool verify_authority( const signed_transaction& trx )const; /** - * @return true if the signers have enough authority to authorize an account + * @return true if the signers have enough authority to authorize an account */ bool verify_account_authority( const string& name_or_id, const flat_set& signers )const; @@ -553,6 +560,8 @@ class database_api }; } } + +FC_REFLECT( graphene::app::order, (price)(quote)(base) ); FC_REFLECT( graphene::app::order_book, (base)(quote)(bids)(asks) ); FC_REFLECT( graphene::app::market_ticker, (base)(quote)(latest)(lowest_ask)(highest_bid)(percent_change)(base_volume)(quote_volume) ); FC_REFLECT( graphene::app::market_volume, (base)(quote)(base_volume)(quote_volume) ); diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index cdb9036e..fcf59a2e 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -849,6 +849,51 @@ class wallet_api uint32_t timeout_sec = 0, bool fill_or_kill = false, bool broadcast = false); + + /** Place a limit order attempting to sell one asset for another. + * + * This API call abstracts away some of the details of the sell_asset call to be more + * user friendly. All orders placed with sell never timeout and will not be killed if they + * cannot be filled immediately. If you wish for one of these parameters to be different, + * then sell_asset should be used instead. + * + * @param seller_account the account providing the asset being sold, and which will + * receive the processed of the sale. + * @param base The name or id of the asset to sell. + * @param quote The name or id of the asset to recieve. + * @param rate The rate in base:quote at which you want to sell. + * @param amount The amount of base you want to sell. + * @param broadcast true to broadcast the transaction on the network. + * @returns The signed transaction selling the funds. + */ + signed_transaction sell( string seller_account, + string base, + string quote, + double rate, + double amount, + bool broadcast ); + + /** Place a limit order attempting to buy one asset with another. + * + * This API call abstracts away some of the details of the sell_asset call to be more + * user friendly. All orders placed with buy never timeout and will not be killed if they + * cannot be filled immediately. If you wish for one of these parameters to be different, + * then sell_asset should be used instead. + * + * @param buyer_account The account buying the asset for another asset. + * @param base The name or id of the asset to buy. + * @param quote The name or id of the assest being offered as payment. + * @param rate The rate in base:quote at which you want to buy. + * @param amount the amount of base you want to buy. + * @param broadcast true to broadcast the transaction on the network. + * @param The signed transaction selling the funds. + */ + signed_transaction buy( string buyer_account, + string base, + string quote, + double rate, + double amount, + bool broadcast ); /** Borrow an asset or update the debt/collateral ratio for the loan. * @@ -1401,6 +1446,8 @@ class wallet_api const approval_delta& delta, bool broadcast /* = false */ ); + + order_book get_order_book( const string& base, const string& quote, unsigned limit = 50); void dbg_make_uia(string creator, string symbol); void dbg_make_mia(string creator, string symbol); @@ -1517,6 +1564,8 @@ FC_API( graphene::wallet::wallet_api, (upgrade_account) (create_account_with_brain_key) (sell_asset) + (sell) + (buy) (borrow_asset) (cancel_order) (transfer) @@ -1589,4 +1638,5 @@ FC_API( graphene::wallet::wallet_api, (blind_transfer) (blind_history) (receive_blind_transfer) + (get_order_book) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 3dd8b9e3..5b5d6138 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -280,7 +280,7 @@ private: auto iter = _wallet.pending_witness_registrations.find(witness_name); FC_ASSERT(iter != _wallet.pending_witness_registrations.end()); std::string wif_key = iter->second; - + // get the list key id this key is registered with in the chain fc::optional witness_private_key = wif_to_key(wif_key); FC_ASSERT(witness_private_key); @@ -316,12 +316,12 @@ private: if( optional_account ) claim_registered_account(*optional_account); } - + if (!_wallet.pending_witness_registrations.empty()) { // make a vector of the owner accounts for witnesses pending registration std::vector pending_witness_names = boost::copy_range >(boost::adaptors::keys(_wallet.pending_witness_registrations)); - + // look up the owners on the blockchain std::vector> owner_account_objects = _remote_db->lookup_account_names(pending_witness_names); @@ -365,9 +365,9 @@ private: // if the user executes the same command twice in quick succession, // we might generate the same transaction id, and cause the second - // transaction to be rejected. This can be avoided by altering the + // transaction to be rejected. This can be avoided by altering the // second transaction slightly (bumping up the expiration time by - // a second). Keep track of recent transaction ids we've generated + // a second). Keep track of recent transaction ids we've generated // so we can know if we need to do this struct recently_generated_transaction_record { @@ -377,7 +377,7 @@ private: struct timestamp_index{}; typedef boost::multi_index_container, std::hash >, boost::multi_index::ordered_non_unique, @@ -686,7 +686,7 @@ public: if (!optional_private_key) FC_THROW("Invalid private key"); graphene::chain::public_key_type wif_pub_key = optional_private_key->get_public_key(); - + account_object account = get_account( account_name_or_id ); // make a list of all current public keys for the named account @@ -1042,7 +1042,7 @@ public: { try { int active_key_index = find_first_unused_derived_key_index(owner_privkey); fc::ecc::private_key active_privkey = derive_private_key( key_to_wif(owner_privkey), active_key_index); - + int memo_key_index = find_first_unused_derived_key_index(active_privkey); fc::ecc::private_key memo_privkey = derive_private_key( key_to_wif(active_privkey), memo_key_index); @@ -1350,7 +1350,7 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (authorizing_account)(account_to_list)(new_listing_status)(broadcast) ) } - signed_transaction create_committee_member(string owner_account, string url, + signed_transaction create_committee_member(string owner_account, string url, bool broadcast /* = false */) { try { @@ -1370,7 +1370,7 @@ public: witness_object get_witness(string owner_account) { - try + try { fc::optional witness_id = maybe_id(owner_account); if (witness_id) @@ -1405,7 +1405,7 @@ public: committee_member_object get_committee_member(string owner_account) { - try + try { fc::optional committee_member_id = maybe_id(owner_account); if (committee_member_id) @@ -1460,7 +1460,7 @@ public: tx.operations.push_back( witness_create_op ); set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); tx.validate(); - + _wallet.pending_witness_registrations[owner_account] = key_to_wif(witness_private_key); return sign_transaction( tx, broadcast ); @@ -1774,7 +1774,7 @@ public: if (account_object_to_modify.options.num_witness == desired_number_of_witnesses && account_object_to_modify.options.num_committee == desired_number_of_committee_members) - FC_THROW("Account ${account} is already voting for ${witnesses} witnesses and ${committee_members} committee_members", + FC_THROW("Account ${account} is already voting for ${witnesses} witnesses and ${committee_members} committee_members", ("account", account_to_modify)("witnesses", desired_number_of_witnesses)("committee_members",desired_number_of_witnesses)); account_object_to_modify.options.num_witness = desired_number_of_witnesses; account_object_to_modify.options.num_committee = desired_number_of_committee_members; @@ -2161,11 +2161,94 @@ public: for( auto& r : records ) { asset_object as = get_asset( r.amount.asset_id ); - ss << fc::get_approximate_relative_time_string( r.date ) + ss << fc::get_approximate_relative_time_string( r.date ) << " " << as.amount_to_pretty_string( r.amount ) << " " << r.from_label << " => " << r.to_label << " " << r.memo <<"\n"; } return ss.str(); }; + m["get_order_book"] = [this](variant result, const fc::variants& a) + { + auto orders = result.as(); + auto bids = orders.bids; + auto asks = orders.asks; + std::stringstream ss; + std::stringstream sum_stream; + sum_stream << "Sum(" << orders.base << ')'; + double bid_sum = 0; + double ask_sum = 0; + const int spacing = 20; + + auto prettify_num = [&]( double n ) + { + //ss << n; + if (abs( round( n ) - n ) < 0.00000000001 ) + { + ss << setiosflags( !ios::fixed ) << (int) n; + } + else if (n - floor(n) < 0.000001) + { + ss << setiosflags( ios::fixed ) << setprecision(10) << n; + } + else + { + ss << setiosflags( ios::fixed ) << setprecision(6) << n; + } + }; + + ss << setprecision( 8 ) << setiosflags( ios::fixed ) << setiosflags( ios::left ); + + ss << ' ' << setw( (spacing * 4) + 6 ) << "BUY ORDERS" << "SELL ORDERS\n" + << ' ' << setw( spacing + 1 ) << "Price" << setw( spacing ) << orders.quote << ' ' << setw( spacing ) + << orders.base << ' ' << setw( spacing ) << sum_stream.str() + << " " << setw( spacing + 1 ) << "Price" << setw( spacing ) << orders.quote << ' ' << setw( spacing ) + << orders.base << ' ' << setw( spacing ) << sum_stream.str() + << "\n=====================================================================================" + << "|=====================================================================================\n"; + + for (int i = 0; i < bids.size() || i < asks.size() ; i++) + { + if ( i < bids.size() ) + { + bid_sum += bids[i].base; + ss << ' ' << setw( spacing ); + prettify_num( bids[i].price ); + ss << ' ' << setw( spacing ); + prettify_num( bids[i].quote ); + ss << ' ' << setw( spacing ); + prettify_num( bids[i].base ); + ss << ' ' << setw( spacing ); + prettify_num( bid_sum ); + ss << ' '; + } + else + { + ss << setw( (spacing * 4) + 5 ) << ' '; + } + + ss << '|'; + + if ( i < asks.size() ) + { + ask_sum += asks[i].base; + ss << ' ' << setw( spacing ); + prettify_num( asks[i].price ); + ss << ' ' << setw( spacing ); + prettify_num( asks[i].quote ); + ss << ' ' << setw( spacing ); + prettify_num( asks[i].base ); + ss << ' ' << setw( spacing ); + prettify_num( ask_sum ); + } + + ss << '\n'; + } + + ss << endl + << "Buy Total: " << bid_sum << ' ' << orders.base << endl + << "Sell Total: " << ask_sum << ' ' << orders.base << endl; + + return ss.str(); + }; return m; } @@ -2392,7 +2475,7 @@ public: dbg_make_uia(master.name, "SHILL"); } catch(...) {/* Ignore; the asset probably already exists.*/} - fc::time_point start = fc::time_point::now(); + fc::time_point start = fc::time_point::now(); for( int i = 0; i < number_of_accounts; ++i ) { std::ostringstream brain_key; @@ -2474,7 +2557,7 @@ std::string operation_printer::operator()(const T& op)const //op.get_balance_delta( acc, result ); auto a = wallet.get_asset( op.fee.asset_id ); auto payer = wallet.get_account( op.fee_payer() ); - + string op_name = fc::get_typename::name(); if( op_name.find_last_of(':') != string::npos ) op_name.erase(0, op_name.find_last_of(':')+1); @@ -2494,7 +2577,7 @@ std::string operation_printer::operator()(const transfer_from_blind_operation& o auto a = wallet.get_asset( op.fee.asset_id ); auto receiver = wallet.get_account( op.to ); - out << receiver.name + out << receiver.name << " received " << a.amount_to_pretty_string( op.amount ) << " from blinded balance"; return ""; } @@ -2504,7 +2587,7 @@ std::string operation_printer::operator()(const transfer_to_blind_operation& op) auto a = wallet.get_asset( op.amount.asset_id ); auto sender = wallet.get_account( op.from ); - out << sender.name + out << sender.name << " sent " << a.amount_to_pretty_string( op.amount ) << " to " << op.outputs.size() << " blinded balance" << (op.outputs.size()>1?"s":"") << " fee: " << fa.amount_to_pretty_string( op.fee ); return ""; @@ -2641,7 +2724,7 @@ vector wallet_api::get_account_history(string name, int limit) while( limit > 0 ) { operation_history_id_type start; - if( result.size() ) + if( result.size() ) { start = result.back().op.id; start = start + 1; @@ -3077,7 +3160,7 @@ signed_transaction wallet_api::whitelist_account(string authorizing_account, return my->whitelist_account(authorizing_account, account_to_list, new_listing_status, broadcast); } -signed_transaction wallet_api::create_committee_member(string owner_account, string url, +signed_transaction wallet_api::create_committee_member(string owner_account, string url, bool broadcast /* = false */) { return my->create_committee_member(owner_account, url, broadcast); @@ -3183,7 +3266,7 @@ signed_transaction wallet_api::set_desired_witness_and_committee_member_count(st uint16_t desired_number_of_committee_members, bool broadcast /* = false */) { - return my->set_desired_witness_and_committee_member_count(account_to_modify, desired_number_of_witnesses, + return my->set_desired_witness_and_committee_member_count(account_to_modify, desired_number_of_witnesses, desired_number_of_committee_members, broadcast); } @@ -3522,7 +3605,7 @@ vector< signed_transaction > wallet_api_impl::import_balance( string name_or_id, if( broadcast ) _remote_net_broadcast->broadcast_transaction(signed_tx); } - + return result; } FC_CAPTURE_AND_RETHROW( (name_or_id) ) } @@ -3552,6 +3635,28 @@ signed_transaction wallet_api::sell_asset(string seller_account, symbol_to_receive, expiration, fill_or_kill, broadcast); } +signed_transaction wallet_api::sell( string seller_account, + string base, + string quote, + double rate, + double amount, + bool broadcast ) +{ + return my->sell_asset( seller_account, std::to_string( amount ), base, + std::to_string( rate * amount ), quote, 0, false, broadcast ); +} + +signed_transaction wallet_api::buy( string buyer_account, + string base, + string quote, + double rate, + double amount, + bool broadcast ) +{ + return my->sell_asset( buyer_account, std::to_string( rate * amount ), quote, + std::to_string( amount ), base, 0, false, broadcast ); +} + signed_transaction wallet_api::borrow_asset(string seller_name, string amount_to_sell, string asset_symbol, string amount_of_collateral, bool broadcast) { @@ -3675,7 +3780,7 @@ vector wallet_api::get_blind_balances( string key_or_label ) } blind_confirmation wallet_api::transfer_from_blind( string from_blind_account_key_or_label, - string to_account_id_or_name, + string to_account_id_or_name, string amount_in, string symbol, bool broadcast ) @@ -3694,7 +3799,7 @@ blind_confirmation wallet_api::transfer_from_blind( string from_blind_account_ke auto conf = blind_transfer_help( from_blind_account_key_or_label, - from_blind_account_key_or_label, + from_blind_account_key_or_label, blind_in, symbol, false, true/*to_temp*/ ); FC_ASSERT( conf.outputs.size() > 0 ); @@ -3709,24 +3814,24 @@ blind_confirmation wallet_api::transfer_from_blind( string from_blind_account_ke conf.trx.operations.push_back(from_blind); ilog( "about to validate" ); conf.trx.validate(); - + if( broadcast && conf.outputs.size() == 2 ) { - + // Save the change blind_confirmation::output conf_output; blind_confirmation::output change_output = conf.outputs[0]; - + // The wallet must have a private key for confirmation.to, this is used to decrypt the memo public_key_type from_key = get_public_key(from_blind_account_key_or_label); conf_output.confirmation.to = from_key; conf_output.confirmation.one_time_key = change_output.confirmation.one_time_key; conf_output.confirmation.encrypted_memo = change_output.confirmation.encrypted_memo; conf_output.confirmation_receipt = conf_output.confirmation; - //try { + //try { receive_blind_transfer( conf_output.confirmation_receipt, from_blind_account_key_or_label, "@"+to_account.name ); //} catch ( ... ){} } - + ilog( "about to broadcast" ); conf.trx = sign_transaction( conf.trx, broadcast ); @@ -3747,7 +3852,7 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label, string symbol, bool broadcast, bool to_temp ) -{ +{ blind_confirmation confirm; try { @@ -3791,7 +3896,7 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label, blinding_factors.push_back( start->data.blinding_factor ); total_amount += start->amount; - if( total_amount >= amount + blind_tr.fee ) + if( total_amount >= amount + blind_tr.fee ) break; } ++start; @@ -3859,7 +3964,7 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label, conf_output.decrypted_memo.amount = change; conf_output.decrypted_memo.blinding_factor = change_blind_factor; conf_output.decrypted_memo.commitment = change_out.commitment; - conf_output.decrypted_memo.check = from_secret._hash[0]; + conf_output.decrypted_memo.check = from_secret._hash[0]; conf_output.confirmation.one_time_key = one_time_key.get_public_key(); conf_output.confirmation.to = from_key; conf_output.confirmation.encrypted_memo = fc::aes_encrypt( from_secret, fc::raw::pack( conf_output.decrypted_memo ) ); @@ -3877,7 +3982,7 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label, conf_output.decrypted_memo.amount = amount; conf_output.decrypted_memo.blinding_factor = blind_factor; conf_output.decrypted_memo.commitment = to_out.commitment; - conf_output.decrypted_memo.check = secret._hash[0]; + conf_output.decrypted_memo.check = secret._hash[0]; conf_output.confirmation.one_time_key = one_time_key.get_public_key(); conf_output.confirmation.to = to_key; conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) ); @@ -3887,9 +3992,9 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label, confirm.outputs.push_back( conf_output ); /** commitments must be in sorted order */ - std::sort( blind_tr.outputs.begin(), blind_tr.outputs.end(), + std::sort( blind_tr.outputs.begin(), blind_tr.outputs.end(), [&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } ); - std::sort( blind_tr.inputs.begin(), blind_tr.inputs.end(), + std::sort( blind_tr.inputs.begin(), blind_tr.inputs.end(), [&]( const blind_input& a, const blind_input& b ){ return a.commitment < b.commitment; } ); confirm.trx.operations.emplace_back( std::move(blind_tr) ); @@ -3914,10 +4019,10 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label, * Transfers a public balance from @from to one or more blinded balances using a * stealth transfer. */ -blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name, +blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name, string asset_symbol, /** map from key or label to amount */ - vector> to_amounts, + vector> to_amounts, bool broadcast ) { try { FC_ASSERT( !is_locked() ); @@ -3948,7 +4053,7 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name auto amount = asset_obj->amount_from_string(item.second); total_amount += amount; - + fc::ecc::public_key to_pub_key = to_key; blind_output out; @@ -3964,7 +4069,7 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name conf_output.decrypted_memo.amount = amount; conf_output.decrypted_memo.blinding_factor = blind_factor; conf_output.decrypted_memo.commitment = out.commitment; - conf_output.decrypted_memo.check = secret._hash[0]; + conf_output.decrypted_memo.check = secret._hash[0]; conf_output.confirmation.one_time_key = one_time_key.get_public_key(); conf_output.confirmation.to = to_key; conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) ); @@ -3978,7 +4083,7 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name bop.blinding_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() ); /** commitments must be in sorted order */ - std::sort( bop.outputs.begin(), bop.outputs.end(), + std::sort( bop.outputs.begin(), bop.outputs.end(), [&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } ); confirm.trx.operations.push_back( bop ); @@ -4024,7 +4129,7 @@ blind_receipt wallet_api::receive_blind_transfer( string confirmation_receipt, s result.to_key = *conf.to; result.to_label = get_key_label( result.to_key ); - if( memo.from ) + if( memo.from ) { result.from_key = *memo.from; result.from_label = get_key_label( result.from_key ); @@ -4044,7 +4149,7 @@ blind_receipt wallet_api::receive_blind_transfer( string confirmation_receipt, s // confirm the amount matches the commitment (verify the blinding factor) auto commtiment_test = fc::ecc::blind( memo.blinding_factor, memo.amount.amount.value ); FC_ASSERT( fc::ecc::verify_sum( {commtiment_test}, {memo.commitment}, 0 ) ); - + blind_balance bal; bal.amount = memo.amount; bal.to = *conf.to; @@ -4062,7 +4167,7 @@ blind_receipt wallet_api::receive_blind_transfer( string confirmation_receipt, s auto child_key_itr = owner.key_auths.find( child_pubkey ); if( child_key_itr != owner.key_auths.end() ) my->_keys[child_key_itr->first] = key_to_wif( child_priv_key ); - + // my->_wallet.blinded_balances[memo.amount.asset_id][bal.to].push_back( bal ); result.date = fc::time_point::now(); @@ -4079,7 +4184,7 @@ vector wallet_api::blind_history( string key_or_account ) vector result; auto pub_key = get_public_key( key_or_account ); - if( pub_key == public_key_type() ) + if( pub_key == public_key_type() ) return vector(); for( auto& r : my->_wallet.blind_receipts ) @@ -4091,6 +4196,11 @@ vector wallet_api::blind_history( string key_or_account ) return result; } +order_book wallet_api::get_order_book( const string& base, const string& quote, unsigned limit ) +{ + return( my->_remote_db->get_order_book( base, quote, limit ) ); +} + signed_block_with_info::signed_block_with_info( const signed_block& block ) : signed_block( block ) { From 92cfb96c618e1c051dd7a7224e5392d56c496c06 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Fri, 26 Feb 2016 13:35:19 -0500 Subject: [PATCH 04/34] Test serialization of extensions #599 --- tests/tests/serialization_tests.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/tests/serialization_tests.cpp b/tests/tests/serialization_tests.cpp index 84fced8e..fb87c4c4 100644 --- a/tests/tests/serialization_tests.cpp +++ b/tests/tests/serialization_tests.cpp @@ -122,4 +122,30 @@ BOOST_AUTO_TEST_CASE( extended_public_key_type_test ) } } +BOOST_AUTO_TEST_CASE( extension_serialization_test ) +{ + try + { + buyback_account_options bbo; + bbo.asset_to_buy = asset_id_type(1000); + bbo.asset_to_buy_issuer = account_id_type(2000); + bbo.markets.emplace( asset_id_type() ); + bbo.markets.emplace( asset_id_type(777) ); + account_create_operation create_op = make_account( "rex" ); + create_op.registrar = account_id_type(1234); + create_op.extensions.value.buyback_options = bbo; + + auto packed = fc::raw::pack( create_op ); + account_create_operation unpacked = fc::raw::unpack(packed); + + ilog( "original: ${x}", ("x", create_op) ); + ilog( "unpacked: ${x}", ("x", unpacked) ); + } + catch ( const fc::exception& e ) + { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From a83af9208ea93cdbde9b9ee66eaba5babc7e78fc Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Fri, 26 Feb 2016 14:24:22 -0500 Subject: [PATCH 05/34] ext.hpp: Fix extension unpacking #599 --- libraries/chain/include/graphene/chain/protocol/ext.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/protocol/ext.hpp b/libraries/chain/include/graphene/chain/protocol/ext.hpp index 7e8636f5..ac775535 100644 --- a/libraries/chain/include/graphene/chain/protocol/ext.hpp +++ b/libraries/chain/include/graphene/chain/protocol/ext.hpp @@ -82,6 +82,8 @@ void operator<<( Stream& stream, const graphene::chain::extension& value ) fc::reflector::visit( read_vtor ); } + + template< typename Stream, typename T > struct graphene_extension_unpack_visitor { @@ -108,7 +110,7 @@ struct graphene_extension_unpack_visitor { if( (count_left > 0) && (which == next_which) ) { - Member temp; + typename Member::value_type temp; fc::raw::unpack( stream, temp ); (value.*member) = temp; --count_left; From 529f0bef0eeaef6424c6ff10cbb6415c35b7fe03 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Fri, 26 Feb 2016 15:14:20 -0500 Subject: [PATCH 06/34] Bump fc --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index 83b4de06..38419164 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 83b4de067a6c99704a6d21d6dfc8b1e838ddcaf7 +Subproject commit 38419164b6f1ade468bf4b1d27929b3ac2503b6e From 4138ec29cce8d008a3ae99b20991899f61f2a02a Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Mon, 7 Mar 2016 13:40:47 -0500 Subject: [PATCH 07/34] account_evaluator.cpp: Remove redundant vote check #611 --- libraries/chain/account_evaluator.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 1f29bb0a..e5fcc454 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -85,26 +85,16 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio if( op.extensions.value.buyback_options.valid() ) evaluate_buyback_account_options( d, *op.extensions.value.buyback_options ); - uint32_t max_vote_id = global_props.next_available_vote_id; - - FC_ASSERT( op.options.num_witness <= chain_params.maximum_witness_count, + FC_ASSERT( op.options.num_witness <= chain_params.maximum_witness_count, "Voted for more witnesses than currently allowed (${c})", ("c", chain_params.maximum_witness_count) ); - FC_ASSERT( op.options.num_committee <= chain_params.maximum_committee_count, "Voted for more committee members than currently allowed (${c})", ("c", chain_params.maximum_committee_count) ); - safe counts[vote_id_type::VOTE_TYPE_COUNT]; + uint32_t max_vote_id = global_props.next_available_vote_id; for( auto id : op.options.votes ) { FC_ASSERT( id < max_vote_id ); - counts[id.type()]++; } - FC_ASSERT(counts[vote_id_type::witness] <= op.options.num_witness, - "", - ("count", counts[vote_id_type::witness])("num", op.options.num_witness)); - FC_ASSERT(counts[vote_id_type::committee] <= op.options.num_committee, - "", - ("count", counts[vote_id_type::committee])("num", op.options.num_committee)); auto& acnt_indx = d.get_index_type(); if( op.name.size() ) From 241a7b0c3ad3e330c09b0ff2d959a92d51c77254 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Mon, 7 Mar 2016 13:49:23 -0500 Subject: [PATCH 08/34] account_evaluator.cpp: Refactor verify_account_votes() into own method #611 --- libraries/chain/account_evaluator.cpp | 51 +++++++++++++-------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index e5fcc454..b44597e4 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -54,6 +54,28 @@ void verify_authority_accounts( const database& db, const authority& a ) } } +void verify_account_votes( const database& db, const account_options& options ) +{ + // ensure account's votes satisfy requirements + // NB only the part of vote checking that requires chain state is here, + // the rest occurs in account_options::validate() + + const auto& gpo = db.get_global_properties(); + const auto& chain_params = gpo.parameters; + + FC_ASSERT( options.num_witness <= chain_params.maximum_witness_count, + "Voted for more witnesses than currently allowed (${c})", ("c", chain_params.maximum_witness_count) ); + FC_ASSERT( options.num_committee <= chain_params.maximum_committee_count, + "Voted for more committee members than currently allowed (${c})", ("c", chain_params.maximum_committee_count) ); + + uint32_t max_vote_id = gpo.next_available_vote_id; + for( auto id : options.votes ) + { + FC_ASSERT( id < max_vote_id ); + } +} + + void_result account_create_evaluator::do_evaluate( const account_create_operation& op ) { try { database& d = db(); @@ -67,9 +89,6 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio FC_ASSERT( fee_paying_account->is_lifetime_member(), "Only Lifetime members may register an account." ); FC_ASSERT( op.referrer(d).is_member(d.head_block_time()), "The referrer must be either a lifetime or annual subscriber." ); - const auto& global_props = d.get_global_properties(); - const auto& chain_params = global_props.parameters; - try { verify_authority_accounts( d, op.owner ); @@ -84,17 +103,7 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio evaluate_special_authority( d, *op.extensions.value.active_special_authority ); if( op.extensions.value.buyback_options.valid() ) evaluate_buyback_account_options( d, *op.extensions.value.buyback_options ); - - FC_ASSERT( op.options.num_witness <= chain_params.maximum_witness_count, - "Voted for more witnesses than currently allowed (${c})", ("c", chain_params.maximum_witness_count) ); - FC_ASSERT( op.options.num_committee <= chain_params.maximum_committee_count, - "Voted for more committee members than currently allowed (${c})", ("c", chain_params.maximum_committee_count) ); - - uint32_t max_vote_id = global_props.next_available_vote_id; - for( auto id : op.options.votes ) - { - FC_ASSERT( id < max_vote_id ); - } + verify_account_votes( d, op.options ); auto& acnt_indx = d.get_index_type(); if( op.name.size() ) @@ -214,8 +223,6 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio FC_ASSERT( !o.extensions.value.active_special_authority.valid() ); } - const auto& chain_params = d.get_global_properties().parameters; - try { if( o.owner ) verify_authority_accounts( d, *o.owner ); @@ -231,16 +238,8 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio acnt = &o.account(d); - if( o.new_options ) - { - FC_ASSERT( o.new_options->num_witness <= chain_params.maximum_witness_count ); - FC_ASSERT( o.new_options->num_committee <= chain_params.maximum_committee_count ); - uint32_t max_vote_id = d.get_global_properties().next_available_vote_id; - for( auto id : o.new_options->votes ) - { - FC_ASSERT( id < max_vote_id ); - } - } + if( o.new_options.valid() ) + verify_account_votes( d, *o.new_options ); return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } From 3c6f4ce22359f08e1436c3a645a03b3d95e56521 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 9 Mar 2016 14:37:33 +0100 Subject: [PATCH 09/34] Fix price feed expiration check, fix #540 --- libraries/chain/include/graphene/chain/asset_object.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index f893f34a..3bde61b3 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -212,7 +212,7 @@ namespace graphene { namespace chain { time_point_sec feed_expiration_time()const { return current_feed_publication_time + options.feed_lifetime_sec; } bool feed_is_expired(time_point_sec current_time)const - { return feed_expiration_time() >= current_time; } + { return feed_expiration_time() <= current_time; } void update_median_feeds(time_point_sec current_time); }; From f1cd2c245489d66adc9de2468acaf389d9fad0b4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 10 Mar 2016 06:12:58 +0800 Subject: [PATCH 10/34] Add hard fork logic for #615 feed expiration check issue, fix #540 --- libraries/chain/db_update.cpp | 7 ++++++- libraries/chain/hardfork.d/615.hf | 4 ++++ libraries/chain/include/graphene/chain/asset_object.hpp | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 libraries/chain/hardfork.d/615.hf diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index da96eef9..aa4d5969 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -434,7 +434,12 @@ void database::update_expired_feeds() assert( a.is_market_issued() ); const asset_bitasset_data_object& b = a.bitasset_data(*this); - if( b.feed_is_expired(head_block_time()) ) + bool feed_is_expired; + if( head_block_time() < HARDFORK_615_TIME ) + feed_is_expired = b.feed_is_expired_before_hardfork_615( head_block_time() ); + else + feed_is_expired = b.feed_is_expired( head_block_time() ); + if( feed_is_expired ) { modify(b, [this](asset_bitasset_data_object& a) { a.update_median_feeds(head_block_time()); diff --git a/libraries/chain/hardfork.d/615.hf b/libraries/chain/hardfork.d/615.hf new file mode 100644 index 00000000..a5599bbc --- /dev/null +++ b/libraries/chain/hardfork.d/615.hf @@ -0,0 +1,4 @@ +// #615 Fix price feed expiration check, so websocket server will never spam too much data +#ifndef HARDFORK_615_TIME +#define HARDFORK_615_TIME (fc::time_point_sec( 1457550000 )) +#endif diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 3bde61b3..28d5974e 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -211,6 +211,8 @@ namespace graphene { namespace chain { time_point_sec feed_expiration_time()const { return current_feed_publication_time + options.feed_lifetime_sec; } + bool feed_is_expired_before_hardfork_615(time_point_sec current_time)const + { return feed_expiration_time() >= current_time; } bool feed_is_expired(time_point_sec current_time)const { return feed_expiration_time() <= current_time; } void update_median_feeds(time_point_sec current_time); From 403d3001f6451d1ef46cd832d11d07106eeefc40 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Thu, 10 Mar 2016 17:33:14 -0500 Subject: [PATCH 11/34] Add a command-line option to witness_node, --disable-permessage-deflate to prevent the websocket server from allowing compression on clients that support it. #619 --- libraries/app/application.cpp | 9 +++++++-- libraries/fc | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index d71f3867..6ab43885 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -180,7 +180,9 @@ namespace detail { if( !_options->count("rpc-endpoint") ) return; - _websocket_server = std::make_shared(); + bool enable_deflate_compression = _options->count("disable-permessage-deflate") == 0; + + _websocket_server = std::make_shared(enable_deflate_compression); _websocket_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){ auto wsc = std::make_shared(*c); @@ -207,7 +209,8 @@ namespace detail { } string password = _options->count("server-pem-password") ? _options->at("server-pem-password").as() : ""; - _websocket_tls_server = std::make_shared( _options->at("server-pem").as(), password ); + bool enable_deflate_compression = _options->count("disable-permessage-deflate") == 0; + _websocket_tls_server = std::make_shared( _options->at("server-pem").as(), password, enable_deflate_compression ); _websocket_tls_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){ auto wsc = std::make_shared(*c); @@ -900,6 +903,8 @@ void application::set_program_options(boost::program_options::options_descriptio ("checkpoint,c", bpo::value>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.") ("rpc-endpoint", bpo::value()->implicit_value("127.0.0.1:8090"), "Endpoint for websocket RPC to listen on") ("rpc-tls-endpoint", bpo::value()->implicit_value("127.0.0.1:8089"), "Endpoint for TLS websocket RPC to listen on") + ("disable-permessage-deflate", "Disable support for per-message deflate compression in the websocket servers " + "(--rpc-endpoint and --rpc-tls-endpoint), enabled by default") ("server-pem,p", bpo::value()->implicit_value("server.pem"), "The TLS certificate file for this server") ("server-pem-password,P", bpo::value()->implicit_value(""), "Password for this certificate") ("genesis-json", bpo::value(), "File to read Genesis State from") diff --git a/libraries/fc b/libraries/fc index 38419164..21045dde 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 38419164b6f1ade468bf4b1d27929b3ac2503b6e +Subproject commit 21045dde5faa8fcf5f43b97c85f9df210317633b From 36164263f4cceda85ec92568d48016a48f7ad9c9 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Mon, 14 Mar 2016 18:27:28 -0400 Subject: [PATCH 12/34] Make websocket compression disabled by default, since it causes problems in Chrome. Change the command line option to --enable-permessage-deflate to override. #619 --- libraries/app/application.cpp | 8 ++++---- libraries/fc | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 6ab43885..64464488 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -180,7 +180,7 @@ namespace detail { if( !_options->count("rpc-endpoint") ) return; - bool enable_deflate_compression = _options->count("disable-permessage-deflate") == 0; + bool enable_deflate_compression = _options->count("enable-permessage-deflate") != 0; _websocket_server = std::make_shared(enable_deflate_compression); @@ -209,7 +209,7 @@ namespace detail { } string password = _options->count("server-pem-password") ? _options->at("server-pem-password").as() : ""; - bool enable_deflate_compression = _options->count("disable-permessage-deflate") == 0; + bool enable_deflate_compression = _options->count("enable-permessage-deflate") != 0; _websocket_tls_server = std::make_shared( _options->at("server-pem").as(), password, enable_deflate_compression ); _websocket_tls_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){ @@ -903,8 +903,8 @@ void application::set_program_options(boost::program_options::options_descriptio ("checkpoint,c", bpo::value>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.") ("rpc-endpoint", bpo::value()->implicit_value("127.0.0.1:8090"), "Endpoint for websocket RPC to listen on") ("rpc-tls-endpoint", bpo::value()->implicit_value("127.0.0.1:8089"), "Endpoint for TLS websocket RPC to listen on") - ("disable-permessage-deflate", "Disable support for per-message deflate compression in the websocket servers " - "(--rpc-endpoint and --rpc-tls-endpoint), enabled by default") + ("enable-permessage-deflate", "Enable support for per-message deflate compression in the websocket servers " + "(--rpc-endpoint and --rpc-tls-endpoint), disabled by default") ("server-pem,p", bpo::value()->implicit_value("server.pem"), "The TLS certificate file for this server") ("server-pem-password,P", bpo::value()->implicit_value(""), "Password for this certificate") ("genesis-json", bpo::value(), "File to read Genesis State from") diff --git a/libraries/fc b/libraries/fc index 21045dde..622ff580 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 21045dde5faa8fcf5f43b97c85f9df210317633b +Subproject commit 622ff58039f2388433272a44fe416f5b8025589a From 7f4b40f57d7e6f308a1185f498918fbd66ae032c Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Wed, 9 Dec 2015 13:53:01 -0500 Subject: [PATCH 13/34] Improve index on account operation history - operations are now indexed by account and sequence for effecient traversal and query --- .../include/graphene/chain/account_object.hpp | 2 ++ .../chain/operation_history_object.hpp | 7 +++- .../account_history_plugin.cpp | 34 ++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 5067292d..9f0df9c8 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -47,6 +47,7 @@ namespace graphene { namespace chain { * Keep the most recent operation as a root pointer to a linked list of the transaction history. */ account_transaction_history_id_type most_recent_op; + uint32_t total_ops = 0; /** * When calculating votes it is necessary to know how much is stored in orders (and thus unavailable for @@ -342,6 +343,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (graphene::chain::object), (owner) (most_recent_op) + (total_ops) (total_core_in_orders) (lifetime_fees_paid) (pending_fees)(pending_vested_fees) diff --git a/libraries/chain/include/graphene/chain/operation_history_object.hpp b/libraries/chain/include/graphene/chain/operation_history_object.hpp index b1e9d834..4bf6b527 100644 --- a/libraries/chain/include/graphene/chain/operation_history_object.hpp +++ b/libraries/chain/include/graphene/chain/operation_history_object.hpp @@ -86,8 +86,13 @@ namespace graphene { namespace chain { public: static const uint8_t space_id = implementation_ids; static const uint8_t type_id = impl_account_transaction_history_object_type; + account_id_type account; /// the account this operation applies to operation_history_id_type operation_id; + uint32_t sequence = 0; /// the operation position within the given account account_transaction_history_id_type next; + + std::pair account_op()const { return std::tie( account, operation_id ); } + std::pair account_seq()const { return std::tie( account, sequence ); } }; } } // graphene::chain @@ -95,4 +100,4 @@ FC_REFLECT_DERIVED( graphene::chain::operation_history_object, (graphene::chain: (op)(result)(block_num)(trx_in_block)(op_in_trx)(virtual_op) ) FC_REFLECT_DERIVED( graphene::chain::account_transaction_history_object, (graphene::chain::object), - (operation_id)(next) ) + (account)(operation_id)(sequence)(next) ) diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index f45610fb..58d4293e 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -105,10 +105,13 @@ void account_history_plugin_impl::update_account_histories( const signed_block& const auto& stats_obj = account_id(db).statistics(db); const auto& ath = db.create( [&]( account_transaction_history_object& obj ){ obj.operation_id = oho.id; + obj.account = account_id; + obj.sequence = stats_obj.total_ops+1; obj.next = stats_obj.most_recent_op; }); db.modify( stats_obj, [&]( account_statistics_object& obj ){ obj.most_recent_op = ath.id; + obj.total_ops = ath.sequence; }); } } @@ -134,6 +137,35 @@ void account_history_plugin_impl::update_account_histories( const signed_block& } } // end namespace detail + +struct by_id; +struct by_seq; +struct by_op; +typedef multi_index_container< + account_transaction_history_object, + indexed_by< + ordered_unique< tag, + member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key< account_transaction_history_object, + member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>, + member< account_transaction_history_object, uint32_t, &account_transaction_history_object::sequence> + > + >, + ordered_unique< tag, + composite_key< account_transaction_history_object, + member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>, + member< account_transaction_history_object, operation_history_id_type, &account_transaction_history_object::operation_id> + > + > + > +> account_transaction_history_multi_index_type; + +typedef generic_index account_transaction_history_index; + + + + account_history_plugin::account_history_plugin() : my( new detail::account_history_plugin_impl(*this) ) { @@ -163,7 +195,7 @@ void account_history_plugin::plugin_initialize(const boost::program_options::var { database().applied_block.connect( [&]( const signed_block& b){ my->update_account_histories(b); } ); database().add_index< primary_index< simple_index< operation_history_object > > >(); - database().add_index< primary_index< simple_index< account_transaction_history_object > > >(); + database().add_index< primary_index< account_transaction_history_index > >(); LOAD_VALUE_SET(options, "tracked-accounts", my->_tracked_accounts, graphene::chain::account_id_type); } From c89d60ba92800967ce814d63813b976168fa4081 Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Thu, 10 Dec 2015 14:13:49 -0500 Subject: [PATCH 14/34] First implementation of get_account_history api changes. --- libraries/app/CMakeLists.txt | 2 +- libraries/app/api.cpp | 22 +++++++++++++ libraries/app/include/graphene/app/api.hpp | 5 +++ .../chain/operation_history_object.hpp | 31 +++++++++++++++++-- .../account_history_plugin.cpp | 24 -------------- .../account_history_plugin.hpp | 31 ++++++++++++++++++- 6 files changed, 87 insertions(+), 28 deletions(-) diff --git a/libraries/app/CMakeLists.txt b/libraries/app/CMakeLists.txt index caef157e..b01e3bde 100644 --- a/libraries/app/CMakeLists.txt +++ b/libraries/app/CMakeLists.txt @@ -11,7 +11,7 @@ add_library( graphene_app ${EGENESIS_HEADERS} ) -target_link_libraries( graphene_app graphene_market_history graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities ) +target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities ) target_include_directories( graphene_app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../egenesis/include" ) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 9533588b..635867a4 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -357,6 +357,28 @@ namespace graphene { namespace app { } return result; } + + vector history_api::get_relative_account_history( account_id_type account, uint32_t stop, unsigned limit, uint32_t start) const + { + FC_ASSERT(_app.chain_database()); + const auto& db = *_app.chain_database(); + FC_ASSERT(limit <= 100); + vector result; + if (start == 0) + start = account(db).statistics(db).total_ops; + const auto& hist_idx = db.get_index_type(); + const auto& by_seq_idx = hist_idx.indices().get(); + + auto itr = by_seq_idx.upper_bound( boost::make_tuple( account, start ) ); + + while ( itr != by_seq_idx.end() && itr->sequence > stop && result.size() < limit ) + { + result.push_back( itr->operation_id(db) ); + ++itr; + } + + return result; + } flat_set history_api::get_market_history_buckets()const { diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 46c6d457..9f092902 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -70,6 +70,11 @@ namespace graphene { namespace app { unsigned limit = 100, operation_history_id_type start = operation_history_id_type())const; + vector get_relative_account_history( account_id_type account, + uint32_t stop = 0, + unsigned limit = 100, + uint32_t start = 0) const; + vector get_fill_order_history( asset_id_type a, asset_id_type b, uint32_t limit )const; vector get_market_history( asset_id_type a, asset_id_type b, uint32_t bucket_seconds, fc::time_point_sec start, fc::time_point_sec end )const; diff --git a/libraries/chain/include/graphene/chain/operation_history_object.hpp b/libraries/chain/include/graphene/chain/operation_history_object.hpp index 4bf6b527..746d8eba 100644 --- a/libraries/chain/include/graphene/chain/operation_history_object.hpp +++ b/libraries/chain/include/graphene/chain/operation_history_object.hpp @@ -21,6 +21,7 @@ #pragma once #include #include +#include namespace graphene { namespace chain { @@ -91,9 +92,35 @@ namespace graphene { namespace chain { uint32_t sequence = 0; /// the operation position within the given account account_transaction_history_id_type next; - std::pair account_op()const { return std::tie( account, operation_id ); } - std::pair account_seq()const { return std::tie( account, sequence ); } + //std::pair account_op()const { return std::tie( account, operation_id ); } + //std::pair account_seq()const { return std::tie( account, sequence ); } }; + + struct by_id; +struct by_seq; +struct by_op; +typedef multi_index_container< + account_transaction_history_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key< account_transaction_history_object, + member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>, + member< account_transaction_history_object, uint32_t, &account_transaction_history_object::sequence> + > + >, + ordered_unique< tag, + composite_key< account_transaction_history_object, + member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>, + member< account_transaction_history_object, operation_history_id_type, &account_transaction_history_object::operation_id> + > + > + > +> account_transaction_history_multi_index_type; + +typedef generic_index account_transaction_history_index; + + } } // graphene::chain FC_REFLECT_DERIVED( graphene::chain::operation_history_object, (graphene::chain::object), diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 58d4293e..0d1c4f59 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -138,30 +138,6 @@ void account_history_plugin_impl::update_account_histories( const signed_block& } // end namespace detail -struct by_id; -struct by_seq; -struct by_op; -typedef multi_index_container< - account_transaction_history_object, - indexed_by< - ordered_unique< tag, - member< object, object_id_type, &object::id > >, - ordered_unique< tag, - composite_key< account_transaction_history_object, - member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>, - member< account_transaction_history_object, uint32_t, &account_transaction_history_object::sequence> - > - >, - ordered_unique< tag, - composite_key< account_transaction_history_object, - member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>, - member< account_transaction_history_object, operation_history_id_type, &account_transaction_history_object::operation_id> - > - > - > -> account_transaction_history_multi_index_type; - -typedef generic_index account_transaction_history_index; diff --git a/libraries/plugins/account_history/include/graphene/account_history/account_history_plugin.hpp b/libraries/plugins/account_history/include/graphene/account_history/account_history_plugin.hpp index e6edc8a5..61e71c77 100644 --- a/libraries/plugins/account_history/include/graphene/account_history/account_history_plugin.hpp +++ b/libraries/plugins/account_history/include/graphene/account_history/account_history_plugin.hpp @@ -23,10 +23,15 @@ #include #include +#include + #include namespace graphene { namespace account_history { -using namespace chain; + using namespace chain; + //using namespace graphene::db; + //using boost::multi_index_container; + //using namespace boost::multi_index; // // Plugins should #define their SPACE_ID's so plugins with @@ -75,3 +80,27 @@ class account_history_plugin : public graphene::app::plugin } } //graphene::account_history +/*struct by_id; +struct by_seq; +struct by_op; +typedef boost::multi_index_container< + graphene::chain::account_transaction_history_object, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique< tag, member< object, object_id_type, &object::id > >, + boost::multi_index::ordered_unique< tag, + composite_key< account_transaction_history_object, + member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>, + member< account_transaction_history_object, uint32_t, &account_transaction_history_object::sequence> + > + >, + boost::multi_index::ordered_unique< tag, + composite_key< account_transaction_history_object, + member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>, + member< account_transaction_history_object, operation_history_id_type, &account_transaction_history_object::operation_id> + > + > + > +> account_transaction_history_multi_index_type; + +typedef graphene::account_history::generic_index account_transaction_history_index; +*/ \ No newline at end of file From 9485ebfd647d34c56e4b36ff4cbf23bafc71f033 Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Fri, 11 Dec 2015 11:11:36 -0500 Subject: [PATCH 15/34] Fixed bug in get_account_history that prevented all operations from being returned when specific bounds were set. --- libraries/app/api.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 635867a4..e9f7d197 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -339,22 +339,24 @@ namespace graphene { namespace app { vector history_api::get_account_history(account_id_type account, operation_history_id_type stop, unsigned limit, operation_history_id_type start) const { FC_ASSERT(_app.chain_database()); - const auto& db = *_app.chain_database(); + const auto& db = *_app.chain_database(); FC_ASSERT(limit <= 100); vector result; const auto& stats = account(db).statistics(db); if(stats.most_recent_op == account_transaction_history_id_type()) return result; const account_transaction_history_object* node = &stats.most_recent_op(db); if(start == operation_history_id_type()) - start = node->id; + start = node->operation_id; + while(node && node->operation_id.instance.value > stop.instance.value && result.size() < limit) { - if(node->id.instance() <= start.instance.value) + if (node->operation_id.instance.value <= start.instance.value) result.push_back(node->operation_id(db)); if(node->next == account_transaction_history_id_type()) node = nullptr; - else node = db.find(node->next); + else node = &node->next(db); } + return result; } From ae82e0a9a686fea3a97e9baff70542bca6d35373 Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Fri, 11 Dec 2015 14:07:03 -0500 Subject: [PATCH 16/34] Tested and validated the new get_relative_account_history API call. --- libraries/app/api.cpp | 35 ++++++++++++++-------- libraries/app/include/graphene/app/api.hpp | 14 ++++++++- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index e9f7d197..5e6d57cb 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -336,23 +336,26 @@ namespace graphene { namespace app { return result; } - vector history_api::get_account_history(account_id_type account, operation_history_id_type stop, unsigned limit, operation_history_id_type start) const + vector history_api::get_account_history( account_id_type account, + operation_history_id_type stop, + unsigned limit, + operation_history_id_type start ) const { - FC_ASSERT(_app.chain_database()); + FC_ASSERT( _app.chain_database() ); const auto& db = *_app.chain_database(); - FC_ASSERT(limit <= 100); + FC_ASSERT( limit <= 100 ); vector result; const auto& stats = account(db).statistics(db); - if(stats.most_recent_op == account_transaction_history_id_type()) return result; + if( stats.most_recent_op == account_transaction_history_id_type() ) return result; const account_transaction_history_object* node = &stats.most_recent_op(db); - if(start == operation_history_id_type()) + if( start == operation_history_id_type() ) start = node->operation_id; while(node && node->operation_id.instance.value > stop.instance.value && result.size() < limit) { - if (node->operation_id.instance.value <= start.instance.value) - result.push_back(node->operation_id(db)); - if(node->next == account_transaction_history_id_type()) + if( node->operation_id.instance.value <= start.instance.value ) + result.push_back( node->operation_id(db) ); + if( node->next == account_transaction_history_id_type() ) node = nullptr; else node = &node->next(db); } @@ -360,23 +363,29 @@ namespace graphene { namespace app { return result; } - vector history_api::get_relative_account_history( account_id_type account, uint32_t stop, unsigned limit, uint32_t start) const + vector history_api::get_relative_account_history( account_id_type account, + uint32_t stop, + unsigned limit, + uint32_t start) const { - FC_ASSERT(_app.chain_database()); + FC_ASSERT( _app.chain_database() ); const auto& db = *_app.chain_database(); FC_ASSERT(limit <= 100); vector result; - if (start == 0) + if( start == 0 ) start = account(db).statistics(db).total_ops; + else start = min( account(db).statistics(db).total_ops, start ); const auto& hist_idx = db.get_index_type(); const auto& by_seq_idx = hist_idx.indices().get(); auto itr = by_seq_idx.upper_bound( boost::make_tuple( account, start ) ); + auto itr_stop = by_seq_idx.lower_bound( boost::make_tuple( account, stop ) ); + --itr; - while ( itr != by_seq_idx.end() && itr->sequence > stop && result.size() < limit ) + while ( itr != itr_stop && result.size() < limit ) { result.push_back( itr->operation_id(db) ); - ++itr; + --itr; } return result; diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 9f092902..fb31033a 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -69,7 +69,18 @@ namespace graphene { namespace app { operation_history_id_type stop = operation_history_id_type(), unsigned limit = 100, operation_history_id_type start = operation_history_id_type())const; - + /** + * @breif Get operations relevant to the specified account referenced + * by an event numbering specific to the account. The current number of operations + * for the account can be found in the account statistics (or use 0 for start). + * @param account The account whose history should be queried + * @param stop Sequence number of earliest operation. 0 is default and will + * query 'limit' number of operations. + * @param limit Maximum number of operations to retrieve (must not exceed 100) + * @param start Sequence number of the most recent operation to retrieve. + * 0 is default, which will start querying from the most recent operation. + * @return A list of operations performed by account, ordered from most recent to oldest. + */ vector get_relative_account_history( account_id_type account, uint32_t stop = 0, unsigned limit = 100, @@ -209,6 +220,7 @@ FC_REFLECT( graphene::app::network_broadcast_api::transaction_confirmation, FC_API(graphene::app::history_api, (get_account_history) + (get_relative_account_history) (get_fill_order_history) (get_market_history) (get_market_history_buckets) From 77ac461a8afc5e18af1190fe4c5d0f7e33257241 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 3 Mar 2016 14:12:27 -0500 Subject: [PATCH 17/34] Implement debug_node target #606 --- libraries/app/CMakeLists.txt | 3 +- libraries/app/api.cpp | 12 + libraries/app/include/graphene/app/api.hpp | 6 + libraries/chain/db_block.cpp | 2 + libraries/chain/db_debug.cpp | 103 ++++++ .../chain/include/graphene/chain/database.hpp | 2 + .../graphene/chain/node_property_object.hpp | 1 + libraries/db/include/graphene/db/index.hpp | 20 ++ libraries/plugins/CMakeLists.txt | 1 + .../plugins/debug_witness/CMakeLists.txt | 18 + libraries/plugins/debug_witness/debug_api.cpp | 122 +++++++ .../plugins/debug_witness/debug_witness.cpp | 126 +++++++ .../graphene/debug_witness/debug_api.hpp | 83 +++++ .../graphene/debug_witness/debug_witness.hpp | 59 ++++ .../wallet/include/graphene/wallet/wallet.hpp | 7 + libraries/wallet/wallet.cpp | 55 ++++ programs/CMakeLists.txt | 1 + programs/debug_node/CMakeLists.txt | 21 ++ programs/debug_node/main.cpp | 307 ++++++++++++++++++ programs/witness_node/CMakeLists.txt | 3 +- 20 files changed, 950 insertions(+), 2 deletions(-) create mode 100644 libraries/plugins/debug_witness/CMakeLists.txt create mode 100644 libraries/plugins/debug_witness/debug_api.cpp create mode 100644 libraries/plugins/debug_witness/debug_witness.cpp create mode 100644 libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp create mode 100644 libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp create mode 100644 programs/debug_node/CMakeLists.txt create mode 100644 programs/debug_node/main.cpp diff --git a/libraries/app/CMakeLists.txt b/libraries/app/CMakeLists.txt index caef157e..19742cb8 100644 --- a/libraries/app/CMakeLists.txt +++ b/libraries/app/CMakeLists.txt @@ -11,7 +11,8 @@ add_library( graphene_app ${EGENESIS_HEADERS} ) -target_link_libraries( graphene_app graphene_market_history graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities ) +# need to link graphene_debug_witness because plugins aren't sufficiently isolated #246 +target_link_libraries( graphene_app graphene_market_history graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness ) target_include_directories( graphene_app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../egenesis/include" ) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 35cf3880..4e6120d6 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -95,6 +95,12 @@ namespace graphene { namespace app { { _crypto_api = std::make_shared< crypto_api >(); } + else if( api_name == "debug_api" ) + { + // can only enable this API if the plugin was loaded + if( _app.get_plugin( "debug_witness" ) ) + _debug_api = std::make_shared< graphene::debug_witness::debug_api >( _app.chain_database() ); + } return; } @@ -211,6 +217,12 @@ namespace graphene { namespace app { return *_crypto_api; } + fc::api login_api::debug() const + { + FC_ASSERT(_debug_api); + return *_debug_api; + } + vector get_relevant_accounts( const object* obj ) { vector result; diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 59c905d4..a898649a 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -30,6 +30,8 @@ #include +#include + #include #include @@ -265,6 +267,8 @@ namespace graphene { namespace app { fc::api network_node()const; /// @brief Retrieve the cryptography API fc::api crypto()const; + /// @brief Retrieve the debug API (if available) + fc::api debug()const; private: /// @brief Called to enable an API, not reflected. @@ -276,6 +280,7 @@ namespace graphene { namespace app { optional< fc::api > _network_node_api; optional< fc::api > _history_api; optional< fc::api > _crypto_api; + optional< fc::api > _debug_api; }; }} // graphene::app @@ -326,4 +331,5 @@ FC_API(graphene::app::login_api, (history) (network_node) (crypto) + (debug) ) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 2e511591..8f4f1f1e 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -534,6 +534,8 @@ void database::_apply_block( const signed_block& next_block ) // to be called for header validation? update_maintenance_flag( maint_needed ); update_witness_schedule(); + if( !_node_property_object.debug_updates.empty() ) + apply_debug_updates(); // notify observers that the block has been applied applied_block( next_block ); //emit diff --git a/libraries/chain/db_debug.cpp b/libraries/chain/db_debug.cpp index 7fafcf57..aa91fd44 100644 --- a/libraries/chain/db_debug.cpp +++ b/libraries/chain/db_debug.cpp @@ -95,4 +95,107 @@ void database::debug_dump() */ } +void debug_apply_update( database& db, const fc::variant_object& vo ) +{ + static const uint8_t + db_action_nil = 0, + db_action_create = 1, + db_action_write = 2, + db_action_update = 3, + db_action_delete = 4; + + // "_action" : "create" object must not exist, unspecified fields take defaults + // "_action" : "write" object may exist, is replaced entirely, unspecified fields take defaults + // "_action" : "update" object must exist, unspecified fields don't change + // "_action" : "delete" object must exist, will be deleted + + // if _action is unspecified: + // - delete if object contains only ID field + // - otherwise, write + + object_id_type oid; + uint8_t action = db_action_nil; + auto it_id = vo.find("id"); + FC_ASSERT( it_id != vo.end() ); + + from_variant( it_id->value(), oid ); + action = ( vo.size() == 1 ) ? db_action_delete : db_action_write; + + from_variant( vo["id"], oid ); + if( vo.size() == 1 ) + action = db_action_delete; + auto it_action = vo.find("_action" ); + if( it_action != vo.end() ) + { + const std::string& str_action = it_action->value().get_string(); + if( str_action == "create" ) + action = db_action_create; + else if( str_action == "write" ) + action = db_action_write; + else if( str_action == "update" ) + action = db_action_update; + else if( str_action == "delete" ) + action = db_action_delete; + } + + auto& idx = db.get_index( oid ); + + switch( action ) + { + case db_action_create: + /* + idx.create( [&]( object& obj ) + { + idx.object_from_variant( vo, obj ); + } ); + */ + FC_ASSERT( false ); + break; + case db_action_write: + db.modify( db.get_object( oid ), [&]( object& obj ) + { + idx.object_default( obj ); + idx.object_from_variant( vo, obj ); + } ); + break; + case db_action_update: + db.modify( db.get_object( oid ), [&]( object& obj ) + { + idx.object_from_variant( vo, obj ); + } ); + break; + case db_action_delete: + db.remove( db.get_object( oid ) ); + break; + default: + FC_ASSERT( false ); + } +} + +void database::apply_debug_updates() +{ + block_id_type head_id = head_block_id(); + auto it = _node_property_object.debug_updates.find( head_id ); + if( it == _node_property_object.debug_updates.end() ) + return; + for( const fc::variant_object& update : it->second ) + debug_apply_update( *this, update ); +} + +void database::debug_update( const fc::variant_object& update ) +{ + block_id_type head_id = head_block_id(); + auto it = _node_property_object.debug_updates.find( head_id ); + if( it == _node_property_object.debug_updates.end() ) + it = _node_property_object.debug_updates.emplace( head_id, std::vector< fc::variant_object >() ).first; + it->second.emplace_back( update ); + + optional head_block = fetch_block_by_id( head_id ); + FC_ASSERT( head_block.valid() ); + + // What the last block does has been changed by adding to node_property_object, so we have to re-apply it + pop_block(); + push_block( *head_block ); +} + } } diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index b73b8931..1b721253 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -322,6 +322,8 @@ namespace graphene { namespace chain { //////////////////// db_debug.cpp //////////////////// void debug_dump(); + void apply_debug_updates(); + void debug_update( const fc::variant_object& update ); //////////////////// db_market.cpp //////////////////// diff --git a/libraries/chain/include/graphene/chain/node_property_object.hpp b/libraries/chain/include/graphene/chain/node_property_object.hpp index cf2285a8..b9cdfb6e 100644 --- a/libraries/chain/include/graphene/chain/node_property_object.hpp +++ b/libraries/chain/include/graphene/chain/node_property_object.hpp @@ -43,5 +43,6 @@ namespace graphene { namespace chain { ~node_property_object(){} uint32_t skip_flags = 0; + std::map< block_id_type, std::vector< fc::variant_object > > debug_updates; }; } } // graphene::chain diff --git a/libraries/db/include/graphene/db/index.hpp b/libraries/db/include/graphene/db/index.hpp index 6c1c975e..7ecf5a66 100644 --- a/libraries/db/include/graphene/db/index.hpp +++ b/libraries/db/include/graphene/db/index.hpp @@ -130,6 +130,8 @@ namespace graphene { namespace db { virtual fc::uint128 hash()const = 0; virtual void add_observer( const shared_ptr& ) = 0; + virtual void object_from_variant( const fc::variant& var, object& obj )const = 0; + virtual void object_default( object& obj )const = 0; }; class secondary_index @@ -298,6 +300,24 @@ namespace graphene { namespace db { _observers.emplace_back( o ); } + virtual void object_from_variant( const fc::variant& var, object& obj )const override + { + object_id_type id = obj.id; + object_type* result = dynamic_cast( &obj ); + FC_ASSERT( result != nullptr ); + fc::from_variant( var, *result ); + obj.id = id; + } + + virtual void object_default( object& obj )const override + { + object_id_type id = obj.id; + object_type* result = dynamic_cast( &obj ); + FC_ASSERT( result != nullptr ); + (*result) = object_type(); + obj.id = id; + } + private: object_id_type _next_id; }; diff --git a/libraries/plugins/CMakeLists.txt b/libraries/plugins/CMakeLists.txt index 128d8aa1..7a02ceba 100644 --- a/libraries/plugins/CMakeLists.txt +++ b/libraries/plugins/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory( witness ) add_subdirectory( account_history ) add_subdirectory( market_history ) add_subdirectory( delayed_node ) +add_subdirectory( debug_witness ) \ No newline at end of file diff --git a/libraries/plugins/debug_witness/CMakeLists.txt b/libraries/plugins/debug_witness/CMakeLists.txt new file mode 100644 index 00000000..f0fcb7fb --- /dev/null +++ b/libraries/plugins/debug_witness/CMakeLists.txt @@ -0,0 +1,18 @@ +file(GLOB HEADERS "include/graphene/debug_witness/*.hpp") + +add_library( graphene_debug_witness + debug_api.cpp + debug_witness.cpp + ) + +target_link_libraries( graphene_debug_witness graphene_chain graphene_app ) +target_include_directories( graphene_debug_witness + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + +install( TARGETS + graphene_debug_witness + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) diff --git a/libraries/plugins/debug_witness/debug_api.cpp b/libraries/plugins/debug_witness/debug_api.cpp new file mode 100644 index 00000000..53b2e7a8 --- /dev/null +++ b/libraries/plugins/debug_witness/debug_api.cpp @@ -0,0 +1,122 @@ + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace graphene { namespace debug_witness { + +namespace detail { + +class debug_api_impl +{ + public: + debug_api_impl( std::shared_ptr< graphene::chain::database >& _db ); + + void debug_push_blocks( const std::string& src_filename, uint32_t count ); + void debug_generate_blocks( const std::string& debug_key, uint32_t count ); + void debug_update_object( const fc::variant_object& update ); + //void debug_save_db( std::string db_path ); + + std::shared_ptr< graphene::chain::database > db; +}; + +debug_api_impl::debug_api_impl( std::shared_ptr< graphene::chain::database >& _db ) : db( _db ) +{} + + +void debug_api_impl::debug_push_blocks( const std::string& src_filename, uint32_t count ) +{ + if( count == 0 ) + return; + + fc::path src_path = fc::path( src_filename ); + if( fc::is_directory( src_path ) ) + { + ilog( "Loading ${n} from block_database ${fn}", ("n", count)("fn", src_filename) ); + graphene::chain::block_database bdb; + bdb.open( src_path ); + uint32_t first_block = db->head_block_num()+1; + for( uint32_t i=0; i block = bdb.fetch_by_number( first_block+i ); + if( !block.valid() ) + { + wlog( "Block database ${fn} only contained ${i} of ${n} requested blocks", ("i", i)("n", count)("fn", src_filename) ); + return; + } + try + { + db->push_block( *block ); + } + catch( const fc::exception& e ) + { + elog( "Got exception pushing block ${bn} : ${bid} (${i} of ${n})", ("bn", block->block_num())("bid", block->id())("i", i)("n", count) ); + elog( "Exception backtrace: ${bt}", ("bt", e.to_detail_string()) ); + } + } + ilog( "Completed loading block_database successfully" ); + return; + } +} + +void debug_api_impl::debug_generate_blocks( const std::string& debug_key, uint32_t count ) +{ + if( count == 0 ) + return; + + fc::optional debug_private_key = graphene::utilities::wif_to_key( debug_key ); + FC_ASSERT( debug_private_key.valid() ); + graphene::chain::public_key_type debug_public_key = debug_private_key->get_public_key(); + + for( uint32_t i=0; iget_scheduled_witness( 1 ); + fc::time_point_sec scheduled_time = db->get_slot_time( 1 ); + graphene::chain::public_key_type scheduled_key = scheduled_witness( *db ).signing_key; + if( scheduled_key != debug_public_key ) + { + ilog( "Modified key for witness ${w}", ("w", scheduled_witness) ); + fc::mutable_variant_object update; + update("_action", "update")("id", scheduled_witness)("signing_key", debug_public_key); + db->debug_update( update ); + } + db->generate_block( scheduled_time, scheduled_witness, *debug_private_key, graphene::chain::database::skip_nothing ); + } +} + +void debug_api_impl::debug_update_object( const fc::variant_object& update ) +{ + db->debug_update( update ); +} + +} // detail + +debug_api::debug_api( std::shared_ptr< graphene::chain::database > db ) +{ + my = std::make_shared< detail::debug_api_impl >(db); +} + +void debug_api::debug_push_blocks( std::string source_filename, uint32_t count ) +{ + my->debug_push_blocks( source_filename, count ); +} + +void debug_api::debug_generate_blocks( std::string debug_key, uint32_t count ) +{ + my->debug_generate_blocks( debug_key, count ); +} + +void debug_api::debug_update_object( fc::variant_object update ) +{ + my->debug_update_object( update ); +} + +} } // graphene::debug_witness diff --git a/libraries/plugins/debug_witness/debug_witness.cpp b/libraries/plugins/debug_witness/debug_witness.cpp new file mode 100644 index 00000000..9d6601c4 --- /dev/null +++ b/libraries/plugins/debug_witness/debug_witness.cpp @@ -0,0 +1,126 @@ +/* + * 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. + */ +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +using namespace graphene::debug_witness_plugin; +using std::string; +using std::vector; + +namespace bpo = boost::program_options; + +debug_witness_plugin::~debug_witness_plugin() {} + +void debug_witness_plugin::plugin_set_program_options( + boost::program_options::options_description& command_line_options, + boost::program_options::options_description& config_file_options) +{ + auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan"))); + command_line_options.add_options() + ("private-key", bpo::value>()->composing()->multitoken()-> + DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()), graphene::utilities::key_to_wif(default_priv_key))), + "Tuple of [PublicKey, WIF private key] (may specify multiple times)"); + config_file_options.add(command_line_options); +} + +std::string debug_witness_plugin::plugin_name()const +{ + return "debug_witness"; +} + +void debug_witness_plugin::plugin_initialize(const boost::program_options::variables_map& options) +{ try { + ilog("debug_witness plugin: plugin_initialize() begin"); + _options = &options; + + if( options.count("private-key") ) + { + const std::vector key_id_to_wif_pair_strings = options["private-key"].as>(); + for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings) + { + auto key_id_to_wif_pair = graphene::app::dejsonify >(key_id_to_wif_pair_string); + idump((key_id_to_wif_pair)); + fc::optional private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second); + if (!private_key) + { + // the key isn't in WIF format; see if they are still passing the old native private key format. This is + // just here to ease the transition, can be removed soon + try + { + private_key = fc::variant(key_id_to_wif_pair.second).as(); + } + catch (const fc::exception&) + { + FC_THROW("Invalid WIF-format private key ${key_string}", ("key_string", key_id_to_wif_pair.second)); + } + } + _private_keys[key_id_to_wif_pair.first] = *private_key; + } + } + ilog("debug_witness plugin: plugin_initialize() end"); +} FC_LOG_AND_RETHROW() } + +void debug_witness_plugin::plugin_startup() +{ + ilog("debug_witness_plugin::plugin_startup() begin"); + chain::database& db = database(); + + // connect needed signals + + db.applied_block.connect([this](const graphene::chain::signed_block& b){ on_applied_block(b); }); + db.changed_objects.connect([this](const std::vector& ids){ on_changed_objects(ids); }); + db.removed_objects.connect([this](const std::vector& objs){ on_removed_objects(objs); }); + + return; +} + +void debug_witness_plugin::on_changed_objects( const std::vector& ids ) +{ + +} + +void debug_witness_plugin::on_removed_objects( const std::vector objs ) +{ + +} + +void debug_witness_plugin::on_applied_block( const graphene::chain::signed_block& b ) +{ + +} + +void debug_witness_plugin::plugin_shutdown() +{ + return; +} diff --git a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp new file mode 100644 index 00000000..126f2708 --- /dev/null +++ b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp @@ -0,0 +1,83 @@ +/* + * 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 +#include + +#include +#include + +namespace graphene { namespace chain { +class database; +} } + +namespace graphene { namespace debug_witness { + +namespace detail { +class debug_api_impl; +} + +class debug_api +{ + public: + debug_api( std::shared_ptr db ); + + /** + * Push blocks from existing database. + */ + void debug_push_blocks( std::string src_filename, uint32_t count ); + + /** + * Generate blocks locally. + */ + void debug_generate_blocks( std::string debug_key, uint32_t count ); + + /** + * Directly manipulate database objects (will undo and re-apply last block with new changes post-applied). + */ + void debug_update_object( fc::variant_object update ); + + /** + * Start a node with given initial path. + */ + // not implemented + //void start_node( std::string name, std::string initial_db_path ); + + /** + * Save the database to disk. + */ + // not implemented + //void save_db( std::string db_path ); + + std::shared_ptr< detail::debug_api_impl > my; +}; + +} } + +FC_API(graphene::debug_witness::debug_api, + (debug_push_blocks) + (debug_generate_blocks) + (debug_update_object) + ) diff --git a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp new file mode 100644 index 00000000..1dde26cc --- /dev/null +++ b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp @@ -0,0 +1,59 @@ +/* + * 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 +#include + +#include + +namespace graphene { namespace debug_witness_plugin { + +class debug_witness_plugin : public graphene::app::plugin { +public: + ~debug_witness_plugin(); + + std::string plugin_name()const override; + + virtual void plugin_set_program_options( + boost::program_options::options_description &command_line_options, + boost::program_options::options_description &config_file_options + ) override; + + virtual void plugin_initialize( const boost::program_options::variables_map& options ) override; + virtual void plugin_startup() override; + virtual void plugin_shutdown() override; + +private: + + void on_changed_objects( const std::vector& ids ); + void on_removed_objects( const std::vector objs ); + void on_applied_block( const graphene::chain::signed_block& b ); + + boost::program_options::variables_map _options; + + std::map _private_keys; +}; + +} } //graphene::debug_witness_plugin diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index cdb9036e..847f2d6e 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1404,6 +1404,10 @@ class wallet_api void dbg_make_uia(string creator, string symbol); void dbg_make_mia(string creator, string symbol); + void dbg_push_blocks( std::string src_filename, uint32_t count ); + void dbg_generate_blocks( std::string debug_wif_key, uint32_t count ); + void dbg_update_object( fc::variant_object update ); + void flood_network(string prefix, uint32_t number_of_transactions); void network_add_nodes( const vector& nodes ); @@ -1574,6 +1578,9 @@ FC_API( graphene::wallet::wallet_api, (approve_proposal) (dbg_make_uia) (dbg_make_mia) + (dbg_push_blocks) + (dbg_generate_blocks) + (dbg_update_object) (flood_network) (network_add_nodes) (network_get_connected_peers) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 3dd8b9e3..3dbeda82 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include #ifndef WIN32 @@ -2338,6 +2339,24 @@ public: create_asset(get_account(creator).name, symbol, 2, opts, bopts, true); } + void dbg_push_blocks( const std::string& src_filename, uint32_t count ) + { + use_debug_api(); + (*_remote_debug)->debug_push_blocks( src_filename, count ); + } + + void dbg_generate_blocks( const std::string& debug_wif_key, uint32_t count ) + { + use_debug_api(); + (*_remote_debug)->debug_generate_blocks( debug_wif_key, count ); + } + + void dbg_update_object( const fc::variant_object& update ) + { + use_debug_api(); + (*_remote_debug)->debug_update_object( update ); + } + void use_network_node_api() { if( _remote_net_node ) @@ -2356,6 +2375,26 @@ public: } } + void use_debug_api() + { + if( _remote_debug ) + return; + try + { + _remote_debug = _remote_api->debug(); + } + catch( const fc::exception& e ) + { + std::cerr << "\nCouldn't get debug node API. You probably are not configured\n" + "to access the debug API on the node you are connecting to.\n" + "\n" + "To fix this problem:\n" + "- Please ensure you are running debug_node, not witness_node.\n" + "- Please follow the instructions in README.md to set up an apiaccess file.\n" + "\n"; + } + } + void network_add_nodes( const vector& nodes ) { use_network_node_api(); @@ -2449,6 +2488,7 @@ public: fc::api _remote_net_broadcast; fc::api _remote_hist; optional< fc::api > _remote_net_node; + optional< fc::api > _remote_debug; flat_map _prototype_ops; @@ -3214,6 +3254,21 @@ void wallet_api::dbg_make_mia(string creator, string symbol) my->dbg_make_mia(creator, symbol); } +void wallet_api::dbg_push_blocks( std::string src_filename, uint32_t count ) +{ + my->dbg_push_blocks( src_filename, count ); +} + +void wallet_api::dbg_generate_blocks( std::string debug_wif_key, uint32_t count ) +{ + my->dbg_generate_blocks( debug_wif_key, count ); +} + +void wallet_api::dbg_update_object( fc::variant_object update ) +{ + my->dbg_update_object( update ); +} + void wallet_api::network_add_nodes( const vector& nodes ) { my->network_add_nodes( nodes ); diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index b17a972c..932e69b7 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory( build_helpers ) add_subdirectory( cli_wallet ) add_subdirectory( genesis_util ) add_subdirectory( witness_node ) +add_subdirectory( debug_node ) add_subdirectory( delayed_node ) add_subdirectory( js_operation_serializer ) add_subdirectory( size_checker ) diff --git a/programs/debug_node/CMakeLists.txt b/programs/debug_node/CMakeLists.txt new file mode 100644 index 00000000..8ec7362b --- /dev/null +++ b/programs/debug_node/CMakeLists.txt @@ -0,0 +1,21 @@ +add_executable( debug_node main.cpp ) +if( UNIX AND NOT APPLE ) + set(rt_library rt ) +endif() + +find_package( Gperftools QUIET ) +if( GPERFTOOLS_FOUND ) + message( STATUS "Found gperftools; compiling debug_node with TCMalloc") + list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) +endif() + +target_link_libraries( debug_node + PRIVATE graphene_app graphene_account_history graphene_market_history graphene_witness graphene_debug_witness graphene_chain graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) + +install( TARGETS + debug_node + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) diff --git a/programs/debug_node/main.cpp b/programs/debug_node/main.cpp new file mode 100644 index 00000000..4b89c199 --- /dev/null +++ b/programs/debug_node/main.cpp @@ -0,0 +1,307 @@ +/* + * 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. + */ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#ifdef WIN32 +# include +#else +# include +#endif + +using namespace graphene; +namespace bpo = boost::program_options; + +void write_default_logging_config_to_stream(std::ostream& out); +fc::optional load_logging_config_from_ini_file(const fc::path& config_ini_filename); + +int main(int argc, char** argv) { + app::application* node = new app::application(); + fc::oexception unhandled_exception; + try { + bpo::options_description app_options("Graphene Witness Node"); + bpo::options_description cfg_options("Graphene Witness Node"); + app_options.add_options() + ("help,h", "Print this help message and exit.") + ("data-dir,d", bpo::value()->default_value("witness_node_data_dir"), "Directory containing databases, configuration file, etc.") + ; + + bpo::variables_map options; + + auto witness_plug = node->register_plugin(); + auto history_plug = node->register_plugin(); + auto market_history_plug = node->register_plugin(); + + try + { + bpo::options_description cli, cfg; + node->set_program_options(cli, cfg); + app_options.add(cli); + cfg_options.add(cfg); + bpo::store(bpo::parse_command_line(argc, argv, app_options), options); + } + catch (const boost::program_options::error& e) + { + std::cerr << "Error parsing command line: " << e.what() << "\n"; + return 1; + } + + if( options.count("help") ) + { + std::cout << app_options << "\n"; + return 0; + } + + fc::path data_dir; + if( options.count("data-dir") ) + { + data_dir = options["data-dir"].as(); + if( data_dir.is_relative() ) + data_dir = fc::current_path() / data_dir; + } + + fc::path config_ini_path = data_dir / "config.ini"; + if( fc::exists(config_ini_path) ) + { + // get the basic options + bpo::store(bpo::parse_config_file(config_ini_path.preferred_string().c_str(), cfg_options, true), options); + + // try to get logging options from the config file. + try + { + fc::optional logging_config = load_logging_config_from_ini_file(config_ini_path); + if (logging_config) + fc::configure_logging(*logging_config); + } + catch (const fc::exception&) + { + wlog("Error parsing logging config from config file ${config}, using default config", ("config", config_ini_path.preferred_string())); + } + } + else + { + ilog("Writing new config file at ${path}", ("path", config_ini_path)); + if( !fc::exists(data_dir) ) + fc::create_directories(data_dir); + + std::ofstream out_cfg(config_ini_path.preferred_string()); + for( const boost::shared_ptr od : cfg_options.options() ) + { + if( !od->description().empty() ) + out_cfg << "# " << od->description() << "\n"; + boost::any store; + if( !od->semantic()->apply_default(store) ) + out_cfg << "# " << od->long_name() << " = \n"; + else { + auto example = od->format_parameter(); + if( example.empty() ) + // This is a boolean switch + out_cfg << od->long_name() << " = " << "false\n"; + else { + // The string is formatted "arg (=)" + example.erase(0, 6); + example.erase(example.length()-1); + out_cfg << od->long_name() << " = " << example << "\n"; + } + } + out_cfg << "\n"; + } + write_default_logging_config_to_stream(out_cfg); + out_cfg.close(); + // read the default logging config we just wrote out to the file and start using it + fc::optional logging_config = load_logging_config_from_ini_file(config_ini_path); + if (logging_config) + fc::configure_logging(*logging_config); + } + + bpo::notify(options); + node->initialize(data_dir, options); + node->initialize_plugins( options ); + + node->startup(); + node->startup_plugins(); + + fc::promise::ptr exit_promise = new fc::promise("UNIX Signal Handler"); + + fc::set_signal_handler([&exit_promise](int signal) { + elog( "Caught SIGINT attempting to exit cleanly" ); + exit_promise->set_value(signal); + }, SIGINT); + + fc::set_signal_handler([&exit_promise](int signal) { + elog( "Caught SIGTERM attempting to exit cleanly" ); + exit_promise->set_value(signal); + }, SIGTERM); + + ilog("Started witness node on a chain with ${h} blocks.", ("h", node->chain_database()->head_block_num())); + ilog("Chain ID is ${id}", ("id", node->chain_database()->get_chain_id()) ); + + int signal = exit_promise->wait(); + ilog("Exiting from signal ${n}", ("n", signal)); + node->shutdown_plugins(); + node->shutdown(); + delete node; + return 0; + } catch( const fc::exception& e ) { + // deleting the node can yield, so do this outside the exception handler + unhandled_exception = e; + } + + if (unhandled_exception) + { + elog("Exiting with error:\n${e}", ("e", unhandled_exception->to_detail_string())); + node->shutdown(); + delete node; + return 1; + } +} + +// logging config is too complicated to be parsed by boost::program_options, +// so we do it by hand +// +// Currently, you can only specify the filenames and logging levels, which +// are all most users would want to change. At a later time, options can +// be added to control rotation intervals, compression, and other seldom- +// used features +void write_default_logging_config_to_stream(std::ostream& out) +{ + out << "# declare an appender named \"stderr\" that writes messages to the console\n" + "[log.console_appender.stderr]\n" + "stream=std_error\n\n" + "# declare an appender named \"p2p\" that writes messages to p2p.log\n" + "[log.file_appender.p2p]\n" + "filename=logs/p2p/p2p.log\n" + "# filename can be absolute or relative to this config file\n\n" + "# route any messages logged to the default logger to the \"stderr\" logger we\n" + "# declared above, if they are info level are higher\n" + "[logger.default]\n" + "level=info\n" + "appenders=stderr\n\n" + "# route messages sent to the \"p2p\" logger to the p2p appender declared above\n" + "[logger.p2p]\n" + "level=debug\n" + "appenders=p2p\n\n"; +} + +fc::optional load_logging_config_from_ini_file(const fc::path& config_ini_filename) +{ + try + { + fc::logging_config logging_config; + bool found_logging_config = false; + + boost::property_tree::ptree config_ini_tree; + boost::property_tree::ini_parser::read_ini(config_ini_filename.preferred_string().c_str(), config_ini_tree); + for (const auto& section : config_ini_tree) + { + const std::string& section_name = section.first; + const boost::property_tree::ptree& section_tree = section.second; + + const std::string console_appender_section_prefix = "log.console_appender."; + const std::string file_appender_section_prefix = "log.file_appender."; + const std::string logger_section_prefix = "logger."; + + if (boost::starts_with(section_name, console_appender_section_prefix)) + { + std::string console_appender_name = section_name.substr(console_appender_section_prefix.length()); + std::string stream_name = section_tree.get("stream"); + + // construct a default console appender config here + // stdout/stderr will be taken from ini file, everything else hard-coded here + fc::console_appender::config console_appender_config; + console_appender_config.level_colors.emplace_back( + fc::console_appender::level_color(fc::log_level::debug, + fc::console_appender::color::green)); + console_appender_config.level_colors.emplace_back( + fc::console_appender::level_color(fc::log_level::warn, + fc::console_appender::color::brown)); + console_appender_config.level_colors.emplace_back( + fc::console_appender::level_color(fc::log_level::error, + fc::console_appender::color::cyan)); + console_appender_config.stream = fc::variant(stream_name).as(); + logging_config.appenders.push_back(fc::appender_config(console_appender_name, "console", fc::variant(console_appender_config))); + found_logging_config = true; + } + else if (boost::starts_with(section_name, file_appender_section_prefix)) + { + std::string file_appender_name = section_name.substr(file_appender_section_prefix.length()); + fc::path file_name = section_tree.get("filename"); + if (file_name.is_relative()) + file_name = fc::absolute(config_ini_filename).parent_path() / file_name; + + + // construct a default file appender config here + // filename will be taken from ini file, everything else hard-coded here + fc::file_appender::config file_appender_config; + file_appender_config.filename = file_name; + file_appender_config.flush = true; + file_appender_config.rotate = true; + file_appender_config.rotation_interval = fc::hours(1); + file_appender_config.rotation_limit = fc::days(1); + logging_config.appenders.push_back(fc::appender_config(file_appender_name, "file", fc::variant(file_appender_config))); + found_logging_config = true; + } + else if (boost::starts_with(section_name, logger_section_prefix)) + { + std::string logger_name = section_name.substr(logger_section_prefix.length()); + std::string level_string = section_tree.get("level"); + std::string appenders_string = section_tree.get("appenders"); + fc::logger_config logger_config(logger_name); + logger_config.level = fc::variant(level_string).as(); + boost::split(logger_config.appenders, appenders_string, + boost::is_any_of(" ,"), + boost::token_compress_on); + logging_config.loggers.push_back(logger_config); + found_logging_config = true; + } + } + if (found_logging_config) + return logging_config; + else + return fc::optional(); + } + FC_RETHROW_EXCEPTIONS(warn, "") +} diff --git a/programs/witness_node/CMakeLists.txt b/programs/witness_node/CMakeLists.txt index 41bd784d..0509a0af 100644 --- a/programs/witness_node/CMakeLists.txt +++ b/programs/witness_node/CMakeLists.txt @@ -9,8 +9,9 @@ if( GPERFTOOLS_FOUND ) list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) endif() +# We have to link against graphene_debug_witness because deficiency in our API infrastructure doesn't allow plugins to be fully abstracted #246 target_link_libraries( witness_node - PRIVATE graphene_app graphene_account_history graphene_market_history graphene_witness graphene_chain graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) + PRIVATE graphene_app graphene_account_history graphene_market_history graphene_witness graphene_chain graphene_debug_witness graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) install( TARGETS witness_node From c37c2d4543bc97068528c0b170e2ab6ab79d2ab9 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 15 Mar 2016 00:38:33 -0400 Subject: [PATCH 18/34] debug_api: Take application object, not database, as parameter #606 --- libraries/app/api.cpp | 2 +- libraries/plugins/debug_witness/debug_api.cpp | 15 ++++++++++----- .../include/graphene/debug_witness/debug_api.hpp | 6 +++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 4e6120d6..53e8f53e 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -99,7 +99,7 @@ namespace graphene { namespace app { { // can only enable this API if the plugin was loaded if( _app.get_plugin( "debug_witness" ) ) - _debug_api = std::make_shared< graphene::debug_witness::debug_api >( _app.chain_database() ); + _debug_api = std::make_shared< graphene::debug_witness::debug_api >( std::ref(_app) ); } return; } diff --git a/libraries/plugins/debug_witness/debug_api.cpp b/libraries/plugins/debug_witness/debug_api.cpp index 53b2e7a8..885ecef7 100644 --- a/libraries/plugins/debug_witness/debug_api.cpp +++ b/libraries/plugins/debug_witness/debug_api.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include #include @@ -18,17 +20,17 @@ namespace detail { class debug_api_impl { public: - debug_api_impl( std::shared_ptr< graphene::chain::database >& _db ); + debug_api_impl( graphene::app::application& _app ); void debug_push_blocks( const std::string& src_filename, uint32_t count ); void debug_generate_blocks( const std::string& debug_key, uint32_t count ); void debug_update_object( const fc::variant_object& update ); //void debug_save_db( std::string db_path ); - std::shared_ptr< graphene::chain::database > db; + graphene::app::application& app; }; -debug_api_impl::debug_api_impl( std::shared_ptr< graphene::chain::database >& _db ) : db( _db ) +debug_api_impl::debug_api_impl( graphene::app::application& _app ) : app( _app ) {} @@ -37,6 +39,7 @@ void debug_api_impl::debug_push_blocks( const std::string& src_filename, uint32_ if( count == 0 ) return; + std::shared_ptr< graphene::chain::database > db = app.chain_database(); fc::path src_path = fc::path( src_filename ); if( fc::is_directory( src_path ) ) { @@ -76,6 +79,7 @@ void debug_api_impl::debug_generate_blocks( const std::string& debug_key, uint32 FC_ASSERT( debug_private_key.valid() ); graphene::chain::public_key_type debug_public_key = debug_private_key->get_public_key(); + std::shared_ptr< graphene::chain::database > db = app.chain_database(); for( uint32_t i=0; iget_scheduled_witness( 1 ); @@ -94,14 +98,15 @@ void debug_api_impl::debug_generate_blocks( const std::string& debug_key, uint32 void debug_api_impl::debug_update_object( const fc::variant_object& update ) { + std::shared_ptr< graphene::chain::database > db = app.chain_database(); db->debug_update( update ); } } // detail -debug_api::debug_api( std::shared_ptr< graphene::chain::database > db ) +debug_api::debug_api( graphene::app::application& app ) { - my = std::make_shared< detail::debug_api_impl >(db); + my = std::make_shared< detail::debug_api_impl >(app); } void debug_api::debug_push_blocks( std::string source_filename, uint32_t count ) diff --git a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp index 126f2708..9306e4be 100644 --- a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp +++ b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp @@ -29,8 +29,8 @@ #include #include -namespace graphene { namespace chain { -class database; +namespace graphene { namespace app { +class application; } } namespace graphene { namespace debug_witness { @@ -42,7 +42,7 @@ class debug_api_impl; class debug_api { public: - debug_api( std::shared_ptr db ); + debug_api( graphene::app::application& app ); /** * Push blocks from existing database. From da9ee0c4998c78f80a70123f04bdfa53d22f69f7 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 15 Mar 2016 01:17:07 -0400 Subject: [PATCH 19/34] db_block.cpp: Include smart_ref_impl --- libraries/chain/db_block.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index e141cd08..2e511591 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -36,6 +36,8 @@ #include #include +#include + namespace graphene { namespace chain { bool database::is_known_block( const block_id_type& id )const From a7c88be7c72ee8c3619b07c617cd9f13cd3f364c Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 15 Mar 2016 00:38:55 -0400 Subject: [PATCH 20/34] debug_api: Add API call to stream JSON objects to file --- libraries/plugins/debug_witness/debug_api.cpp | 30 ++++++++++ .../plugins/debug_witness/debug_witness.cpp | 57 +++++++++++++++++-- .../graphene/debug_witness/debug_api.hpp | 13 +++++ .../graphene/debug_witness/debug_witness.hpp | 8 +++ .../wallet/include/graphene/wallet/wallet.hpp | 2 + libraries/wallet/wallet.cpp | 15 +++++ 6 files changed, 120 insertions(+), 5 deletions(-) diff --git a/libraries/plugins/debug_witness/debug_api.cpp b/libraries/plugins/debug_witness/debug_api.cpp index 885ecef7..5c2d9a37 100644 --- a/libraries/plugins/debug_witness/debug_api.cpp +++ b/libraries/plugins/debug_witness/debug_api.cpp @@ -12,6 +12,7 @@ #include #include +#include namespace graphene { namespace debug_witness { @@ -26,6 +27,9 @@ class debug_api_impl void debug_generate_blocks( const std::string& debug_key, uint32_t count ); void debug_update_object( const fc::variant_object& update ); //void debug_save_db( std::string db_path ); + void debug_stream_json_objects( const std::string& filename ); + void debug_stream_json_objects_flush(); + std::shared_ptr< graphene::debug_witness_plugin::debug_witness_plugin > get_plugin(); graphene::app::application& app; }; @@ -102,6 +106,21 @@ void debug_api_impl::debug_update_object( const fc::variant_object& update ) db->debug_update( update ); } +std::shared_ptr< graphene::debug_witness_plugin::debug_witness_plugin > debug_api_impl::get_plugin() +{ + return app.get_plugin< graphene::debug_witness_plugin::debug_witness_plugin >( "debug_witness" ); +} + +void debug_api_impl::debug_stream_json_objects( const std::string& filename ) +{ + get_plugin()->set_json_object_stream( filename ); +} + +void debug_api_impl::debug_stream_json_objects_flush() +{ + get_plugin()->flush_json_object_stream(); +} + } // detail debug_api::debug_api( graphene::app::application& app ) @@ -124,4 +143,15 @@ void debug_api::debug_update_object( fc::variant_object update ) my->debug_update_object( update ); } +void debug_api::debug_stream_json_objects( std::string filename ) +{ + my->debug_stream_json_objects( filename ); +} + +void debug_api::debug_stream_json_objects_flush() +{ + my->debug_stream_json_objects_flush(); +} + + } } // graphene::debug_witness diff --git a/libraries/plugins/debug_witness/debug_witness.cpp b/libraries/plugins/debug_witness/debug_witness.cpp index 9d6601c4..7bb5562d 100644 --- a/libraries/plugins/debug_witness/debug_witness.cpp +++ b/libraries/plugins/debug_witness/debug_witness.cpp @@ -98,29 +98,76 @@ void debug_witness_plugin::plugin_startup() // connect needed signals - db.applied_block.connect([this](const graphene::chain::signed_block& b){ on_applied_block(b); }); - db.changed_objects.connect([this](const std::vector& ids){ on_changed_objects(ids); }); - db.removed_objects.connect([this](const std::vector& objs){ on_removed_objects(objs); }); + _applied_block_conn = db.applied_block.connect([this](const graphene::chain::signed_block& b){ on_applied_block(b); }); + _changed_objects_conn = db.changed_objects.connect([this](const std::vector& ids){ on_changed_objects(ids); }); + _removed_objects_conn = db.removed_objects.connect([this](const std::vector& objs){ on_removed_objects(objs); }); return; } void debug_witness_plugin::on_changed_objects( const std::vector& ids ) { - + if( _json_object_stream && (ids.size() > 0) ) + { + const chain::database& db = database(); + for( const graphene::db::object_id_type& oid : ids ) + { + const graphene::db::object* obj = db.find_object( oid ); + if( obj == nullptr ) + { + (*_json_object_stream) << "{\"id\":" << fc::json::to_string( oid ) << "}\n"; + } + else + { + (*_json_object_stream) << fc::json::to_string( obj->to_variant() ) << '\n'; + } + } + } } void debug_witness_plugin::on_removed_objects( const std::vector objs ) { - + /* + if( _json_object_stream ) + { + for( const graphene::db::object* obj : objs ) + { + (*_json_object_stream) << "{\"id\":" << fc::json::to_string( obj->id ) << "}\n"; + } + } + */ } void debug_witness_plugin::on_applied_block( const graphene::chain::signed_block& b ) { + if( _json_object_stream ) + { + (*_json_object_stream) << "{\"bn\":" << fc::to_string( b.block_num() ) << "}\n"; + } +} +void debug_witness_plugin::set_json_object_stream( const std::string& filename ) +{ + if( _json_object_stream ) + { + _json_object_stream->close(); + _json_object_stream.reset(); + } + _json_object_stream = std::make_shared< std::ofstream >( filename ); +} + +void debug_witness_plugin::flush_json_object_stream() +{ + if( _json_object_stream ) + _json_object_stream->flush(); } void debug_witness_plugin::plugin_shutdown() { + if( _json_object_stream ) + { + _json_object_stream->close(); + _json_object_stream.reset(); + } return; } diff --git a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp index 9306e4be..7985f27f 100644 --- a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp +++ b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp @@ -71,6 +71,17 @@ class debug_api // not implemented //void save_db( std::string db_path ); + /** + * Stream objects to file. (Hint: Create with mkfifo and pipe it to a script) + */ + + void debug_stream_json_objects( std::string filename ); + + /** + * Flush streaming file. + */ + void debug_stream_json_objects_flush(); + std::shared_ptr< detail::debug_api_impl > my; }; @@ -80,4 +91,6 @@ FC_API(graphene::debug_witness::debug_api, (debug_push_blocks) (debug_generate_blocks) (debug_update_object) + (debug_stream_json_objects) + (debug_stream_json_objects_flush) ) diff --git a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp index 1dde26cc..0e5c173f 100644 --- a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp +++ b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp @@ -45,6 +45,9 @@ public: virtual void plugin_startup() override; virtual void plugin_shutdown() override; + void set_json_object_stream( const std::string& filename ); + void flush_json_object_stream(); + private: void on_changed_objects( const std::vector& ids ); @@ -54,6 +57,11 @@ private: boost::program_options::variables_map _options; std::map _private_keys; + + std::shared_ptr< std::ofstream > _json_object_stream; + boost::signals2::scoped_connection _applied_block_conn; + boost::signals2::scoped_connection _changed_objects_conn; + boost::signals2::scoped_connection _removed_objects_conn; }; } } //graphene::debug_witness_plugin diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 847f2d6e..9f5d26eb 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1406,6 +1406,7 @@ class wallet_api void dbg_make_mia(string creator, string symbol); void dbg_push_blocks( std::string src_filename, uint32_t count ); void dbg_generate_blocks( std::string debug_wif_key, uint32_t count ); + void dbg_stream_json_objects( const std::string& filename ); void dbg_update_object( fc::variant_object update ); void flood_network(string prefix, uint32_t number_of_transactions); @@ -1580,6 +1581,7 @@ FC_API( graphene::wallet::wallet_api, (dbg_make_mia) (dbg_push_blocks) (dbg_generate_blocks) + (dbg_stream_json_objects) (dbg_update_object) (flood_network) (network_add_nodes) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 3dbeda82..3904967e 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2343,18 +2343,28 @@ public: { use_debug_api(); (*_remote_debug)->debug_push_blocks( src_filename, count ); + (*_remote_debug)->debug_stream_json_objects_flush(); } void dbg_generate_blocks( const std::string& debug_wif_key, uint32_t count ) { use_debug_api(); (*_remote_debug)->debug_generate_blocks( debug_wif_key, count ); + (*_remote_debug)->debug_stream_json_objects_flush(); + } + + void dbg_stream_json_objects( const std::string& filename ) + { + use_debug_api(); + (*_remote_debug)->debug_stream_json_objects( filename ); + (*_remote_debug)->debug_stream_json_objects_flush(); } void dbg_update_object( const fc::variant_object& update ) { use_debug_api(); (*_remote_debug)->debug_update_object( update ); + (*_remote_debug)->debug_stream_json_objects_flush(); } void use_network_node_api() @@ -3264,6 +3274,11 @@ void wallet_api::dbg_generate_blocks( std::string debug_wif_key, uint32_t count my->dbg_generate_blocks( debug_wif_key, count ); } +void wallet_api::dbg_stream_json_objects( const std::string& filename ) +{ + my->dbg_stream_json_objects( filename ); +} + void wallet_api::dbg_update_object( fc::variant_object update ) { my->dbg_update_object( update ); From 14f7b520bd58716d6fa28959474a4e857c67a7e0 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Mon, 7 Mar 2016 14:30:09 -0500 Subject: [PATCH 21/34] Disable negative voting on workers #607 --- libraries/chain/account_evaluator.cpp | 16 ++++++++++++++++ libraries/chain/db_maint.cpp | 3 ++- libraries/chain/hardfork.d/607.hf | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 libraries/chain/hardfork.d/607.hf diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index b44597e4..eb86764c 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -69,10 +70,25 @@ void verify_account_votes( const database& db, const account_options& options ) "Voted for more committee members than currently allowed (${c})", ("c", chain_params.maximum_committee_count) ); uint32_t max_vote_id = gpo.next_available_vote_id; + bool has_worker_votes = false; for( auto id : options.votes ) { FC_ASSERT( id < max_vote_id ); + has_worker_votes |= (id.type() == vote_id_type::worker); } + + if( has_worker_votes && (db.head_block_time() >= HARDFORK_607_TIME) ) + { + const auto& against_worker_idx = db.get_index_type().indices().get(); + for( auto id : options.votes ) + { + if( id.type() == vote_id_type::worker ) + { + FC_ASSERT( against_worker_idx.find( id ) == against_worker_idx.end() ); + } + } + } + } diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index c7e39d75..709cd583 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -102,11 +102,12 @@ void database::update_worker_votes() { auto& idx = get_index_type(); auto itr = idx.indices().get().begin(); + bool allow_negative_votes = (head_block_time() < HARDFORK_607_TIME); while( itr != idx.indices().get().end() ) { modify( *itr, [&]( worker_object& obj ){ obj.total_votes_for = _vote_tally_buffer[obj.vote_for]; - obj.total_votes_against = _vote_tally_buffer[obj.vote_against]; + obj.total_votes_against = allow_negative_votes ? _vote_tally_buffer[obj.vote_against] : 0; }); ++itr; } diff --git a/libraries/chain/hardfork.d/607.hf b/libraries/chain/hardfork.d/607.hf new file mode 100644 index 00000000..135619c2 --- /dev/null +++ b/libraries/chain/hardfork.d/607.hf @@ -0,0 +1,4 @@ +// #607 Disable negative voting on workers +#ifndef HARDFORK_607_TIME +#define HARDFORK_607_TIME (fc::time_point_sec( 1458061200 )) +#endif From 2a745f5882770161c369af9eae2d23ee87f9d8ee Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 15 Mar 2016 15:07:53 -0400 Subject: [PATCH 22/34] db_update.cpp: Include hardfork.hpp, fix non-unity build broken by #615 fix --- libraries/chain/db_update.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index aa4d5969..2219136e 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include From c4fc67602a06d9e95d9732b7f90912c343e38e72 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 15 Mar 2016 12:34:57 -0400 Subject: [PATCH 23/34] Implement hardfork for serialization fix #599 --- libraries/chain/account_evaluator.cpp | 13 +++++++++++++ libraries/chain/hardfork.d/599.hf | 4 ++++ 2 files changed, 17 insertions(+) create mode 100644 libraries/chain/hardfork.d/599.hf diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 1f29bb0a..f34af18f 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -62,6 +62,13 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio FC_ASSERT( !op.extensions.value.owner_special_authority.valid() ); FC_ASSERT( !op.extensions.value.active_special_authority.valid() ); } + if( d.head_block_time() < HARDFORK_599_TIME ) + { + FC_ASSERT( !op.extensions.value.null_ext.valid() ); + FC_ASSERT( !op.extensions.value.owner_special_authority.valid() ); + FC_ASSERT( !op.extensions.value.active_special_authority.valid() ); + FC_ASSERT( !op.extensions.value.buyback_options.valid() ); + } FC_ASSERT( d.find_object(op.options.voting_account), "Invalid proxy account specified." ); FC_ASSERT( fee_paying_account->is_lifetime_member(), "Only Lifetime members may register an account." ); @@ -223,6 +230,12 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio FC_ASSERT( !o.extensions.value.owner_special_authority.valid() ); FC_ASSERT( !o.extensions.value.active_special_authority.valid() ); } + if( d.head_block_time() < HARDFORK_599_TIME ) + { + FC_ASSERT( !o.extensions.value.null_ext.valid() ); + FC_ASSERT( !o.extensions.value.owner_special_authority.valid() ); + FC_ASSERT( !o.extensions.value.active_special_authority.valid() ); + } const auto& chain_params = d.get_global_properties().parameters; diff --git a/libraries/chain/hardfork.d/599.hf b/libraries/chain/hardfork.d/599.hf new file mode 100644 index 00000000..71f7e94e --- /dev/null +++ b/libraries/chain/hardfork.d/599.hf @@ -0,0 +1,4 @@ +// #599 Unpacking of extension is incorrect +#ifndef HARDFORK_599_TIME +#define HARDFORK_599_TIME (fc::time_point_sec( 1458061200 )) +#endif From ee3f81fa31e39a8ae4ebcfe5b3d78f2dbc103e1b Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 15 Mar 2016 15:58:02 -0400 Subject: [PATCH 24/34] Fix build broken by previous patch #592 --- libraries/wallet/wallet.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 5b5d6138..59a50829 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2183,7 +2183,8 @@ public: //ss << n; if (abs( round( n ) - n ) < 0.00000000001 ) { - ss << setiosflags( !ios::fixed ) << (int) n; + //ss << setiosflags( !ios::fixed ) << (int) n; // doesn't compile on Linux with gcc + ss << (int) n; } else if (n - floor(n) < 0.000001) { From e0c4c5e91469871db652476cb2a3d12ab9348695 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 15 Mar 2016 16:15:36 -0400 Subject: [PATCH 25/34] Wait for HARDFORK_599_TIME when needed in tests #599 --- tests/tests/fee_tests.cpp | 1 + tests/tests/operation_tests2.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/tests/tests/fee_tests.cpp b/tests/tests/fee_tests.cpp index 984c6fce..d6f26170 100644 --- a/tests/tests/fee_tests.cpp +++ b/tests/tests/fee_tests.cpp @@ -747,6 +747,7 @@ BOOST_AUTO_TEST_CASE( stealth_fba_test ) generate_blocks( HARDFORK_555_TIME ); generate_blocks( HARDFORK_563_TIME ); generate_blocks( HARDFORK_572_TIME ); + generate_blocks( HARDFORK_599_TIME ); // Philbin (registrar who registers Rex) diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index 6d08d686..8cefec4e 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -1338,6 +1338,7 @@ BOOST_AUTO_TEST_CASE( top_n_special ) ACTORS( (alice)(bob)(chloe)(dan)(izzy)(stan) ); generate_blocks( HARDFORK_516_TIME ); + generate_blocks( HARDFORK_599_TIME ); try { @@ -1490,6 +1491,7 @@ BOOST_AUTO_TEST_CASE( buyback ) generate_blocks( HARDFORK_538_TIME ); generate_blocks( HARDFORK_555_TIME ); + generate_blocks( HARDFORK_599_TIME ); try { From b7b4d4fc5de01c2547e5748a582de97a7e09f247 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Mon, 14 Mar 2016 23:32:43 -0400 Subject: [PATCH 26/34] Deprecate annual memberships #613 --- libraries/chain/account_evaluator.cpp | 2 ++ libraries/chain/db_maint.cpp | 38 ++++++++++++++++++++++++++- libraries/chain/hardfork.d/613.hf | 4 +++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 libraries/chain/hardfork.d/613.hf diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 1f29bb0a..42922473 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -371,11 +371,13 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator: a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - a.network_fee_percentage; } else if( a.is_annual_member(d.head_block_time()) ) { // Renew an annual subscription that's still in effect. + FC_ASSERT( d.head_block_time() <= HARDFORK_613_TIME ); FC_ASSERT(a.membership_expiration_date - d.head_block_time() < fc::days(3650), "May not extend annual membership more than a decade into the future."); a.membership_expiration_date += fc::days(365); } else { // Upgrade from basic account. + FC_ASSERT( d.head_block_time() <= HARDFORK_613_TIME ); a.statistics(d).process_fees(a, d); assert(a.is_basic_account(d.head_block_time())); a.referrer = a.get_id(); diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index c7e39d75..bccf4958 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -684,6 +684,37 @@ void create_buyback_orders( database& db ) return; } +void deprecate_annual_members( database& db ) +{ + const auto& account_idx = db.get_index_type().indices().get(); + fc::time_point_sec now = db.head_block_time(); + for( const account_object& acct : account_idx ) + { + try + { + transaction_evaluation_state upgrade_context(&db); + upgrade_context.skip_fee_schedule_check = true; + + if( acct.is_annual_member( now ) ) + { + account_upgrade_operation upgrade_vop; + upgrade_vop.fee = asset( 0, asset_id_type() ); + upgrade_vop.account_to_upgrade = acct.id; + upgrade_vop.upgrade_to_lifetime_member = true; + db.apply_operation( upgrade_context, upgrade_vop ); + } + } + catch( const fc::exception& e ) + { + // we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account + wlog( "Skipping annual member deprecate processing for account ${a} (${an}) at block ${n}; exception was ${e}", + ("a", acct.id)("an", acct.name)("n", db.head_block_num())("e", e.to_detail_string()) ); + continue; + } + } + return; +} + void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { const auto& gpo = get_global_properties(); @@ -830,7 +861,12 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g } } - modify(get_dynamic_global_properties(), [next_maintenance_time](dynamic_global_property_object& d) { + const dynamic_global_property_object& dgpo = get_dynamic_global_properties(); + + if( (dgpo.next_maintenance_time < HARDFORK_613_TIME) && (next_maintenance_time >= HARDFORK_613_TIME) ) + deprecate_annual_members(*this); + + modify(dgpo, [next_maintenance_time](dynamic_global_property_object& d) { d.next_maintenance_time = next_maintenance_time; d.accounts_registered_this_interval = 0; }); diff --git a/libraries/chain/hardfork.d/613.hf b/libraries/chain/hardfork.d/613.hf new file mode 100644 index 00000000..74c740fb --- /dev/null +++ b/libraries/chain/hardfork.d/613.hf @@ -0,0 +1,4 @@ +// #613 Deprecate annual membership +#ifndef HARDFORK_613_TIME +#define HARDFORK_613_TIME (fc::time_point_sec( 1458061200 )) +#endif From abc7853c995e6cc564aaf7f18b28ba07af2bf36a Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Sun, 26 Jun 2016 15:41:07 -0400 Subject: [PATCH 27/34] Initial work on dividend-paying assets. Basic functionality works in simple cases. --- libraries/app/impacted.cpp | 1 + libraries/chain/asset_evaluator.cpp | 61 ++++ libraries/chain/db_init.cpp | 4 + libraries/chain/db_maint.cpp | 287 ++++++++++++++++++ .../include/graphene/chain/account_object.hpp | 55 ++++ .../graphene/chain/asset_evaluator.hpp | 12 + .../include/graphene/chain/asset_object.hpp | 86 ++++++ .../graphene/chain/protocol/asset_ops.hpp | 68 +++++ .../graphene/chain/protocol/operations.hpp | 3 +- .../include/graphene/chain/protocol/types.hpp | 12 +- libraries/chain/protocol/asset_ops.cpp | 10 + libraries/wallet/wallet.cpp | 25 +- tests/common/database_fixture.cpp | 18 ++ tests/common/database_fixture.hpp | 3 + tests/tests/operation_tests.cpp | 222 ++++++++++++++ 15 files changed, 863 insertions(+), 4 deletions(-) diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 85787423..ad9d9e0c 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -90,6 +90,7 @@ struct get_impacted_account_visitor } void operator()( const asset_update_bitasset_operation& op ) {} + void operator()( const asset_update_dividend_operation& op ) {} void operator()( const asset_update_feed_producers_operation& op ) {} void operator()( const asset_issue_operation& op ) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 25daa7cd..f7405366 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -32,6 +32,8 @@ #include +#include + namespace graphene { namespace chain { void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) @@ -355,6 +357,65 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } +void_result asset_update_dividend_evaluator::do_evaluate(const asset_update_dividend_operation& o) +{ try { + database& d = db(); + + const asset_object& a = o.asset_to_update(d); + asset_to_update = &a; + + FC_ASSERT( o.issuer == a.issuer, "", ("o.issuer", o.issuer)("a.issuer", a.issuer) ); + auto& params = db().get_global_properties().parameters; + if (o.new_options.payout_interval && + *o.new_options.payout_interval < params.maintenance_interval) + FC_THROW("New payout interval may not be less than the maintenance interval", + ("new_payout_interval", o.new_options.payout_interval)("maintenance_interval", params.maintenance_interval)); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + +void_result asset_update_dividend_evaluator::do_apply( const asset_update_dividend_operation& op ) +{ try { + database& d = db(); + if (!asset_to_update->dividend_data_id) + { + // this was not a dividend-paying asset, we're converting it to a dividend-paying asset + std::string dividend_distribution_account_name(boost::to_lower_copy(asset_to_update->symbol) + "-dividend-distribution"); + + const auto& new_acnt_object = db().create( [&]( account_object& obj ){ + obj.registrar = op.issuer; + obj.referrer = op.issuer; + obj.lifetime_referrer = op.issuer(db()).lifetime_referrer; + + auto& params = db().get_global_properties().parameters; + obj.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + obj.lifetime_referrer_fee_percentage = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE; + obj.referrer_rewards_percentage = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE; + + obj.name = dividend_distribution_account_name; + obj.owner.weight_threshold = 1; + obj.active.weight_threshold = 1; + obj.statistics = db().create([&](account_statistics_object& s){s.owner = obj.id;}).id; + }); + + const asset_dividend_data_object& dividend_data = d.create( [&]( asset_dividend_data_object& dividend_data_obj ) { + dividend_data_obj.options = op.new_options; + dividend_data_obj.dividend_distribution_account = new_acnt_object.id; + }); + + d.modify(*asset_to_update, [&](asset_object& a) { + a.dividend_data_id = dividend_data.id; + }); + } + else + { + const asset_dividend_data_object& dividend_data = asset_to_update->dividend_data(d); + d.modify(dividend_data, [&]( asset_dividend_data_object& dividend_data_obj ) { + dividend_data_obj.options = op.new_options; + }); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_evaluator::operation_type& o) { try { database& d = db(); diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 914b3fa8..8e0372ba 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -143,6 +143,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); @@ -204,6 +205,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); add_index< primary_index> >(); add_index< primary_index> >(); add_index< primary_index> >(); @@ -216,6 +218,8 @@ void database::initialize_indexes() add_index< primary_index< buyback_index > >(); add_index< primary_index< simple_index< fba_accumulator_object > > >(); + add_index< primary_index >(); + add_index< primary_index >(); } void database::init_genesis(const genesis_state_type& genesis_state) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index d515a961..6bef6051 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -716,6 +716,291 @@ void deprecate_annual_members( database& db ) return; } +void schedule_pending_dividend_balances(database& db, + const asset_object& dividend_holder_asset_obj, + const asset_dividend_data_object& dividend_data, + const account_balance_index& balance_index, + const distributed_dividend_balance_object_index& distributed_dividend_balance_index, + const pending_dividend_payout_balance_object_index& pending_payout_balance_index) +{ + dlog("Processing dividend payments for dividend holder asset asset type ${holder_asset}", ("holder_asset", dividend_holder_asset_obj.symbol)); + auto current_distribution_account_balance_range = + balance_index.indices().get().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account)); + auto previous_distribution_account_balance_range = + distributed_dividend_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + // the current range is now all current balances for the distribution account, sorted by asset_type + // the previous range is now all previous balances for this account, sorted by asset type + + auto current_distribution_account_balance_iter = current_distribution_account_balance_range.first; + auto previous_distribution_account_balance_iter = previous_distribution_account_balance_range.first; + dlog("Current balances in distribution account: ${current}, Previous balances: ${previous}", + ("current", std::distance(current_distribution_account_balance_range.first, current_distribution_account_balance_range.second)) + ("previous", std::distance(previous_distribution_account_balance_range.first, previous_distribution_account_balance_range.second))); + + while (current_distribution_account_balance_iter != current_distribution_account_balance_range.second || + previous_distribution_account_balance_iter != previous_distribution_account_balance_range.second) + { + share_type current_balance; + share_type previous_balance; + asset_id_type payout_asset_type; + + if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || + current_distribution_account_balance_iter->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type) + { + payout_asset_type = current_distribution_account_balance_iter->asset_type; + current_balance = current_distribution_account_balance_iter->balance; + idump((payout_asset_type)(current_balance)); + } + else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.second || + previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->asset_type) + { + payout_asset_type = previous_distribution_account_balance_iter->dividend_payout_asset_type; + previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; + idump((payout_asset_type)(previous_balance)); + } + else + { + payout_asset_type = current_distribution_account_balance_iter->asset_type; + current_balance = current_distribution_account_balance_iter->balance; + previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; + idump((payout_asset_type)(current_balance)(previous_balance)); + } + + share_type delta_balance = current_balance - previous_balance; + dlog("Processing dividend payments of asset type ${payout_asset_type}, delta balance is ${delta_balance}", ("payout_asset_type", payout_asset_type(db).symbol)("delta_balance", delta_balance)); + if (delta_balance > 0) + { + // we need to pay out delta_balance to shareholders proportional to their stake + auto holder_account_balance_range = + balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + share_type total_balance_of_dividend_asset; + for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second)) + if (holder_balance_object.owner != dividend_data.dividend_distribution_account) + // TODO: if holder_balance_object.owner is able to accept payout_asset_type + total_balance_of_dividend_asset += holder_balance_object.balance; + + dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}", + ("count", std::distance(holder_account_balance_range.first, holder_account_balance_range.second)) + ("total", total_balance_of_dividend_asset)); + share_type remaining_amount_to_distribute = delta_balance; + share_type remaining_balance_of_dividend_asset = total_balance_of_dividend_asset; + + for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second)) + if (holder_balance_object.owner != dividend_data.dividend_distribution_account) + { + // TODO: if holder_balance_object.owner is able to accept payout_asset_type + fc::uint128_t amount_to_credit(remaining_amount_to_distribute.value); + amount_to_credit *= holder_balance_object.balance.value; + amount_to_credit /= remaining_balance_of_dividend_asset.value; + share_type shares_to_credit((int64_t)amount_to_credit.to_uint64()); + + remaining_amount_to_distribute -= shares_to_credit; + remaining_balance_of_dividend_asset -= holder_balance_object.balance; + + dlog("Crediting account ${account} with ${amount}", ("account", holder_balance_object.owner(db).name)("amount", amount_to_credit)); + auto pending_payout_iter = + pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner)); + if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) + db.create( [&]( pending_dividend_payout_balance_object& obj ){ + obj.owner = holder_balance_object.owner; + obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; + obj.dividend_payout_asset_type = payout_asset_type; + obj.pending_balance = shares_to_credit; + }); + else + db.modify(*pending_payout_iter, [&]( pending_dividend_payout_balance_object& pending_balance ){ + pending_balance.pending_balance += shares_to_credit; + }); + } + + for (const auto& pending_payout : pending_payout_balance_index.indices()) + { + dlog("Pending payout: ${account_name} -> ${amount}", ("account_name", pending_payout.owner(db).name)("amount", pending_payout.pending_balance)); + } + + share_type distributed_amount = current_balance - remaining_amount_to_distribute; + if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || + previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type) + db.create( [&]( distributed_dividend_balance_object& obj ){ + obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; + obj.dividend_payout_asset_type = payout_asset_type; + obj.balance_at_last_maintenance_interval = distributed_amount; + }); + else + db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){ + obj.balance_at_last_maintenance_interval = distributed_amount; + }); + } + else if (delta_balance < 0) + { + // some amount of the asset has been withdrawn from the dividend_distribution_account, + // meaning the current pending payout balances will add up to more than our current balance. + // This should be extremely rare. + // Reduce all pending payouts proportionally + share_type total_pending_balances; + auto pending_payouts_range = + pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); + + for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) + total_pending_balances += pending_balance_object.pending_balance; + + share_type remaining_amount_to_recover = -delta_balance; + share_type remaining_pending_balances = total_pending_balances; + for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) + { + fc::uint128_t amount_to_debit(remaining_amount_to_recover.value); + amount_to_debit *= pending_balance_object.pending_balance.value; + amount_to_debit /= remaining_pending_balances.value; + share_type shares_to_debit((int64_t)amount_to_debit.to_uint64()); + + remaining_amount_to_recover -= shares_to_debit; + remaining_pending_balances -= pending_balance_object.pending_balance; + + db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ + pending_balance.pending_balance -= shares_to_debit; + }); + } + + if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || + previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type) + db.create( [&]( distributed_dividend_balance_object& obj ){ + obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; + obj.dividend_payout_asset_type = payout_asset_type; + obj.balance_at_last_maintenance_interval = 0; + }); + else + db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){ + obj.balance_at_last_maintenance_interval = 0; + }); + } + + // iterate + if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || + current_distribution_account_balance_iter->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type) + ++current_distribution_account_balance_iter; + else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.second || + previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->asset_type) + ++previous_distribution_account_balance_iter; + else + { + ++current_distribution_account_balance_iter; + ++previous_distribution_account_balance_iter; + } + } +} + +void process_dividend_assets(database& db) +{ + ilog("In process_dividend_assets time ${time}", ("time", db.head_block_time())); + + const account_balance_index& balance_index = db.get_index_type(); + const distributed_dividend_balance_object_index& distributed_dividend_balance_index = db.get_index_type(); + const pending_dividend_payout_balance_object_index& pending_payout_balance_index = db.get_index_type(); + + // TODO: switch to iterating over only dividend assets (generalize the by_type index) + for( const asset_object& dividend_holder_asset_obj : db.get_index_type().indices() ) + if (dividend_holder_asset_obj.dividend_data_id) + { + const asset_dividend_data_object& dividend_data = dividend_holder_asset_obj.dividend_data(db); + schedule_pending_dividend_balances(db, dividend_holder_asset_obj, dividend_data, + balance_index, distributed_dividend_balance_index, pending_payout_balance_index); + fc::time_point_sec current_head_block_time = db.head_block_time(); + if (dividend_data.options.next_payout_time && + db.head_block_time() >= *dividend_data.options.next_payout_time) + { + dlog("Dividend payout time has arrived for asset ${holder_asset}", + ("holder_asset", dividend_holder_asset_obj.symbol)); + +#ifndef NDEBUG + // dump balances before the payouts for debugging + const auto& balance_idx = db.get_index_type().indices().get(); + auto holder_account_balance_range = balance_idx.equal_range(boost::make_tuple(dividend_data.dividend_distribution_account)); + for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second)) + ilog(" Current balance: ${asset}", ("asset", asset(holder_balance_object.balance, holder_balance_object.asset_type))); +#endif + + // when we do the payouts, we first increase the balances in all of the receiving accounts + // and use this map to keep track of the total amount of each asset paid out. + // Afterwards, we decrease the distribution account's balance by the total amount paid out, + // and modify the distributed_balances accordingly +#ifndef NDEBUG + // for debugging, sum up our payouts here + std::map amounts_paid_out_by_asset; +#endif + auto pending_payouts_range = + pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; ) + { + const pending_dividend_payout_balance_object& pending_balance_object = *pending_balance_object_iter; + ilog("Processing payout of ${asset} to account ${account}", + ("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)) + ("account", pending_balance_object.owner(db).name)); + + db.adjust_balance(pending_balance_object.owner, + asset(pending_balance_object.pending_balance, + pending_balance_object.dividend_payout_asset_type)); +#ifndef NDEBUG + amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance; +#endif + + ++pending_balance_object_iter; + db.remove(pending_balance_object); + } + + // now debit the total amount of dividends paid out from the distribution account + auto distributed_balance_range = + distributed_dividend_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + +#ifndef NDEBUG + // validate that we actually paid out exactly as much as we had planned to + assert(amounts_paid_out_by_asset.size() == std::distance(distributed_balance_range.first, distributed_balance_range.second)); + if (amounts_paid_out_by_asset.size() == std::distance(distributed_balance_range.first, distributed_balance_range.second)) + { + auto distributed_balance_object_iter = distributed_balance_range.first; + for (const auto& asset_and_amount_paid_out : amounts_paid_out_by_asset) + { + assert(distributed_balance_object_iter->dividend_payout_asset_type == asset_and_amount_paid_out.first); + assert(distributed_balance_object_iter->balance_at_last_maintenance_interval == asset_and_amount_paid_out.second); + ++distributed_balance_object_iter; + } + } +#endif + + for (auto distributed_balance_object_iter = distributed_balance_range.first; distributed_balance_object_iter != distributed_balance_range.second; ) + { + const distributed_dividend_balance_object& distributed_balance_object = *distributed_balance_object_iter; + db.adjust_balance(dividend_data.dividend_distribution_account, + asset(-distributed_balance_object.balance_at_last_maintenance_interval, + distributed_balance_object.dividend_payout_asset_type)); + ++distributed_balance_object_iter; + db.remove(distributed_balance_object); // now they've been paid out, reset to zero + } + + // now schedule the next payout time + db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) { + dlog("Updating dividend payout time, new values are:"); + dividend_data_obj.last_scheduled_payout_time = dividend_data_obj.options.next_payout_time; + dividend_data_obj.last_payout_time = current_head_block_time; + fc::optional next_payout_time; + if (dividend_data_obj.options.payout_interval) + { + // if there was a previous payout, make our next payment one interval + uint32_t current_time_sec = current_head_block_time.sec_since_epoch(); + fc::time_point_sec reference_time = *dividend_data_obj.last_scheduled_payout_time; + uint32_t next_possible_time_sec = dividend_data_obj.last_scheduled_payout_time->sec_since_epoch(); + do + next_possible_time_sec += *dividend_data_obj.options.payout_interval; + while (next_possible_time_sec <= current_time_sec); + + next_payout_time = next_possible_time_sec; + } + dividend_data_obj.options.next_payout_time = next_payout_time; + idump((dividend_data_obj.last_scheduled_payout_time)(dividend_data_obj.last_payout_time)(dividend_data_obj.options.next_payout_time)); + }); + } + } +} + void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { const auto& gpo = get_global_properties(); @@ -723,6 +1008,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g distribute_fba_balances(*this); create_buyback_orders(*this); + process_dividend_assets(*this); + struct vote_tally_helper { database& d; const global_property_object& props; diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index faf59e22..90becf9e 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -308,6 +308,31 @@ namespace graphene { namespace chain { /** maps the referrer to the set of accounts that they have referred */ map< account_id_type, set > referred_by; }; + + /** + * @brief Tracks a pending payout of a single dividend payout asset + * from a single dividend holder asset to a holder's account. + * + * Each maintenance interval, this will be adjusted to account for + * any new transfers to the dividend distribution account. + * @ingroup object + * + */ + class pending_dividend_payout_balance_object : public abstract_object + { + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_pending_dividend_payout_balance_object_type; + + account_id_type owner; + asset_id_type dividend_holder_asset_type; + asset_id_type dividend_payout_asset_type; + share_type pending_balance; + + asset get_pending_balance()const { return asset(pending_balance, dividend_payout_asset_type); } + void adjust_balance(const asset& delta); + }; + struct by_account_asset; struct by_asset_balance; @@ -364,6 +389,31 @@ namespace graphene { namespace chain { */ typedef generic_index account_index; + struct by_dividend_asset_account_asset{}; + + /** + * @ingroup object_index + */ + typedef multi_index_container< + pending_dividend_payout_balance_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key< + pending_dividend_payout_balance_object, + member, + member, + member + > + > + > + > pending_dividend_payout_balance_object_multi_index_type; + + /** + * @ingroup object_index + */ + typedef generic_index pending_dividend_payout_balance_object_index; + }} FC_REFLECT_DERIVED( graphene::chain::account_object, @@ -392,3 +442,8 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (pending_fees)(pending_vested_fees) ) +FC_REFLECT_DERIVED( graphene::chain::pending_dividend_payout_balance_object, + (graphene::db::object), + (owner)(dividend_holder_asset_type)(dividend_payout_asset_type)(pending_balance) ) + + diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index eb8d5789..234a60d7 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -82,6 +82,18 @@ namespace graphene { namespace chain { const asset_bitasset_data_object* bitasset_to_update = nullptr; }; + class asset_update_dividend_evaluator : public evaluator + { + public: + typedef asset_update_dividend_operation operation_type; + + void_result do_evaluate( const asset_update_dividend_operation& o ); + void_result do_apply( const asset_update_dividend_operation& o ); + + const asset_object* asset_to_update = nullptr; + const asset_dividend_data_object* asset_dividend_data_to_update = nullptr; + }; + class asset_update_feed_producers_evaluator : public evaluator { public: diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 28d5974e..db3e386b 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -132,6 +132,9 @@ namespace graphene { namespace chain { optional buyback_account; + /// Extra data associated with dividend-paying assets. + optional dividend_data_id; + asset_id_type get_id()const { return id; } void validate()const @@ -148,6 +151,10 @@ namespace graphene { namespace chain { const asset_bitasset_data_object& bitasset_data(const DB& db)const { assert(bitasset_data_id); return db.get(*bitasset_data_id); } + template + const asset_dividend_data_object& dividend_data(const DB& db)const + { assert(dividend_data_id); return db.get(*dividend_data_id); } + template const asset_dynamic_data_object& dynamic_data(const DB& db)const { return db.get(dynamic_asset_data_id); } @@ -247,6 +254,72 @@ namespace graphene { namespace chain { > asset_object_multi_index_type; typedef generic_index asset_index; + /** + * @brief contains properties that only apply to dividend-paying assets + * + * @ingroup object + * @ingroup implementation + */ + class asset_dividend_data_object : public abstract_object + { + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_asset_dividend_data_type; + + /// The tunable options for Dividend-paying assets are stored in this field. + dividend_asset_options options; + + /// The time payouts on this asset were scheduled to be processed last + fc::optional last_scheduled_payout_time; + + /// The time payouts on this asset were last processed + /// (this should be the maintenance interval at or after last_scheduled_payout_time) + fc::optional last_payout_time; + + /// The account which collects pending payouts + account_id_type dividend_distribution_account; + }; + typedef multi_index_container< + asset_dividend_data_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > > + > + > asset_dividend_data_object_multi_index_type; + typedef generic_index asset_dividend_data_object_index; + + + // This tracks the balances in a dividend distribution account at the last time + // pending dividend payouts were calculated (last maintenance interval). + // At each maintenance interval, we will compare the current balance to the + // balance stored here to see how much was deposited during that interval. + class distributed_dividend_balance_object : public abstract_object + { + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_distributed_dividend_balance_data_type; + + asset_id_type dividend_holder_asset_type; + asset_id_type dividend_payout_asset_type; + share_type balance_at_last_maintenance_interval; + }; + struct by_dividend_payout_asset{}; + typedef multi_index_container< + distributed_dividend_balance_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key< + distributed_dividend_balance_object, + member, + member + > + > + > + > distributed_dividend_balance_object_multi_index_type; + typedef generic_index distributed_dividend_balance_object_index; + + + } } // graphene::chain FC_REFLECT_DERIVED( graphene::chain::asset_dynamic_data_object, (graphene::db::object), @@ -262,6 +335,19 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db:: (settlement_price) (settlement_fund) ) + +FC_REFLECT_DERIVED( graphene::chain::asset_dividend_data_object, (graphene::db::object), + (options) + (last_scheduled_payout_time) + (last_payout_time ) + (dividend_distribution_account) + ) + +FC_REFLECT_DERIVED( graphene::chain::distributed_dividend_balance_object, (graphene::db::object), + (dividend_holder_asset_type) + (dividend_payout_asset_type) + (balance_at_last_maintenance_interval) + ) FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object), (symbol) diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index 3f5ede19..eab3ae38 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -112,6 +112,30 @@ namespace graphene { namespace chain { void validate()const; }; + /** + * @brief The dividend_asset_options struct contains configurable options available only to dividend-paying assets. + * + * @note Changes to this struct will break protocol compatibility + */ + struct dividend_asset_options { + /// Time when the next payout should occur. + /// The payouts will happen on the maintenance interval at or after this time + /// If this is set to null, there will be no payouts. + fc::optional next_payout_time; + /// If payouts happen on a fixed schedule, this specifies the interval between + /// payouts in seconds. After each payout, the next payout time will be incremented by + /// this amount. + /// If payout_interval is not set, the next payout (if any) will be the last until + /// the options are updated again. + fc::optional payout_interval; + + extensions_type extensions; + + /// Perform internal consistency checks. + /// @throws fc::exception if any check fails + void validate()const; + }; + /** * @ingroup operations @@ -319,6 +343,35 @@ namespace graphene { namespace chain { void validate()const; }; + /** + * @brief Update options specific to dividend-paying assets + * @ingroup operations + * + * Dividend-paying assets have some options which are not relevant to other asset types. + * This operation is used to update those options an an existing dividend-paying asset. + * This can also be used to convert a non-dividend-paying asset into a dividend-paying + * asset. + * + * @pre @ref issuer MUST be an existing account and MUST match asset_object::issuer on @ref asset_to_update + * @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it + * @pre @ref new_options SHALL be internally consistent, as verified by @ref validate() + * @post @ref asset_to_update will have dividend-specific options matching those of new_options + */ + struct asset_update_dividend_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type issuer; + asset_id_type asset_to_update; + + dividend_asset_options new_options; + extensions_type extensions; + + account_id_type fee_payer()const { return issuer; } + void validate()const; + }; + /** * @brief Update the set of feed-producing accounts for a BitAsset * @ingroup operations @@ -462,6 +515,13 @@ FC_REFLECT( graphene::chain::asset_options, (description) (extensions) ) + +FC_REFLECT( graphene::chain::dividend_asset_options, + (next_payout_time) + (payout_interval) + (extensions) + ) + FC_REFLECT( graphene::chain::bitasset_options, (feed_lifetime_sec) (minimum_feeds) @@ -480,6 +540,7 @@ FC_REFLECT( graphene::chain::asset_settle_cancel_operation::fee_parameters_type, FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_update_bitasset_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::asset_update_dividend_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_update_feed_producers_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_publish_feed_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) ) @@ -511,6 +572,13 @@ FC_REFLECT( graphene::chain::asset_update_bitasset_operation, (new_options) (extensions) ) +FC_REFLECT( graphene::chain::asset_update_dividend_operation, + (fee) + (issuer) + (asset_to_update) + (new_options) + (extensions) + ) FC_REFLECT( graphene::chain::asset_update_feed_producers_operation, (fee)(issuer)(asset_to_update)(new_feed_producers)(extensions) ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 7f2639f1..0c02ed75 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -91,7 +91,8 @@ namespace graphene { namespace chain { transfer_from_blind_operation, asset_settle_cancel_operation, // VIRTUAL asset_claim_fees_operation, - fba_distribute_operation // VIRTUAL + fba_distribute_operation, // VIRTUAL + asset_update_dividend_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 5237fcad..948e22d9 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -155,7 +155,10 @@ namespace graphene { namespace chain { impl_budget_record_object_type, impl_special_authority_object_type, impl_buyback_object_type, - impl_fba_accumulator_object_type + impl_fba_accumulator_object_type, + impl_asset_dividend_data_type, + impl_pending_dividend_payout_balance_object_type, + impl_distributed_dividend_balance_data_type }; //typedef fc::unsigned_int object_id_type; @@ -207,11 +210,15 @@ namespace graphene { namespace chain { class special_authority_object; class buyback_object; class fba_accumulator_object; + class asset_dividend_data_object; + class pending_dividend_payout_balance_object; typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type; typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type; typedef object_id< implementation_ids, impl_asset_dynamic_data_type, asset_dynamic_data_object> asset_dynamic_data_id_type; typedef object_id< implementation_ids, impl_asset_bitasset_data_type, asset_bitasset_data_object> asset_bitasset_data_id_type; + typedef object_id< implementation_ids, impl_asset_dividend_data_type, asset_dividend_data_object> asset_dividend_data_id_type; + typedef object_id< implementation_ids, impl_pending_dividend_payout_balance_object_type, pending_dividend_payout_balance_object> pending_dividend_payout_balance_object_type; typedef object_id< implementation_ids, impl_account_balance_object_type, account_balance_object> account_balance_id_type; typedef object_id< implementation_ids, impl_account_statistics_object_type,account_statistics_object> account_statistics_id_type; typedef object_id< implementation_ids, impl_transaction_object_type, transaction_object> transaction_obj_id_type; @@ -359,6 +366,9 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_special_authority_object_type) (impl_buyback_object_type) (impl_fba_accumulator_object_type) + (impl_asset_dividend_data_type) + (impl_pending_dividend_payout_balance_object_type) + (impl_distributed_dividend_balance_data_type) ) FC_REFLECT_TYPENAME( graphene::chain::share_type ) diff --git a/libraries/chain/protocol/asset_ops.cpp b/libraries/chain/protocol/asset_ops.cpp index 1626a2a9..65bb2cdc 100644 --- a/libraries/chain/protocol/asset_ops.cpp +++ b/libraries/chain/protocol/asset_ops.cpp @@ -180,6 +180,12 @@ void asset_update_bitasset_operation::validate() const new_options.validate(); } +void asset_update_dividend_operation::validate() const +{ + FC_ASSERT( fee.amount >= 0 ); + new_options.validate(); +} + void asset_update_feed_producers_operation::validate() const { FC_ASSERT( fee.amount >= 0 ); @@ -198,6 +204,10 @@ void bitasset_options::validate() const FC_ASSERT(maximum_force_settlement_volume <= GRAPHENE_100_PERCENT); } +void dividend_asset_options::validate() const +{ +} + void asset_options::validate()const { FC_ASSERT( max_supply > 0 ); diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 3cec70d2..d52d5f58 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1199,6 +1199,27 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) } + signed_transaction update_dividend_asset(string symbol, + dividend_asset_options new_options, + bool broadcast /* = false */) + { try { + optional asset_to_update = find_asset(symbol); + if (!asset_to_update) + FC_THROW("No asset with that symbol exists!"); + + asset_update_dividend_operation update_op; + update_op.issuer = asset_to_update->issuer; + update_op.asset_to_update = asset_to_update->id; + update_op.new_options = new_options; + + signed_transaction tx; + tx.operations.push_back( update_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) } + signed_transaction update_asset_feed_producers(string symbol, flat_set new_feed_producers, bool broadcast /* = false */) @@ -2207,7 +2228,7 @@ public: << "\n=====================================================================================" << "|=====================================================================================\n"; - for (int i = 0; i < bids.size() || i < asks.size() ; i++) + for (unsigned i = 0; i < bids.size() || i < asks.size() ; i++) { if ( i < bids.size() ) { @@ -2788,7 +2809,7 @@ vector wallet_api::get_account_history(string name, int limit) auto memo = o.op.visit(detail::operation_printer(ss, *my, o.result)); result.push_back( operation_detail{ memo, ss.str(), o } ); } - if( current.size() < std::min(100,limit) ) + if( (int)current.size() < std::min(100,limit) ) break; limit -= current.size(); } diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 67e4dd0d..23e38dad 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1036,6 +1036,24 @@ int64_t database_fixture::get_balance( const account_object& account, const asse return db.get_balance(account.get_id(), a.get_id()).amount.value; } +int64_t database_fixture::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 +{ + const pending_dividend_payout_balance_object_index& pending_payout_balance_index = + db.get_index_type(); + dlog("searching ${a}", ("a", dividend_holder_asset_type(db).symbol)); + dlog("searching ${b}", ("b", dividend_payout_asset_type(db).symbol)); + dlog("searching ${c}", ("c", dividend_holder_account_id(db).name)); + dlog("searching ${a} ${b} ${c}", ("a", dividend_holder_asset_type(db).symbol)("b", dividend_payout_asset_type(db).symbol)("c", dividend_holder_account_id(db).name)); + auto pending_payout_iter = + pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id)); + if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) + return 0; + else + return pending_payout_iter->pending_balance.value; +} + vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const { vector< operation_history_object > result; diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index ec5e9bd7..de859a86 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -280,6 +280,9 @@ struct database_fixture { 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; }; diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 7b3867d7..fc9bdefa 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1111,6 +1111,228 @@ BOOST_AUTO_TEST_CASE( uia_fees ) } } +BOOST_AUTO_TEST_CASE( create_dividend_uia ) +{ + using namespace graphene; + try { + BOOST_TEST_MESSAGE("Creating dividend holder asset"); + { + asset_create_operation creator; + creator.issuer = account_id_type(); + creator.fee = asset(); + creator.symbol = "DIVIDEND"; + creator.common_options.max_supply = 100000000; + creator.precision = 2; + creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ + creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + creator.common_options.flags = charge_market_fee; + creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))}); + trx.operations.push_back(std::move(creator)); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + // it should not yet be a divdend asset + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + BOOST_CHECK(!dividend_holder_asset_object.dividend_data_id); + + BOOST_TEST_MESSAGE("Converting the new asset to a dividend holder asset"); + { + asset_update_dividend_operation op; + op.issuer = dividend_holder_asset_object.issuer; + op.asset_to_update = dividend_holder_asset_object.id; + op.new_options.next_payout_time = fc::time_point::now() + fc::minutes(1); + op.new_options.payout_interval = 60 * 60 * 24 * 7; // one week + + trx.operations.push_back(op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + //const auto& test_readback = get_asset("TEST"); + //BOOST_REQUIRE(test_readback.dividend_data_id); + BOOST_TEST_MESSAGE("Verifying the dividend holder asset options"); + BOOST_REQUIRE(dividend_holder_asset_object.dividend_data_id); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + { + BOOST_REQUIRE(dividend_data.options.payout_interval); + BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24 * 7); + } + + BOOST_TEST_MESSAGE("Updating the payout interval"); + { + asset_update_dividend_operation op; + op.issuer = dividend_holder_asset_object.issuer; + op.asset_to_update = dividend_holder_asset_object.id; + op.new_options.next_payout_time = fc::time_point::now() + fc::minutes(1); + op.new_options.payout_interval = 60 * 60 * 24; // one day + trx.operations.push_back(op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Verifying the updated dividend holder asset options"); + { + BOOST_REQUIRE(dividend_data.options.payout_interval); + BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24); + } + + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + BOOST_CHECK_EQUAL(dividend_distribution_account.name, "dividend-dividend-distribution"); + + + BOOST_TEST_MESSAGE("Creating test accounts"); + create_account("alice"); + create_account("bob"); + create_account("carol"); + create_account("dave"); + create_account("frank"); + generate_block(); + const account_object& alice = get_account("alice"); + const account_object& bob = get_account("bob"); + const account_object& carol = get_account("carol"); + const account_object& dave = get_account("dave"); + const account_object& frank = get_account("frank"); + + BOOST_TEST_MESSAGE("Creating test asset"); + { + asset_create_operation creator; + creator.issuer = account_id_type(); + creator.fee = asset(); + creator.symbol = "TEST"; + creator.common_options.max_supply = 100000000; + creator.precision = 2; + creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ + creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + creator.common_options.flags = charge_market_fee; + creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))}); + trx.operations.push_back(std::move(creator)); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + // it should not yet be a divdend asset + const auto& test_asset_object = get_asset("TEST"); + + auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) + { + asset_issue_operation op; + op.issuer = asset_to_issue.issuer; + op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id); + op.issue_to_account = destination_account.id; + trx.operations.push_back( op ); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + }; + + // Set up the first test, issue alice, bob, and carol each 100 DIVIDEND. + // Then deposit 300 TEST in the distribution account, and see that they + // each are credited 100 TEST. + issue_asset_to_account(dividend_holder_asset_object, alice, 100000); + issue_asset_to_account(dividend_holder_asset_object, bob, 100000); + issue_asset_to_account(dividend_holder_asset_object, carol, 100000); + + BOOST_TEST_MESSAGE("Issuing 300 TEST to the dividend account"); + issue_asset_to_account(test_asset_object, dividend_distribution_account, 30000); + + generate_block(); // get the maintenance skip slots out of the way + + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + + // for (const auto& pending_payout : db.get_index_type().indices()) + // dlog("In test, pending payout: ${account_name} -> ${amount}", ("account_name", pending_payout.owner(db).name)("amount", pending_payout.pending_balance)); + + dlog("Test asset object symbol is ${symbol}", ("symbol", test_asset_object.get_id()(db).symbol)); + auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { + int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, + holder_account_obj.id, + payout_asset_obj.id); + BOOST_CHECK_EQUAL(pending_balance, expected_balance); + }; + verify_pending_balance(alice, test_asset_object, 10000); + verify_pending_balance(bob, test_asset_object, 10000); + verify_pending_balance(carol, test_asset_object, 10000); + + // For the second test, issue carol more than the other two, so it's + // alice: 100 DIVIDND, bob: 100 DIVIDEND, carol: 200 DIVIDEND + // Then deposit 400 TEST in the distribution account, and see that alice + // and bob are credited with 100 TEST, and carol gets 200 TEST + BOOST_TEST_MESSAGE("Issuing carol twice as much of the holder asset"); + issue_asset_to_account(dividend_holder_asset_object, carol, 100000); // one thousand at two digits of precision + issue_asset_to_account(test_asset_object, dividend_distribution_account, 40000); // one thousand at two digits of precision + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + verify_pending_balance(alice, test_asset_object, 20000); + verify_pending_balance(bob, test_asset_object, 20000); + verify_pending_balance(carol, test_asset_object, 30000); + + auto advance_to_next_payout_time = [&]() { + // Advance to the next upcoming payout time + BOOST_REQUIRE(dividend_data.options.next_payout_time); + fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; + // generate blocks up to the next scheduled time + generate_blocks(next_payout_scheduled_time); + // if the scheduled time fell on a maintenance interval, then we should have paid out. + // if not, we need to advance to the next maintenance interval to trigger the payout + BOOST_REQUIRE(dividend_data.options.next_payout_time); + if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + }; + + fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time; + advance_to_next_payout_time(); + + + BOOST_REQUIRE_MESSAGE(dividend_data.options.next_payout_time, "No new payout was scheduled"); + BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time != *dividend_data.options.next_payout_time, + "New payout was scheduled for the same time as the last payout"); + BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time + *dividend_data.options.payout_interval == *dividend_data.options.next_payout_time, + "New payout was not scheduled for the expected time"); + + BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000); + verify_pending_balance(alice, test_asset_object, 0); + BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000); + verify_pending_balance(bob, test_asset_object, 0); + BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 30000); + verify_pending_balance(carol, test_asset_object, 0); + + + BOOST_TEST_MESSAGE("Removing the payout interval"); + { + asset_update_dividend_operation op; + op.issuer = dividend_holder_asset_object.issuer; + op.asset_to_update = dividend_holder_asset_object.id; + op.new_options.next_payout_time = dividend_data.options.next_payout_time; + op.new_options.payout_interval = fc::optional(); + trx.operations.push_back(op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + BOOST_CHECK(!dividend_data.options.payout_interval); + advance_to_next_payout_time(); + BOOST_REQUIRE_MESSAGE(!dividend_data.options.next_payout_time, "A new payout was scheduled, but none should have been"); + + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_CASE( cancel_limit_order_test ) { try { INVOKE( issue_uia ); From 7857ac48a4731aeaf079e1afd7bd814b74e2d022 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Mon, 27 Jun 2016 16:24:13 -0400 Subject: [PATCH 28/34] Correctly generating virtual transactions for payouts --- libraries/app/impacted.cpp | 5 +++ libraries/chain/db_maint.cpp | 37 +++++++++++++++-- .../include/graphene/chain/account_object.hpp | 13 +++++- .../graphene/chain/protocol/asset_ops.hpp | 40 ++++++++++++++++++- .../graphene/chain/protocol/operations.hpp | 3 +- libraries/wallet/wallet.cpp | 20 +++++++++- tests/common/database_fixture.cpp | 8 +--- tests/tests/operation_tests.cpp | 36 ++++++++++++++--- 8 files changed, 141 insertions(+), 21 deletions(-) diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index ad9d9e0c..68be8f92 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -91,6 +91,11 @@ struct get_impacted_account_visitor void operator()( const asset_update_bitasset_operation& op ) {} void operator()( const asset_update_dividend_operation& op ) {} + void operator()( const asset_dividend_distribution_operation& op ) + { + _impacted.insert( op.account_id ); + } + void operator()( const asset_update_feed_producers_operation& op ) {} void operator()( const asset_issue_operation& op ) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 6bef6051..0f9137b9 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -799,8 +799,8 @@ void schedule_pending_dividend_balances(database& db, dlog("Crediting account ${account} with ${amount}", ("account", holder_balance_object.owner(db).name)("amount", amount_to_credit)); auto pending_payout_iter = - pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner)); - if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) + pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner)); + if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) db.create( [&]( pending_dividend_payout_balance_object& obj ){ obj.owner = holder_balance_object.owner; obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; @@ -839,7 +839,7 @@ void schedule_pending_dividend_balances(database& db, // Reduce all pending payouts proportionally share_type total_pending_balances; auto pending_payouts_range = - pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); + pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) total_pending_balances += pending_balance_object.pending_balance; @@ -927,11 +927,28 @@ void process_dividend_assets(database& db) // for debugging, sum up our payouts here std::map amounts_paid_out_by_asset; #endif + auto pending_payouts_range = - pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + // the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account + // we iterate in this order so we can build up a list of payouts for each account to put in the + // virtual op + flat_set payouts_for_this_holder; + fc::optional last_holder_account_id; for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; ) { const pending_dividend_payout_balance_object& pending_balance_object = *pending_balance_object_iter; + + if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner) + { + // we've moved on to a new account, generate the dividend payment virtual op for the previous one + db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, + *last_holder_account_id, + payouts_for_this_holder)); + ilog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); + payouts_for_this_holder.clear(); + } + ilog("Processing payout of ${asset} to account ${account}", ("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)) ("account", pending_balance_object.owner(db).name)); @@ -939,6 +956,9 @@ void process_dividend_assets(database& db) db.adjust_balance(pending_balance_object.owner, asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)); + payouts_for_this_holder.insert(asset(pending_balance_object.pending_balance, + pending_balance_object.dividend_payout_asset_type)); + last_holder_account_id = pending_balance_object.owner; #ifndef NDEBUG amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance; #endif @@ -946,6 +966,15 @@ void process_dividend_assets(database& db) ++pending_balance_object_iter; db.remove(pending_balance_object); } + // we will always be left with the last holder's data, generate the virtual op for it now. + if (last_holder_account_id) + { + // we've moved on to a new account, generate the dividend payment virtual op for the previous one + db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, + *last_holder_account_id, + payouts_for_this_holder)); + ilog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); + } // now debit the total amount of dividends paid out from the distribution account auto distributed_balance_range = diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 90becf9e..c6c5ded7 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -389,7 +389,8 @@ namespace graphene { namespace chain { */ typedef generic_index account_index; - struct by_dividend_asset_account_asset{}; + struct by_dividend_payout_account{}; + struct by_dividend_account_payout{}; /** * @ingroup object_index @@ -398,13 +399,21 @@ namespace graphene { namespace chain { pending_dividend_payout_balance_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, - ordered_unique< tag, + ordered_unique< tag, composite_key< pending_dividend_payout_balance_object, member, member, member > + >, + ordered_unique< tag, + composite_key< + pending_dividend_payout_balance_object, + member, + member, + member + > > > > pending_dividend_payout_balance_object_multi_index_type; diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index eab3ae38..db111cbe 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -260,6 +260,43 @@ namespace graphene { namespace chain { { return 0; } }; + /** + * Virtual op generated when a dividend asset pays out dividends + */ + struct asset_dividend_distribution_operation : public base_operation + { + asset_dividend_distribution_operation() {} + asset_dividend_distribution_operation(const asset_id_type& dividend_asset_id, + const account_id_type& account_id, + const flat_set& amounts) : + dividend_asset_id(dividend_asset_id), + account_id(account_id), + amounts(amounts) + {} + struct fee_parameters_type { }; + + asset fee; + + /// The dividend-paying asset which triggered this payout + asset_id_type dividend_asset_id; + + /// The user account receiving the dividends + account_id_type account_id; + + /// The amounts received + flat_set amounts; + + extensions_type extensions; + + account_id_type fee_payer()const { return account_id; } + void validate()const { + FC_ASSERT( false, "virtual operation" ); + } + + share_type calculate_fee(const fee_parameters_type& params)const + { return 0; } + }; + /** * @ingroup operations */ @@ -545,7 +582,7 @@ FC_REFLECT( graphene::chain::asset_update_feed_producers_operation::fee_paramete FC_REFLECT( graphene::chain::asset_publish_feed_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_reserve_operation::fee_parameters_type, (fee) ) - +FC_REFLECT( graphene::chain::asset_dividend_distribution_operation::fee_parameters_type, ) FC_REFLECT( graphene::chain::asset_create_operation, (fee) @@ -593,3 +630,4 @@ FC_REFLECT( graphene::chain::asset_reserve_operation, (fee)(payer)(amount_to_reserve)(extensions) ) FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation, (fee)(from_account)(asset_id)(amount)(extensions) ); +FC_REFLECT( graphene::chain::asset_dividend_distribution_operation, (fee)(dividend_asset_id)(account_id)(amounts)(extensions) ); diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 0c02ed75..244f44e0 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -92,7 +92,8 @@ namespace graphene { namespace chain { asset_settle_cancel_operation, // VIRTUAL asset_claim_fees_operation, fba_distribute_operation, // VIRTUAL - asset_update_dividend_operation + asset_update_dividend_operation, + asset_dividend_distribution_operation // VIRTUAL > operation; /// @} // operations group diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index d52d5f58..91632b58 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -123,6 +124,7 @@ public: std::string operator()(const account_create_operation& op)const; std::string operator()(const account_update_operation& op)const; std::string operator()(const asset_create_operation& op)const; + std::string operator()(const asset_dividend_distribution_operation& op)const; }; template @@ -2639,9 +2641,7 @@ std::string operation_printer::operator()(const T& op)const operation_result_printer rprinter(wallet); std::string str_result = result.visit(rprinter); if( str_result != "" ) - { out << " result: " << str_result; - } return ""; } std::string operation_printer::operator()(const transfer_from_blind_operation& op)const @@ -2721,6 +2721,22 @@ std::string operation_printer::operator()(const asset_create_operation& op) cons return fee(op.fee); } +std::string operation_printer::operator()(const asset_dividend_distribution_operation& op)const +{ + asset_object dividend_paying_asset = wallet.get_asset(op.dividend_asset_id); + account_object receiver = wallet.get_account(op.account_id); + + out << receiver.name << " received dividend payments for " << dividend_paying_asset.symbol << ": "; + std::vector pretty_payout_amounts; + for (const asset& payment : op.amounts) + { + asset_object payout_asset = wallet.get_asset(payment.asset); + pretty_payout_amounts.push_back(payout_asset.amount_to_pretty_string(payout_asset.amount)); + } + out << boost::algorithm::join(pretty_payout_amounts, ", "); + return ""; +} + std::string operation_result_printer::operator()(const void_result& x) const { return ""; diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 23e38dad..a69bfc30 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1042,13 +1042,9 @@ int64_t database_fixture::get_dividend_pending_payout_balance(asset_id_type divi { const pending_dividend_payout_balance_object_index& pending_payout_balance_index = db.get_index_type(); - dlog("searching ${a}", ("a", dividend_holder_asset_type(db).symbol)); - dlog("searching ${b}", ("b", dividend_payout_asset_type(db).symbol)); - dlog("searching ${c}", ("c", dividend_holder_account_id(db).name)); - dlog("searching ${a} ${b} ${c}", ("a", dividend_holder_asset_type(db).symbol)("b", dividend_payout_asset_type(db).symbol)("c", dividend_holder_account_id(db).name)); auto pending_payout_iter = - pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id)); - if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) + pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id)); + if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) return 0; else return pending_payout_iter->pending_balance.value; diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index fc9bdefa..cfd47f13 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include @@ -1143,7 +1144,7 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) asset_update_dividend_operation op; op.issuer = dividend_holder_asset_object.issuer; op.asset_to_update = dividend_holder_asset_object.id; - op.new_options.next_payout_time = fc::time_point::now() + fc::minutes(1); + op.new_options.next_payout_time = db.head_block_time() + fc::minutes(1); op.new_options.payout_interval = 60 * 60 * 24 * 7; // one week trx.operations.push_back(op); @@ -1286,10 +1287,16 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) generate_blocks(next_payout_scheduled_time); // if the scheduled time fell on a maintenance interval, then we should have paid out. // if not, we need to advance to the next maintenance interval to trigger the payout - BOOST_REQUIRE(dividend_data.options.next_payout_time); - if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way + if (dividend_data.options.next_payout_time) + { + // we know there was a next_payout_time set when we entered this, so if + // it has been cleared, we must have already processed payouts, no need to + // further advance time. + BOOST_REQUIRE(dividend_data.options.next_payout_time); + if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + } }; fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time; @@ -1302,14 +1309,33 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time + *dividend_data.options.payout_interval == *dividend_data.options.next_payout_time, "New payout was not scheduled for the expected time"); + auto verify_dividend_payout_operations = [&](const account_object& destination_account, const asset& expected_payout) + { + BOOST_TEST_MESSAGE("Verifying the virtual op was created"); + const account_transaction_history_index& hist_idx = db.get_index_type(); + auto account_history_range = hist_idx.indices().get().equal_range(boost::make_tuple(destination_account.id)); + BOOST_REQUIRE(account_history_range.first != account_history_range.second); + const operation_history_object& history_object = std::prev(account_history_range.second)->operation_id(db); + const asset_dividend_distribution_operation& distribution_operation = history_object.op.get(); + BOOST_CHECK(distribution_operation.account_id == destination_account.id); + BOOST_CHECK(distribution_operation.amounts.find(expected_payout) != distribution_operation.amounts.end()); + }; + + BOOST_TEST_MESSAGE("Verifying the payouts"); BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000); + verify_dividend_payout_operations(alice, asset(20000, test_asset_object.id)); verify_pending_balance(alice, test_asset_object, 0); + BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000); + verify_dividend_payout_operations(bob, asset(20000, test_asset_object.id)); verify_pending_balance(bob, test_asset_object, 0); + BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 30000); + verify_dividend_payout_operations(carol, asset(30000, test_asset_object.id)); verify_pending_balance(carol, test_asset_object, 0); + BOOST_TEST_MESSAGE("Removing the payout interval"); { asset_update_dividend_operation op; From 06b213408550e476bfa5b9456f9c9fe45b91ba8c Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Mon, 27 Jun 2016 17:31:14 -0400 Subject: [PATCH 29/34] Keep pending dividend balance and distributed dividend balance objects around (with zero balance) after payouts, they will probably be needed again. --- libraries/chain/db_maint.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 0f9137b9..d2e44327 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -964,7 +964,9 @@ void process_dividend_assets(database& db) #endif ++pending_balance_object_iter; - db.remove(pending_balance_object); + db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ + pending_balance.pending_balance = 0; + }); } // we will always be left with the last holder's data, generate the virtual op for it now. if (last_holder_account_id) @@ -1002,7 +1004,9 @@ void process_dividend_assets(database& db) asset(-distributed_balance_object.balance_at_last_maintenance_interval, distributed_balance_object.dividend_payout_asset_type)); ++distributed_balance_object_iter; - db.remove(distributed_balance_object); // now they've been paid out, reset to zero + db.modify(distributed_balance_object, [&]( distributed_dividend_balance_object& obj ){ + obj.balance_at_last_maintenance_interval = 0; // now they've been paid out, reset to zero + }); } // now schedule the next payout time From b584ee15dd747960d8949434d853009bc3bf33e5 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Thu, 30 Jun 2016 12:05:16 -0400 Subject: [PATCH 30/34] Separate out unit tests for dividend-assets into their own test suite --- libraries/app/database_api.cpp | 7 + .../app/include/graphene/app/full_account.hpp | 2 + libraries/chain/db_maint.cpp | 104 +++--- .../include/graphene/chain/account_object.hpp | 13 +- tests/tests/operation_tests.cpp | 327 ++++++++++++------ 5 files changed, 309 insertions(+), 144 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 734d68b2..42a0b641 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -604,6 +605,12 @@ std::map database_api_impl::get_full_accounts( const [&acnt] (const call_order_object& call) { acnt.call_orders.emplace_back(call); }); + + auto pending_payouts_range = + _db.get_index_type().indices().get().equal_range(boost::make_tuple(account->id)); + + std::copy(pending_payouts_range.first, pending_payouts_range.second, std::back_inserter(acnt.pending_dividend_payments)); + results[account_name_or_id] = acnt; } return results; diff --git a/libraries/app/include/graphene/app/full_account.hpp b/libraries/app/include/graphene/app/full_account.hpp index 74724630..787207ef 100644 --- a/libraries/app/include/graphene/app/full_account.hpp +++ b/libraries/app/include/graphene/app/full_account.hpp @@ -44,6 +44,7 @@ namespace graphene { namespace app { vector limit_orders; vector call_orders; vector proposals; + vector pending_dividend_payments; }; } } @@ -61,4 +62,5 @@ FC_REFLECT( graphene::app::full_account, (limit_orders) (call_orders) (proposals) + (pending_dividend_payments) ) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index d2e44327..93507b7c 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -716,6 +717,10 @@ void deprecate_annual_members( database& db ) return; } +// Schedules payouts from a dividend distribution account to the current holders of the +// dividend-paying asset. This takes any deposits made to the dividend distribution account +// since the last time it was called, and distributes them to the current owners of the +// dividend-paying asset according to the amount they own. void schedule_pending_dividend_balances(database& db, const asset_object& dividend_holder_asset_obj, const asset_dividend_data_object& dividend_data, @@ -723,7 +728,8 @@ void schedule_pending_dividend_balances(database& db, const distributed_dividend_balance_object_index& distributed_dividend_balance_index, const pending_dividend_payout_balance_object_index& pending_payout_balance_index) { - dlog("Processing dividend payments for dividend holder asset asset type ${holder_asset}", ("holder_asset", dividend_holder_asset_obj.symbol)); + dlog("Processing dividend payments for dividend holder asset asset type ${holder_asset} at time ${t}", + ("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time())); auto current_distribution_account_balance_range = balance_index.indices().get().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account)); auto previous_distribution_account_balance_range = @@ -786,7 +792,8 @@ void schedule_pending_dividend_balances(database& db, share_type remaining_balance_of_dividend_asset = total_balance_of_dividend_asset; for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second)) - if (holder_balance_object.owner != dividend_data.dividend_distribution_account) + if (holder_balance_object.owner != dividend_data.dividend_distribution_account && + holder_balance_object.balance.value) { // TODO: if holder_balance_object.owner is able to accept payout_asset_type fc::uint128_t amount_to_credit(remaining_amount_to_distribute.value); @@ -902,6 +909,8 @@ void process_dividend_assets(database& db) if (dividend_holder_asset_obj.dividend_data_id) { const asset_dividend_data_object& dividend_data = dividend_holder_asset_obj.dividend_data(db); + const account_object& dividend_distribution_account_object = dividend_data.dividend_distribution_account(db); + schedule_pending_dividend_balances(db, dividend_holder_asset_obj, dividend_data, balance_index, distributed_dividend_balance_index, pending_payout_balance_index); fc::time_point_sec current_head_block_time = db.head_block_time(); @@ -923,10 +932,7 @@ void process_dividend_assets(database& db) // and use this map to keep track of the total amount of each asset paid out. // Afterwards, we decrease the distribution account's balance by the total amount paid out, // and modify the distributed_balances accordingly -#ifndef NDEBUG - // for debugging, sum up our payouts here std::map amounts_paid_out_by_asset; -#endif auto pending_payouts_range = pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); @@ -935,6 +941,20 @@ void process_dividend_assets(database& db) // virtual op flat_set payouts_for_this_holder; fc::optional last_holder_account_id; + + // cache the assets the distribution account is approved to send, we will be asking + // for these often + flat_map approved_assets; // assets that the dividend distribution account is authorized to send/receive + auto is_asset_approved_for_distribution_account = [&](const asset_id_type& asset_id) { + auto approved_assets_iter = approved_assets.find(asset_id); + if (approved_assets_iter != approved_assets.end()) + return approved_assets_iter->second; + bool is_approved = is_authorized_asset(db, dividend_distribution_account_object, + asset_id(db)); + approved_assets[asset_id] = is_approved; + return is_approved; + }; + for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; ) { const pending_dividend_payout_balance_object& pending_balance_object = *pending_balance_object_iter; @@ -947,26 +967,31 @@ void process_dividend_assets(database& db) payouts_for_this_holder)); ilog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); payouts_for_this_holder.clear(); + last_holder_account_id.reset(); } - ilog("Processing payout of ${asset} to account ${account}", - ("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)) - ("account", pending_balance_object.owner(db).name)); - db.adjust_balance(pending_balance_object.owner, - asset(pending_balance_object.pending_balance, - pending_balance_object.dividend_payout_asset_type)); - payouts_for_this_holder.insert(asset(pending_balance_object.pending_balance, - pending_balance_object.dividend_payout_asset_type)); - last_holder_account_id = pending_balance_object.owner; -#ifndef NDEBUG - amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance; -#endif + if (is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) && + is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type)) + { + ilog("Processing payout of ${asset} to account ${account}", + ("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)) + ("account", pending_balance_object.owner(db).name)); + + db.adjust_balance(pending_balance_object.owner, + asset(pending_balance_object.pending_balance, + pending_balance_object.dividend_payout_asset_type)); + payouts_for_this_holder.insert(asset(pending_balance_object.pending_balance, + pending_balance_object.dividend_payout_asset_type)); + last_holder_account_id = pending_balance_object.owner; + amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance; + + db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ + pending_balance.pending_balance = 0; + }); + } ++pending_balance_object_iter; - db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ - pending_balance.pending_balance = 0; - }); } // we will always be left with the last holder's data, generate the virtual op for it now. if (last_holder_account_id) @@ -979,34 +1004,25 @@ void process_dividend_assets(database& db) } // now debit the total amount of dividends paid out from the distribution account - auto distributed_balance_range = - distributed_dividend_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + // and reduce the distributed_balances accordingly -#ifndef NDEBUG - // validate that we actually paid out exactly as much as we had planned to - assert(amounts_paid_out_by_asset.size() == std::distance(distributed_balance_range.first, distributed_balance_range.second)); - if (amounts_paid_out_by_asset.size() == std::distance(distributed_balance_range.first, distributed_balance_range.second)) + for (const auto& value : amounts_paid_out_by_asset) { - auto distributed_balance_object_iter = distributed_balance_range.first; - for (const auto& asset_and_amount_paid_out : amounts_paid_out_by_asset) - { - assert(distributed_balance_object_iter->dividend_payout_asset_type == asset_and_amount_paid_out.first); - assert(distributed_balance_object_iter->balance_at_last_maintenance_interval == asset_and_amount_paid_out.second); - ++distributed_balance_object_iter; - } - } -#endif + const asset_id_type& asset_paid_out = value.first; + const share_type& amount_paid_out = value.second; - for (auto distributed_balance_object_iter = distributed_balance_range.first; distributed_balance_object_iter != distributed_balance_range.second; ) - { - const distributed_dividend_balance_object& distributed_balance_object = *distributed_balance_object_iter; db.adjust_balance(dividend_data.dividend_distribution_account, - asset(-distributed_balance_object.balance_at_last_maintenance_interval, - distributed_balance_object.dividend_payout_asset_type)); - ++distributed_balance_object_iter; - db.modify(distributed_balance_object, [&]( distributed_dividend_balance_object& obj ){ - obj.balance_at_last_maintenance_interval = 0; // now they've been paid out, reset to zero - }); + asset(-amount_paid_out, + asset_paid_out)); + auto distributed_balance_iter = + distributed_dividend_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, + asset_paid_out)); + assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()); + if (distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()) + db.modify(*distributed_balance_iter, [&]( distributed_dividend_balance_object& obj ){ + obj.balance_at_last_maintenance_interval -= amount_paid_out; // now they've been paid out, reset to zero + }); + } // now schedule the next payout time diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index c6c5ded7..1b65957c 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -389,8 +389,9 @@ namespace graphene { namespace chain { */ typedef generic_index account_index; - struct by_dividend_payout_account{}; - struct by_dividend_account_payout{}; + struct by_dividend_payout_account{}; // use when calculating pending payouts + struct by_dividend_account_payout{}; // use when doing actual payouts + struct by_account_dividend_payout{}; // use in get_full_accounts() /** * @ingroup object_index @@ -414,6 +415,14 @@ namespace graphene { namespace chain { member, member > + >, + ordered_unique< tag, + composite_key< + pending_dividend_payout_balance_object, + member, + member, + member + > > > > pending_dividend_payout_balance_object_multi_index_type; diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index cfd47f13..85ec439f 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1112,6 +1112,8 @@ BOOST_AUTO_TEST_CASE( uia_fees ) } } +BOOST_FIXTURE_TEST_SUITE( dividend_tests, database_fixture ) + BOOST_AUTO_TEST_CASE( create_dividend_uia ) { using namespace graphene; @@ -1133,60 +1135,6 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) PUSH_TX( db, trx, ~0 ); trx.operations.clear(); } - generate_block(); - - // it should not yet be a divdend asset - const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); - BOOST_CHECK(!dividend_holder_asset_object.dividend_data_id); - - BOOST_TEST_MESSAGE("Converting the new asset to a dividend holder asset"); - { - asset_update_dividend_operation op; - op.issuer = dividend_holder_asset_object.issuer; - op.asset_to_update = dividend_holder_asset_object.id; - op.new_options.next_payout_time = db.head_block_time() + fc::minutes(1); - op.new_options.payout_interval = 60 * 60 * 24 * 7; // one week - - trx.operations.push_back(op); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - generate_block(); - - //const auto& test_readback = get_asset("TEST"); - //BOOST_REQUIRE(test_readback.dividend_data_id); - BOOST_TEST_MESSAGE("Verifying the dividend holder asset options"); - BOOST_REQUIRE(dividend_holder_asset_object.dividend_data_id); - const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); - { - BOOST_REQUIRE(dividend_data.options.payout_interval); - BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24 * 7); - } - - BOOST_TEST_MESSAGE("Updating the payout interval"); - { - asset_update_dividend_operation op; - op.issuer = dividend_holder_asset_object.issuer; - op.asset_to_update = dividend_holder_asset_object.id; - op.new_options.next_payout_time = fc::time_point::now() + fc::minutes(1); - op.new_options.payout_interval = 60 * 60 * 24; // one day - trx.operations.push_back(op); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - generate_block(); - - BOOST_TEST_MESSAGE("Verifying the updated dividend holder asset options"); - { - BOOST_REQUIRE(dividend_data.options.payout_interval); - BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24); - } - - const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); - BOOST_CHECK_EQUAL(dividend_distribution_account.name, "dividend-dividend-distribution"); - BOOST_TEST_MESSAGE("Creating test accounts"); create_account("alice"); @@ -1194,12 +1142,6 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) create_account("carol"); create_account("dave"); create_account("frank"); - generate_block(); - const account_object& alice = get_account("alice"); - const account_object& bob = get_account("bob"); - const account_object& carol = get_account("carol"); - const account_object& dave = get_account("dave"); - const account_object& frank = get_account("frank"); BOOST_TEST_MESSAGE("Creating test asset"); { @@ -1220,7 +1162,90 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) } generate_block(); - // it should not yet be a divdend asset + // our DIVIDEND asset should not yet be a divdend asset + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + BOOST_CHECK(!dividend_holder_asset_object.dividend_data_id); + + BOOST_TEST_MESSAGE("Converting the new asset to a dividend holder asset"); + { + asset_update_dividend_operation op; + op.issuer = dividend_holder_asset_object.issuer; + op.asset_to_update = dividend_holder_asset_object.id; + op.new_options.next_payout_time = db.head_block_time() + fc::minutes(1); + op.new_options.payout_interval = 60 * 60 * 24 * 3; + + trx.operations.push_back(op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Verifying the dividend holder asset options"); + BOOST_REQUIRE(dividend_holder_asset_object.dividend_data_id); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + { + BOOST_REQUIRE(dividend_data.options.payout_interval); + BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24 * 3); + } + + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + BOOST_CHECK_EQUAL(dividend_distribution_account.name, "dividend-dividend-distribution"); + + + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( test_update_dividend_interval ) +{ + using namespace graphene; + try { + INVOKE( create_dividend_uia ); + + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + + BOOST_TEST_MESSAGE("Updating the payout interval"); + { + asset_update_dividend_operation op; + op.issuer = dividend_holder_asset_object.issuer; + op.asset_to_update = dividend_holder_asset_object.id; + op.new_options.next_payout_time = fc::time_point::now() + fc::minutes(1); + op.new_options.payout_interval = 60 * 60 * 24; // 1 days + trx.operations.push_back(op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Verifying the updated dividend holder asset options"); + { + BOOST_REQUIRE(dividend_data.options.payout_interval); + BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24); + } + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution ) +{ + using namespace graphene; + try { + INVOKE( create_dividend_uia ); + + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + const account_object& alice = get_account("alice"); + const account_object& bob = get_account("bob"); + const account_object& carol = get_account("carol"); + const account_object& dave = get_account("dave"); + const account_object& frank = get_account("frank"); const auto& test_asset_object = get_asset("TEST"); auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) @@ -1235,49 +1260,12 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) trx.operations.clear(); }; - // Set up the first test, issue alice, bob, and carol each 100 DIVIDEND. - // Then deposit 300 TEST in the distribution account, and see that they - // each are credited 100 TEST. - issue_asset_to_account(dividend_holder_asset_object, alice, 100000); - issue_asset_to_account(dividend_holder_asset_object, bob, 100000); - issue_asset_to_account(dividend_holder_asset_object, carol, 100000); - - BOOST_TEST_MESSAGE("Issuing 300 TEST to the dividend account"); - issue_asset_to_account(test_asset_object, dividend_distribution_account, 30000); - - generate_block(); // get the maintenance skip slots out of the way - - BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - - // for (const auto& pending_payout : db.get_index_type().indices()) - // dlog("In test, pending payout: ${account_name} -> ${amount}", ("account_name", pending_payout.owner(db).name)("amount", pending_payout.pending_balance)); - - dlog("Test asset object symbol is ${symbol}", ("symbol", test_asset_object.get_id()(db).symbol)); auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, holder_account_obj.id, payout_asset_obj.id); BOOST_CHECK_EQUAL(pending_balance, expected_balance); }; - verify_pending_balance(alice, test_asset_object, 10000); - verify_pending_balance(bob, test_asset_object, 10000); - verify_pending_balance(carol, test_asset_object, 10000); - - // For the second test, issue carol more than the other two, so it's - // alice: 100 DIVIDND, bob: 100 DIVIDEND, carol: 200 DIVIDEND - // Then deposit 400 TEST in the distribution account, and see that alice - // and bob are credited with 100 TEST, and carol gets 200 TEST - BOOST_TEST_MESSAGE("Issuing carol twice as much of the holder asset"); - issue_asset_to_account(dividend_holder_asset_object, carol, 100000); // one thousand at two digits of precision - issue_asset_to_account(test_asset_object, dividend_distribution_account, 40000); // one thousand at two digits of precision - BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - verify_pending_balance(alice, test_asset_object, 20000); - verify_pending_balance(bob, test_asset_object, 20000); - verify_pending_balance(carol, test_asset_object, 30000); auto advance_to_next_payout_time = [&]() { // Advance to the next upcoming payout time @@ -1299,6 +1287,45 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) } }; + // the first test will be testing pending balances, so we need to hit a + // maintenance interval that isn't the payout interval. Payout is + // every 3 days, maintenance interval is every 1 day. + advance_to_next_payout_time(); + + // Set up the first test, issue alice, bob, and carol each 100 DIVIDEND. + // Then deposit 300 TEST in the distribution account, and see that they + // each are credited 100 TEST. + issue_asset_to_account(dividend_holder_asset_object, alice, 100000); + issue_asset_to_account(dividend_holder_asset_object, bob, 100000); + issue_asset_to_account(dividend_holder_asset_object, carol, 100000); + + BOOST_TEST_MESSAGE("Issuing 300 TEST to the dividend account"); + issue_asset_to_account(test_asset_object, dividend_distribution_account, 30000); + + generate_block(); + + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + + verify_pending_balance(alice, test_asset_object, 10000); + verify_pending_balance(bob, test_asset_object, 10000); + verify_pending_balance(carol, test_asset_object, 10000); + + // For the second test, issue carol more than the other two, so it's + // alice: 100 DIVIDND, bob: 100 DIVIDEND, carol: 200 DIVIDEND + // Then deposit 400 TEST in the distribution account, and see that alice + // and bob are credited with 100 TEST, and carol gets 200 TEST + BOOST_TEST_MESSAGE("Issuing carol twice as much of the holder asset"); + issue_asset_to_account(dividend_holder_asset_object, carol, 100000); // one thousand at two digits of precision + issue_asset_to_account(test_asset_object, dividend_distribution_account, 40000); // one thousand at two digits of precision + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + verify_pending_balance(alice, test_asset_object, 20000); + verify_pending_balance(bob, test_asset_object, 20000); + verify_pending_balance(carol, test_asset_object, 30000); + fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time; advance_to_next_payout_time(); @@ -1333,8 +1360,111 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 30000); verify_dividend_payout_operations(carol, asset(30000, test_asset_object.id)); verify_pending_balance(carol, test_asset_object, 0); + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE( check_dividend_corner_cases ) +{ + using namespace graphene; + try { + INVOKE( create_dividend_uia ); + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + const account_object& alice = get_account("alice"); + const account_object& bob = get_account("bob"); + const account_object& carol = get_account("carol"); + const account_object& dave = get_account("dave"); + const account_object& frank = get_account("frank"); + const auto& test_asset_object = get_asset("TEST"); + auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) + { + asset_issue_operation op; + op.issuer = asset_to_issue.issuer; + op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id); + op.issue_to_account = destination_account.id; + trx.operations.push_back( op ); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + }; + + auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { + int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, + holder_account_obj.id, + payout_asset_obj.id); + BOOST_CHECK_EQUAL(pending_balance, expected_balance); + }; + + auto reserve_asset_from_account = [&](const asset_object& asset_to_reserve, const account_object& from_account, int64_t amount_to_reserve) + { + asset_reserve_operation reserve_op; + reserve_op.payer = from_account.id; + reserve_op.amount_to_reserve = asset(amount_to_reserve, asset_to_reserve.id); + trx.operations.push_back(reserve_op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + }; + auto advance_to_next_payout_time = [&]() { + // Advance to the next upcoming payout time + BOOST_REQUIRE(dividend_data.options.next_payout_time); + fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; + // generate blocks up to the next scheduled time + generate_blocks(next_payout_scheduled_time); + // if the scheduled time fell on a maintenance interval, then we should have paid out. + // if not, we need to advance to the next maintenance interval to trigger the payout + if (dividend_data.options.next_payout_time) + { + // we know there was a next_payout_time set when we entered this, so if + // it has been cleared, we must have already processed payouts, no need to + // further advance time. + BOOST_REQUIRE(dividend_data.options.next_payout_time); + if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + } + }; + + // the first test will be testing pending balances, so we need to hit a + // maintenance interval that isn't the payout interval. Payout is + // every 3 days, maintenance interval is every 1 day. + advance_to_next_payout_time(); + + BOOST_TEST_MESSAGE("Testing a payout interval when there are no users holding the dividend asset"); + BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0); + issue_asset_to_account(test_asset_object, dividend_distribution_account, 1000); + BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + BOOST_TEST_MESSAGE("Verify that no pending payments were scheduled"); + verify_pending_balance(alice, test_asset_object, 0); + verify_pending_balance(bob, test_asset_object, 0); + verify_pending_balance(carol, test_asset_object, 0); + advance_to_next_payout_time(); + BOOST_TEST_MESSAGE("Verify that no actual payments took place"); + verify_pending_balance(alice, test_asset_object, 0); + verify_pending_balance(bob, test_asset_object, 0); + verify_pending_balance(carol, test_asset_object, 0); + BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, test_asset_object), 1000); + + BOOST_TEST_MESSAGE("Now give alice a small balance and see that she takes it all"); + issue_asset_to_account(dividend_holder_asset_object, alice, 1); + generate_block(); + BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + BOOST_TEST_MESSAGE("Verify that no pending payments were scheduled"); + verify_pending_balance(alice, test_asset_object, 1000); BOOST_TEST_MESSAGE("Removing the payout interval"); { @@ -1358,6 +1488,7 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) throw; } } +BOOST_AUTO_TEST_SUITE_END() // end dividend_tests suite BOOST_AUTO_TEST_CASE( cancel_limit_order_test ) { try { From 6c35e8d2a32161a96d1518a3e986cd76c2fb3b4e Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Thu, 30 Jun 2016 16:00:29 -0400 Subject: [PATCH 31/34] Compile fix --- libraries/wallet/wallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 91632b58..d7e9c1d1 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2730,8 +2730,8 @@ std::string operation_printer::operator()(const asset_dividend_distribution_oper std::vector pretty_payout_amounts; for (const asset& payment : op.amounts) { - asset_object payout_asset = wallet.get_asset(payment.asset); - pretty_payout_amounts.push_back(payout_asset.amount_to_pretty_string(payout_asset.amount)); + asset_object payout_asset = wallet.get_asset(payment.asset_id); + pretty_payout_amounts.push_back(payout_asset.amount_to_pretty_string(payment)); } out << boost::algorithm::join(pretty_payout_amounts, ", "); return ""; From bc212b7d59bd46dee38b780b474f7ba41b4c8001 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Thu, 25 Aug 2016 10:41:01 -0400 Subject: [PATCH 32/34] Fixes to paying out non-core assets using their fee pools --- libraries/chain/asset_evaluator.cpp | 5 + libraries/chain/db_maint.cpp | 365 ++++++++++++------ .../include/graphene/chain/asset_object.hpp | 12 + .../graphene/chain/protocol/asset_ops.hpp | 43 ++- .../wallet/include/graphene/wallet/wallet.hpp | 16 + libraries/wallet/wallet.cpp | 8 + tests/tests/operation_tests.cpp | 109 +++++- 7 files changed, 415 insertions(+), 143 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index f7405366..7af71e44 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -411,6 +411,11 @@ void_result asset_update_dividend_evaluator::do_apply( const asset_update_divide const asset_dividend_data_object& dividend_data = asset_to_update->dividend_data(d); d.modify(dividend_data, [&]( asset_dividend_data_object& dividend_data_obj ) { dividend_data_obj.options = op.new_options; + // whenever new options are set, clear out the scheduled payout/distribution times + // this will reset and cause the next distribution to happen at the next maintenance + // interval and a payout at the next_payout_time + dividend_data_obj.last_scheduled_payout_time.reset(); + dividend_data_obj.last_scheduled_distribution_time.reset(); }); } return void_result(); diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 93507b7c..9f418d3e 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -724,11 +724,12 @@ void deprecate_annual_members( database& db ) void schedule_pending_dividend_balances(database& db, const asset_object& dividend_holder_asset_obj, const asset_dividend_data_object& dividend_data, + const fc::time_point_sec& current_head_block_time, const account_balance_index& balance_index, const distributed_dividend_balance_object_index& distributed_dividend_balance_index, const pending_dividend_payout_balance_object_index& pending_payout_balance_index) { - dlog("Processing dividend payments for dividend holder asset asset type ${holder_asset} at time ${t}", + dlog("Processing dividend payments for dividend holder asset type ${holder_asset} at time ${t}", ("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time())); auto current_distribution_account_balance_range = balance_index.indices().get().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account)); @@ -737,148 +738,257 @@ void schedule_pending_dividend_balances(database& db, // the current range is now all current balances for the distribution account, sorted by asset_type // the previous range is now all previous balances for this account, sorted by asset type + const auto& gpo = db.get_global_properties(); + + // get the list of accounts that hold nonzero balances of the dividend asset + auto holder_balances_begin = + balance_index.indices().get().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id)); + auto holder_balances_end = + balance_index.indices().get().upper_bound(boost::make_tuple(dividend_holder_asset_obj.id, share_type())); + uint32_t holder_account_count = std::distance(holder_balances_begin, holder_balances_end); + uint64_t distribution_base_fee = gpo.parameters.current_fees->get().distribution_base_fee; + uint32_t distribution_fee_per_holder = gpo.parameters.current_fees->get().distribution_fee_per_holder; + // the fee, in BTS, for distributing each asset in the account + uint64_t total_fee_per_asset_in_core = distribution_base_fee + holder_account_count * (uint64_t)distribution_fee_per_holder; + auto current_distribution_account_balance_iter = current_distribution_account_balance_range.first; auto previous_distribution_account_balance_iter = previous_distribution_account_balance_range.first; dlog("Current balances in distribution account: ${current}, Previous balances: ${previous}", ("current", std::distance(current_distribution_account_balance_range.first, current_distribution_account_balance_range.second)) ("previous", std::distance(previous_distribution_account_balance_range.first, previous_distribution_account_balance_range.second))); + // loop through all of the assets currently or previously held in the distribution account while (current_distribution_account_balance_iter != current_distribution_account_balance_range.second || previous_distribution_account_balance_iter != previous_distribution_account_balance_range.second) { - share_type current_balance; - share_type previous_balance; - asset_id_type payout_asset_type; - - if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || - current_distribution_account_balance_iter->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type) + try { - payout_asset_type = current_distribution_account_balance_iter->asset_type; - current_balance = current_distribution_account_balance_iter->balance; - idump((payout_asset_type)(current_balance)); - } - else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.second || - previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->asset_type) - { - payout_asset_type = previous_distribution_account_balance_iter->dividend_payout_asset_type; - previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; - idump((payout_asset_type)(previous_balance)); - } - else - { - payout_asset_type = current_distribution_account_balance_iter->asset_type; - current_balance = current_distribution_account_balance_iter->balance; - previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; - idump((payout_asset_type)(current_balance)(previous_balance)); - } + // First, figure out how much the balance on this asset has changed since the last sharing out + share_type current_balance; + share_type previous_balance; + asset_id_type payout_asset_type; - share_type delta_balance = current_balance - previous_balance; - dlog("Processing dividend payments of asset type ${payout_asset_type}, delta balance is ${delta_balance}", ("payout_asset_type", payout_asset_type(db).symbol)("delta_balance", delta_balance)); - if (delta_balance > 0) - { - // we need to pay out delta_balance to shareholders proportional to their stake - auto holder_account_balance_range = - balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); - share_type total_balance_of_dividend_asset; - for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second)) - if (holder_balance_object.owner != dividend_data.dividend_distribution_account) - // TODO: if holder_balance_object.owner is able to accept payout_asset_type - total_balance_of_dividend_asset += holder_balance_object.balance; + if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || + current_distribution_account_balance_iter->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type) + { + payout_asset_type = current_distribution_account_balance_iter->asset_type; + current_balance = current_distribution_account_balance_iter->balance; + idump((payout_asset_type)(current_balance)); + } + else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.second || + previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->asset_type) + { + payout_asset_type = previous_distribution_account_balance_iter->dividend_payout_asset_type; + previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; + idump((payout_asset_type)(previous_balance)); + } + else + { + payout_asset_type = current_distribution_account_balance_iter->asset_type; + current_balance = current_distribution_account_balance_iter->balance; + previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; + idump((payout_asset_type)(current_balance)(previous_balance)); + } - dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}", - ("count", std::distance(holder_account_balance_range.first, holder_account_balance_range.second)) - ("total", total_balance_of_dividend_asset)); - share_type remaining_amount_to_distribute = delta_balance; - share_type remaining_balance_of_dividend_asset = total_balance_of_dividend_asset; + share_type delta_balance = current_balance - previous_balance; - for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second)) - if (holder_balance_object.owner != dividend_data.dividend_distribution_account && - holder_balance_object.balance.value) + // Next, figure out if we want to share this out -- if the amount added to the distribution + // account since last payout is too small, we won't bother. + + share_type total_fee_per_asset_in_payout_asset; + const asset_object* payout_asset_object = nullptr; + if (payout_asset_type == asset_id_type()) + { + payout_asset_object = &db.get_core_asset(); + total_fee_per_asset_in_payout_asset = total_fee_per_asset_in_core; + dlog("Fee for distributing ${payout_asset_type}: ${fee}", + ("payout_asset_type", asset_id_type()(db).symbol) + ("fee", asset(total_fee_per_asset_in_core, asset_id_type()))); + } + else + { + // figure out what the total fee is in terms of the payout asset + const asset_index& asset_object_index = db.get_index_type(); + auto payout_asset_object_iter = asset_object_index.indices().find(payout_asset_type); + FC_ASSERT(payout_asset_object_iter != asset_object_index.indices().end()); + + payout_asset_object = &*payout_asset_object_iter; + asset total_fee_per_asset = asset(total_fee_per_asset_in_core, asset_id_type()) * payout_asset_object->options.core_exchange_rate; + FC_ASSERT(total_fee_per_asset.asset_id == payout_asset_type); + + total_fee_per_asset_in_payout_asset = total_fee_per_asset.amount; + dlog("Fee for distributing ${payout_asset_type}: ${fee}", + ("payout_asset_type", payout_asset_type(db).symbol)("fee", total_fee_per_asset_in_payout_asset)); + } + + share_type minimum_shares_to_distribute; + if (dividend_data.options.minimum_fee_percentage) + { + fc::uint128_t minimum_amount_to_distribute = total_fee_per_asset_in_payout_asset.value; + minimum_amount_to_distribute *= 100 * GRAPHENE_1_PERCENT; + minimum_amount_to_distribute /= dividend_data.options.minimum_fee_percentage; + wdump((total_fee_per_asset_in_payout_asset)(dividend_data.options)); + minimum_shares_to_distribute = minimum_amount_to_distribute.to_uint64(); + } + + dlog("Processing dividend payments of asset type ${payout_asset_type}, delta balance is ${delta_balance}", ("payout_asset_type", payout_asset_type(db).symbol)("delta_balance", delta_balance)); + if (delta_balance > 0) + { + if (delta_balance >= minimum_shares_to_distribute) { - // TODO: if holder_balance_object.owner is able to accept payout_asset_type - fc::uint128_t amount_to_credit(remaining_amount_to_distribute.value); - amount_to_credit *= holder_balance_object.balance.value; - amount_to_credit /= remaining_balance_of_dividend_asset.value; - share_type shares_to_credit((int64_t)amount_to_credit.to_uint64()); + // first, pay the fee for scheduling these dividend payments + if (payout_asset_type == asset_id_type()) + { + // pay fee to network + db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) { + d.accumulated_fees += total_fee_per_asset_in_core; + }); + db.adjust_balance(dividend_data.dividend_distribution_account, + asset(-total_fee_per_asset_in_core, asset_id_type())); + delta_balance -= total_fee_per_asset_in_core; + } + else + { + const asset_dynamic_data_object& dynamic_data = payout_asset_object->dynamic_data(db); + if (dynamic_data.fee_pool < total_fee_per_asset_in_core) + FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} " + "because insufficient funds in fee pool (need: ${need}, have: ${have})", + ("holder_asset_type", dividend_holder_asset_obj.symbol) + ("payout_asset_type", payout_asset_object->symbol) + ("need", asset(total_fee_per_asset_in_core, asset_id_type())) + ("have", asset(dynamic_data.fee_pool, payout_asset_type))); + // deduct the fee from the dividend distribution account + db.adjust_balance(dividend_data.dividend_distribution_account, + asset(-total_fee_per_asset_in_payout_asset, payout_asset_type)); + // convert it to core + db.modify(payout_asset_object->dynamic_data(db), [total_fee_per_asset_in_core, total_fee_per_asset_in_payout_asset](asset_dynamic_data_object& d) { + d.fee_pool -= total_fee_per_asset_in_core; + d.accumulated_fees += total_fee_per_asset_in_payout_asset; + }); + // and pay it to the network + db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) { + d.accumulated_fees += total_fee_per_asset_in_core; + }); + delta_balance -= total_fee_per_asset_in_payout_asset; + } - remaining_amount_to_distribute -= shares_to_credit; - remaining_balance_of_dividend_asset -= holder_balance_object.balance; + // we need to pay out the remaining delta_balance to shareholders proportional to their stake + // so find out what the total stake + share_type total_balance_of_dividend_asset; + for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end)) + if (holder_balance_object.owner != dividend_data.dividend_distribution_account) + total_balance_of_dividend_asset += holder_balance_object.balance; - dlog("Crediting account ${account} with ${amount}", ("account", holder_balance_object.owner(db).name)("amount", amount_to_credit)); - auto pending_payout_iter = - pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner)); - if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) - db.create( [&]( pending_dividend_payout_balance_object& obj ){ - obj.owner = holder_balance_object.owner; + dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}", + ("count", holder_account_count) + ("total", total_balance_of_dividend_asset)); + share_type remaining_amount_to_distribute = delta_balance; + + // credit each account with their portion + for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end)) + if (holder_balance_object.owner != dividend_data.dividend_distribution_account && + holder_balance_object.balance.value) + { + fc::uint128_t amount_to_credit(delta_balance.value); + amount_to_credit *= holder_balance_object.balance.value; + amount_to_credit /= total_balance_of_dividend_asset.value; + wdump((delta_balance.value)(holder_balance_object.balance)(total_balance_of_dividend_asset)); + share_type shares_to_credit((int64_t)amount_to_credit.to_uint64()); + + remaining_amount_to_distribute -= shares_to_credit; + + dlog("Crediting account ${account} with ${amount}", + ("account", holder_balance_object.owner(db).name) + ("amount", asset(shares_to_credit, payout_asset_type))); + auto pending_payout_iter = + pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner)); + if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) + db.create( [&]( pending_dividend_payout_balance_object& obj ){ + obj.owner = holder_balance_object.owner; + obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; + obj.dividend_payout_asset_type = payout_asset_type; + obj.pending_balance = shares_to_credit; + }); + else + db.modify(*pending_payout_iter, [&]( pending_dividend_payout_balance_object& pending_balance ){ + pending_balance.pending_balance += shares_to_credit; + }); + } + + for (const auto& pending_payout : pending_payout_balance_index.indices()) + dlog("Pending payout: ${account_name} -> ${amount}", + ("account_name", pending_payout.owner(db).name) + ("amount", asset(pending_payout.pending_balance, pending_payout.dividend_payout_asset_type))); + dlog("Remaining balance not paid out: ${amount}", + ("amount", asset(remaining_amount_to_distribute, payout_asset_type))); + + + share_type distributed_amount = delta_balance - remaining_amount_to_distribute; + if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || + previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type) + db.create( [&]( distributed_dividend_balance_object& obj ){ obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; obj.dividend_payout_asset_type = payout_asset_type; - obj.pending_balance = shares_to_credit; + obj.balance_at_last_maintenance_interval = distributed_amount; }); else - db.modify(*pending_payout_iter, [&]( pending_dividend_payout_balance_object& pending_balance ){ - pending_balance.pending_balance += shares_to_credit; + db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){ + obj.balance_at_last_maintenance_interval += distributed_amount; }); } - - for (const auto& pending_payout : pending_payout_balance_index.indices()) - { - dlog("Pending payout: ${account_name} -> ${amount}", ("account_name", pending_payout.owner(db).name)("amount", pending_payout.pending_balance)); + else + FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} " + "because amount ${delta_balance} is too small an amount to distribute.", + ("holder_asset_type", dividend_holder_asset_obj.symbol) + ("payout_asset_type", payout_asset_object->symbol) + ("delta_balance", asset(delta_balance, payout_asset_type))); } + else if (delta_balance < 0) + { + // some amount of the asset has been withdrawn from the dividend_distribution_account, + // meaning the current pending payout balances will add up to more than our current balance. + // This should be extremely rare. + // Reduce all pending payouts proportionally + share_type total_pending_balances; + auto pending_payouts_range = + pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); - share_type distributed_amount = current_balance - remaining_amount_to_distribute; - if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || - previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type) - db.create( [&]( distributed_dividend_balance_object& obj ){ - obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; - obj.dividend_payout_asset_type = payout_asset_type; - obj.balance_at_last_maintenance_interval = distributed_amount; - }); - else - db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){ - obj.balance_at_last_maintenance_interval = distributed_amount; - }); + for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) + total_pending_balances += pending_balance_object.pending_balance; + + share_type remaining_amount_to_recover = -delta_balance; + share_type remaining_pending_balances = total_pending_balances; + for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) + { + fc::uint128_t amount_to_debit(remaining_amount_to_recover.value); + amount_to_debit *= pending_balance_object.pending_balance.value; + amount_to_debit /= remaining_pending_balances.value; + share_type shares_to_debit((int64_t)amount_to_debit.to_uint64()); + + remaining_amount_to_recover -= shares_to_debit; + remaining_pending_balances -= pending_balance_object.pending_balance; + + db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ + pending_balance.pending_balance -= shares_to_debit; + }); + } + + if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || + previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type) + db.create( [&]( distributed_dividend_balance_object& obj ){ + obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; + obj.dividend_payout_asset_type = payout_asset_type; + obj.balance_at_last_maintenance_interval = 0; + }); + else + db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){ + obj.balance_at_last_maintenance_interval = 0; + }); + } // end if deposit was large enough to distribute } - else if (delta_balance < 0) + catch (const fc::exception& e) { - // some amount of the asset has been withdrawn from the dividend_distribution_account, - // meaning the current pending payout balances will add up to more than our current balance. - // This should be extremely rare. - // Reduce all pending payouts proportionally - share_type total_pending_balances; - auto pending_payouts_range = - pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); - - for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) - total_pending_balances += pending_balance_object.pending_balance; - - share_type remaining_amount_to_recover = -delta_balance; - share_type remaining_pending_balances = total_pending_balances; - for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) - { - fc::uint128_t amount_to_debit(remaining_amount_to_recover.value); - amount_to_debit *= pending_balance_object.pending_balance.value; - amount_to_debit /= remaining_pending_balances.value; - share_type shares_to_debit((int64_t)amount_to_debit.to_uint64()); - - remaining_amount_to_recover -= shares_to_debit; - remaining_pending_balances -= pending_balance_object.pending_balance; - - db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ - pending_balance.pending_balance -= shares_to_debit; - }); - } - - if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || - previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type) - db.create( [&]( distributed_dividend_balance_object& obj ){ - obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; - obj.dividend_payout_asset_type = payout_asset_type; - obj.balance_at_last_maintenance_interval = 0; - }); - else - db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){ - obj.balance_at_last_maintenance_interval = 0; - }); + dlog("${e}", ("e", e)); } // iterate @@ -894,6 +1004,11 @@ void schedule_pending_dividend_balances(database& db, ++previous_distribution_account_balance_iter; } } + db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) { + dividend_data_obj.last_scheduled_distribution_time = current_head_block_time; + dividend_data_obj.last_distribution_time = current_head_block_time; + }); + } void process_dividend_assets(database& db) @@ -911,9 +1026,10 @@ void process_dividend_assets(database& db) const asset_dividend_data_object& dividend_data = dividend_holder_asset_obj.dividend_data(db); const account_object& dividend_distribution_account_object = dividend_data.dividend_distribution_account(db); - schedule_pending_dividend_balances(db, dividend_holder_asset_obj, dividend_data, - balance_index, distributed_dividend_balance_index, pending_payout_balance_index); fc::time_point_sec current_head_block_time = db.head_block_time(); + + schedule_pending_dividend_balances(db, dividend_holder_asset_obj, dividend_data, current_head_block_time, + balance_index, distributed_dividend_balance_index, pending_payout_balance_index); if (dividend_data.options.next_payout_time && db.head_block_time() >= *dividend_data.options.next_payout_time) { @@ -965,7 +1081,7 @@ void process_dividend_assets(database& db) db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, *last_holder_account_id, payouts_for_this_holder)); - ilog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); + dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); payouts_for_this_holder.clear(); last_holder_account_id.reset(); } @@ -974,7 +1090,7 @@ void process_dividend_assets(database& db) if (is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) && is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type)) { - ilog("Processing payout of ${asset} to account ${account}", + dlog("Processing payout of ${asset} to account ${account}", ("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)) ("account", pending_balance_object.owner(db).name)); @@ -1000,7 +1116,7 @@ void process_dividend_assets(database& db) db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, *last_holder_account_id, payouts_for_this_holder)); - ilog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); + dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); } // now debit the total amount of dividends paid out from the distribution account @@ -1027,7 +1143,6 @@ void process_dividend_assets(database& db) // now schedule the next payout time db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) { - dlog("Updating dividend payout time, new values are:"); dividend_data_obj.last_scheduled_payout_time = dividend_data_obj.options.next_payout_time; dividend_data_obj.last_payout_time = current_head_block_time; fc::optional next_payout_time; @@ -1044,7 +1159,9 @@ void process_dividend_assets(database& db) next_payout_time = next_possible_time_sec; } dividend_data_obj.options.next_payout_time = next_payout_time; - idump((dividend_data_obj.last_scheduled_payout_time)(dividend_data_obj.last_payout_time)(dividend_data_obj.options.next_payout_time)); + idump((dividend_data_obj.last_scheduled_payout_time) + (dividend_data_obj.last_payout_time) + (dividend_data_obj.options.next_payout_time)); }); } } diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index db3e386b..c284e0f7 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -270,12 +270,24 @@ namespace graphene { namespace chain { dividend_asset_options options; /// The time payouts on this asset were scheduled to be processed last + /// This field is reset any time the dividend_asset_options are updated fc::optional last_scheduled_payout_time; /// The time payouts on this asset were last processed /// (this should be the maintenance interval at or after last_scheduled_payout_time) + /// This can be displayed for the user fc::optional last_payout_time; + /// The time pending payouts on this asset were last computed, used for + /// correctly computing the next pending payout time. + /// This field is reset any time the dividend_asset_options are updated + fc::optional last_scheduled_distribution_time; + + /// The time pending payouts on this asset were last computed. + /// (this should be the maintenance interval at or after last_scheduled_distribution_time) + /// This can be displayed for the user + fc::optional last_distribution_time; + /// The account which collects pending payouts account_id_type dividend_distribution_account; }; diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index db111cbe..ca82526c 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -128,6 +128,29 @@ namespace graphene { namespace chain { /// If payout_interval is not set, the next payout (if any) will be the last until /// the options are updated again. fc::optional payout_interval; + /// Each dividend distribution incurs a fee that is based on the number of accounts + /// that hold the dividend asset, not as a percentage of the amount paid out. + /// This parameter prevents assets from being distributed unless the fee is less than + /// the percentage here, to prevent a slow trickle of deposits to the account from being + /// completely consumed. + /// In other words, if you set this parameter to 10% and the fees work out to 100 BTS + /// to share out, balances in the dividend distribution accounts will not be shared out + /// if the balance is less than 10000 BTS. + uint64_t minimum_fee_percentage; + + /// Normally, pending dividend payments are calculated each maintenance interval in + /// which there are balances in the dividend distribution account. At present, this + /// is once per hour on the BitShares blockchain. If this is too often (too expensive + /// in fees or to computationally-intensive for the blockchain) this can be increased. + /// If you set this to, for example, one day, distributions will take place on even + /// multiples of one day, allowing deposits to the distribution account to accumulate + /// for 23 maintenance intervals and then computing the pending payouts on the 24th. + /// + /// Payouts will always occur at the next payout time whether or not it falls on a + /// multiple of the distribution interval, and the timer on the distribution interval + /// are reset at payout time. So if you have the distribution interval at three days + /// and the payout interval at one week, payouts will occur at days 3, 6, 7, 10, 13, 14... + fc::optional minimum_distribution_interval; extensions_type extensions; @@ -273,7 +296,23 @@ namespace graphene { namespace chain { account_id(account_id), amounts(amounts) {} - struct fee_parameters_type { }; + struct fee_parameters_type { + /* note: this is a virtual op and there are no fees directly charged for it */ + + /* Whenever the system computes the pending dividend payments for an asset, + * it charges the distribution_base_fee + distribution_fee_per_holder. + * The computational cost of distributing the dividend payment is proportional + * to the number of dividend holders the asset is divided up among. + */ + /** This fee is charged whenever the system schedules pending dividend + * payments. + */ + uint64_t distribution_base_fee; + /** This fee is charged (in addition to the distribution_base_fee) for each + * user the dividend payment is shared out amongst + */ + uint32_t distribution_fee_per_holder; + }; asset fee; @@ -582,7 +621,7 @@ FC_REFLECT( graphene::chain::asset_update_feed_producers_operation::fee_paramete FC_REFLECT( graphene::chain::asset_publish_feed_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_reserve_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::chain::asset_dividend_distribution_operation::fee_parameters_type, ) +FC_REFLECT( graphene::chain::asset_dividend_distribution_operation::fee_parameters_type, (distribution_base_fee)(distribution_fee_per_holder)) FC_REFLECT( graphene::chain::asset_create_operation, (fee) diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index b171d5c7..1e4d40a8 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1001,6 +1001,21 @@ class wallet_api bitasset_options new_options, bool broadcast = false); + + /** Update the given asset's dividend asset options. + * + * If the asset is not already a dividend-paying asset, it will be converted into one. + * + * @param symbol the name or id of the asset to update, which must be a market-issued asset + * @param new_options the new dividend_asset_options object, which will entirely replace the existing + * options. + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction updating the asset + */ + signed_transaction update_dividend_asset(string symbol, + dividend_asset_options new_options, + bool broadcast = false); + /** Update the set of feed-producing accounts for a BitAsset. * * BitAssets have price feeds selected by taking the median values of recommendations from a set of feed producers. @@ -1579,6 +1594,7 @@ FC_API( graphene::wallet::wallet_api, (create_asset) (update_asset) (update_bitasset) + (update_dividend_asset) (update_asset_feed_producers) (publish_asset_feed) (issue_asset) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index d7e9c1d1..6797b1e5 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -3194,6 +3194,14 @@ signed_transaction wallet_api::update_bitasset(string symbol, return my->update_bitasset(symbol, new_options, broadcast); } +signed_transaction wallet_api::update_dividend_asset(string symbol, + dividend_asset_options new_options, + bool broadcast /* = false */) +{ + return my->update_dividend_asset(symbol, new_options, broadcast); +} + + signed_transaction wallet_api::update_asset_feed_producers(string symbol, flat_set new_feed_producers, bool broadcast /* = false */) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 85ec439f..d02ee4c0 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1162,6 +1162,18 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) } generate_block(); + BOOST_TEST_MESSAGE("Funding asset fee pool"); + { + asset_fund_fee_pool_operation fund_op; + fund_op.from_account = account_id_type(); + fund_op.asset_id = get_asset("TEST").id; + fund_op.amount = 500000000; + trx.operations.push_back(std::move(fund_op)); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + // our DIVIDEND asset should not yet be a divdend asset const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); BOOST_CHECK(!dividend_holder_asset_object.dividend_data_id); @@ -1192,6 +1204,12 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); BOOST_CHECK_EQUAL(dividend_distribution_account.name, "dividend-dividend-distribution"); + // db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) + // { + // _gpo.parameters.current_fees->get().distribution_base_fee = 100; + // _gpo.parameters.current_fees->get().distribution_fee_per_holder = 100; + // } ); + } catch(fc::exception& e) { edump((e.to_detail_string())); @@ -1208,6 +1226,26 @@ BOOST_AUTO_TEST_CASE( test_update_dividend_interval ) const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + auto advance_to_next_payout_time = [&]() { + // Advance to the next upcoming payout time + BOOST_REQUIRE(dividend_data.options.next_payout_time); + fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; + // generate blocks up to the next scheduled time + generate_blocks(next_payout_scheduled_time); + // if the scheduled time fell on a maintenance interval, then we should have paid out. + // if not, we need to advance to the next maintenance interval to trigger the payout + if (dividend_data.options.next_payout_time) + { + // we know there was a next_payout_time set when we entered this, so if + // it has been cleared, we must have already processed payouts, no need to + // further advance time. + BOOST_REQUIRE(dividend_data.options.next_payout_time); + if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + } + }; + BOOST_TEST_MESSAGE("Updating the payout interval"); { asset_update_dividend_operation op; @@ -1227,6 +1265,23 @@ BOOST_AUTO_TEST_CASE( test_update_dividend_interval ) BOOST_REQUIRE(dividend_data.options.payout_interval); BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24); } + + BOOST_TEST_MESSAGE("Removing the payout interval"); + { + asset_update_dividend_operation op; + op.issuer = dividend_holder_asset_object.issuer; + op.asset_to_update = dividend_holder_asset_object.id; + op.new_options.next_payout_time = dividend_data.options.next_payout_time; + op.new_options.payout_interval = fc::optional(); + trx.operations.push_back(op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + BOOST_CHECK(!dividend_data.options.payout_interval); + advance_to_next_payout_time(); + BOOST_REQUIRE_MESSAGE(!dividend_data.options.next_payout_time, "A new payout was scheduled, but none should have been"); } catch(fc::exception& e) { edump((e.to_detail_string())); throw; @@ -1365,6 +1420,28 @@ BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution ) throw; } } +BOOST_AUTO_TEST_CASE( test_dividend_distribution_interval ) +{ + using namespace graphene; + try { + INVOKE( create_dividend_uia ); + + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + const account_object& alice = get_account("alice"); + const account_object& bob = get_account("bob"); + const account_object& carol = get_account("carol"); + const account_object& dave = get_account("dave"); + const account_object& frank = get_account("frank"); + const auto& test_asset_object = get_asset("TEST"); + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + + BOOST_AUTO_TEST_CASE( check_dividend_corner_cases ) { using namespace graphene; @@ -1463,26 +1540,24 @@ BOOST_AUTO_TEST_CASE( check_dividend_corner_cases ) BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); // get the maintenance skip slots out of the way - BOOST_TEST_MESSAGE("Verify that no pending payments were scheduled"); + BOOST_TEST_MESSAGE("Verify that no alice received her payment of the entire amount"); verify_pending_balance(alice, test_asset_object, 1000); - BOOST_TEST_MESSAGE("Removing the payout interval"); - { - asset_update_dividend_operation op; - op.issuer = dividend_holder_asset_object.issuer; - op.asset_to_update = dividend_holder_asset_object.id; - op.new_options.next_payout_time = dividend_data.options.next_payout_time; - op.new_options.payout_interval = fc::optional(); - trx.operations.push_back(op); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } + // Test that we can pay out the dividend asset itself + issue_asset_to_account(dividend_holder_asset_object, bob, 1); + issue_asset_to_account(dividend_holder_asset_object, carol, 1); + issue_asset_to_account(dividend_holder_asset_object, dividend_distribution_account, 300); generate_block(); - BOOST_CHECK(!dividend_data.options.payout_interval); - advance_to_next_payout_time(); - BOOST_REQUIRE_MESSAGE(!dividend_data.options.next_payout_time, "A new payout was scheduled, but none should have been"); - + BOOST_CHECK_EQUAL(get_balance(alice, dividend_holder_asset_object), 1); + BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 1); + BOOST_CHECK_EQUAL(get_balance(carol, dividend_holder_asset_object), 1); + BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + BOOST_TEST_MESSAGE("Verify that the dividend asset was shared out"); + verify_pending_balance(alice, dividend_holder_asset_object, 100); + verify_pending_balance(bob, dividend_holder_asset_object, 100); + verify_pending_balance(carol, dividend_holder_asset_object, 100); } catch(fc::exception& e) { edump((e.to_detail_string())); throw; From be6ad130d926a5cdb9fe0447c2dcf3ff077d45a7 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Fri, 26 Aug 2016 11:29:37 -0400 Subject: [PATCH 33/34] Code cleanups -- renaming variables, adding comments, fix one bug with override transfers and dividend assets --- docs | 2 +- libraries/app/database_api.cpp | 2 +- .../app/include/graphene/app/full_account.hpp | 2 +- libraries/chain/db_init.cpp | 4 +- libraries/chain/db_maint.cpp | 68 +++++++++---------- .../include/graphene/chain/account_object.hpp | 36 +++++----- .../include/graphene/chain/asset_object.hpp | 16 ++--- .../include/graphene/chain/protocol/types.hpp | 8 +-- tests/common/database_fixture.cpp | 4 +- 9 files changed, 71 insertions(+), 71 deletions(-) diff --git a/docs b/docs index cdc8ea81..97435c1a 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit cdc8ea8133a999afef8051700a4ce8edb0988ec4 +Subproject commit 97435c1a622e41e0a5fc1be72aaadea62e1b7adb diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 42a0b641..dc8c4dfc 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -607,7 +607,7 @@ std::map database_api_impl::get_full_accounts( const }); auto pending_payouts_range = - _db.get_index_type().indices().get().equal_range(boost::make_tuple(account->id)); + _db.get_index_type().indices().get().equal_range(boost::make_tuple(account->id)); std::copy(pending_payouts_range.first, pending_payouts_range.second, std::back_inserter(acnt.pending_dividend_payments)); diff --git a/libraries/app/include/graphene/app/full_account.hpp b/libraries/app/include/graphene/app/full_account.hpp index 787207ef..0d94348f 100644 --- a/libraries/app/include/graphene/app/full_account.hpp +++ b/libraries/app/include/graphene/app/full_account.hpp @@ -44,7 +44,7 @@ namespace graphene { namespace app { vector limit_orders; vector call_orders; vector proposals; - vector pending_dividend_payments; + vector pending_dividend_payments; }; } } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 8e0372ba..325d7862 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -218,8 +218,8 @@ void database::initialize_indexes() add_index< primary_index< buyback_index > >(); add_index< primary_index< simple_index< fba_accumulator_object > > >(); - add_index< primary_index >(); - add_index< primary_index >(); + add_index< primary_index >(); + add_index< primary_index >(); } void database::init_genesis(const genesis_state_type& genesis_state) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 9f418d3e..3684c37f 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -726,8 +726,8 @@ void schedule_pending_dividend_balances(database& db, const asset_dividend_data_object& dividend_data, const fc::time_point_sec& current_head_block_time, const account_balance_index& balance_index, - const distributed_dividend_balance_object_index& distributed_dividend_balance_index, - const pending_dividend_payout_balance_object_index& pending_payout_balance_index) + const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index, + const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index) { dlog("Processing dividend payments for dividend holder asset type ${holder_asset} at time ${t}", ("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time())); @@ -757,6 +757,15 @@ void schedule_pending_dividend_balances(database& db, ("current", std::distance(current_distribution_account_balance_range.first, current_distribution_account_balance_range.second)) ("previous", std::distance(previous_distribution_account_balance_range.first, previous_distribution_account_balance_range.second))); + // when we pay out the dividends to the holders, we need to know the total balance of the dividend asset in all + // accounts other than the distribution account (it would be silly to distribute dividends back to + // the distribution account) + share_type total_balance_of_dividend_asset; + for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end)) + if (holder_balance_object.owner != dividend_data.dividend_distribution_account) + total_balance_of_dividend_asset += holder_balance_object.balance; + + // loop through all of the assets currently or previously held in the distribution account while (current_distribution_account_balance_iter != current_distribution_account_balance_range.second || previous_distribution_account_balance_iter != previous_distribution_account_balance_range.second) @@ -771,6 +780,7 @@ void schedule_pending_dividend_balances(database& db, if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || current_distribution_account_balance_iter->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type) { + // there are no more previous balances or there is no previous balance for this particular asset type payout_asset_type = current_distribution_account_balance_iter->asset_type; current_balance = current_distribution_account_balance_iter->balance; idump((payout_asset_type)(current_balance)); @@ -778,12 +788,14 @@ void schedule_pending_dividend_balances(database& db, else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.second || previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->asset_type) { + // there are no more current balances or there is no current balance for this particular previous asset type payout_asset_type = previous_distribution_account_balance_iter->dividend_payout_asset_type; previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; idump((payout_asset_type)(previous_balance)); } else { + // we have both a previous and a current balance for this asset type payout_asset_type = current_distribution_account_balance_iter->asset_type; current_balance = current_distribution_account_balance_iter->balance; previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; @@ -872,19 +884,12 @@ void schedule_pending_dividend_balances(database& db, delta_balance -= total_fee_per_asset_in_payout_asset; } - // we need to pay out the remaining delta_balance to shareholders proportional to their stake - // so find out what the total stake - share_type total_balance_of_dividend_asset; - for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end)) - if (holder_balance_object.owner != dividend_data.dividend_distribution_account) - total_balance_of_dividend_asset += holder_balance_object.balance; - dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}", ("count", holder_account_count) ("total", total_balance_of_dividend_asset)); share_type remaining_amount_to_distribute = delta_balance; - // credit each account with their portion + // credit each account with their portion, don't send any back to the dividend distribution account for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end)) if (holder_balance_object.owner != dividend_data.dividend_distribution_account && holder_balance_object.balance.value) @@ -903,14 +908,14 @@ void schedule_pending_dividend_balances(database& db, auto pending_payout_iter = pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner)); if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) - db.create( [&]( pending_dividend_payout_balance_object& obj ){ + db.create( [&]( pending_dividend_payout_balance_for_holder_object& obj ){ obj.owner = holder_balance_object.owner; obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; obj.dividend_payout_asset_type = payout_asset_type; obj.pending_balance = shares_to_credit; }); else - db.modify(*pending_payout_iter, [&]( pending_dividend_payout_balance_object& pending_balance ){ + db.modify(*pending_payout_iter, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){ pending_balance.pending_balance += shares_to_credit; }); } @@ -926,13 +931,13 @@ void schedule_pending_dividend_balances(database& db, share_type distributed_amount = delta_balance - remaining_amount_to_distribute; if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type) - db.create( [&]( distributed_dividend_balance_object& obj ){ + db.create( [&]( total_distributed_dividend_balance_object& obj ){ obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; obj.dividend_payout_asset_type = payout_asset_type; obj.balance_at_last_maintenance_interval = distributed_amount; }); else - db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){ + db.modify(*previous_distribution_account_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){ obj.balance_at_last_maintenance_interval += distributed_amount; }); } @@ -947,18 +952,18 @@ void schedule_pending_dividend_balances(database& db, { // some amount of the asset has been withdrawn from the dividend_distribution_account, // meaning the current pending payout balances will add up to more than our current balance. - // This should be extremely rare. + // This should be extremely rare (caused by an override transfer by the asset owner). // Reduce all pending payouts proportionally share_type total_pending_balances; auto pending_payouts_range = pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); - for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) + for (const pending_dividend_payout_balance_for_holder_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) total_pending_balances += pending_balance_object.pending_balance; share_type remaining_amount_to_recover = -delta_balance; share_type remaining_pending_balances = total_pending_balances; - for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) + for (const pending_dividend_payout_balance_for_holder_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) { fc::uint128_t amount_to_debit(remaining_amount_to_recover.value); amount_to_debit *= pending_balance_object.pending_balance.value; @@ -968,22 +973,17 @@ void schedule_pending_dividend_balances(database& db, remaining_amount_to_recover -= shares_to_debit; remaining_pending_balances -= pending_balance_object.pending_balance; - db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ + db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){ pending_balance.pending_balance -= shares_to_debit; }); } - if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || - previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type) - db.create( [&]( distributed_dividend_balance_object& obj ){ - obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; - obj.dividend_payout_asset_type = payout_asset_type; - obj.balance_at_last_maintenance_interval = 0; - }); - else - db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){ - obj.balance_at_last_maintenance_interval = 0; - }); + // if we're here, we know there must be a previous balance, so just adjust it by the + // amount we just reclaimed + db.modify(*previous_distribution_account_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){ + obj.balance_at_last_maintenance_interval += delta_balance; + assert(obj.balance_at_last_maintenance_interval == current_balance); + }); } // end if deposit was large enough to distribute } catch (const fc::exception& e) @@ -1016,8 +1016,8 @@ void process_dividend_assets(database& db) ilog("In process_dividend_assets time ${time}", ("time", db.head_block_time())); const account_balance_index& balance_index = db.get_index_type(); - const distributed_dividend_balance_object_index& distributed_dividend_balance_index = db.get_index_type(); - const pending_dividend_payout_balance_object_index& pending_payout_balance_index = db.get_index_type(); + const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index = db.get_index_type(); + const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index = db.get_index_type(); // TODO: switch to iterating over only dividend assets (generalize the by_type index) for( const asset_object& dividend_holder_asset_obj : db.get_index_type().indices() ) @@ -1073,7 +1073,7 @@ void process_dividend_assets(database& db) for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; ) { - const pending_dividend_payout_balance_object& pending_balance_object = *pending_balance_object_iter; + const pending_dividend_payout_balance_for_holder_object& pending_balance_object = *pending_balance_object_iter; if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner) { @@ -1102,7 +1102,7 @@ void process_dividend_assets(database& db) last_holder_account_id = pending_balance_object.owner; amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance; - db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ + db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){ pending_balance.pending_balance = 0; }); } @@ -1135,7 +1135,7 @@ void process_dividend_assets(database& db) asset_paid_out)); assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()); if (distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()) - db.modify(*distributed_balance_iter, [&]( distributed_dividend_balance_object& obj ){ + db.modify(*distributed_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){ obj.balance_at_last_maintenance_interval -= amount_paid_out; // now they've been paid out, reset to zero }); diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 1b65957c..9f51de7c 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -318,11 +318,11 @@ namespace graphene { namespace chain { * @ingroup object * */ - class pending_dividend_payout_balance_object : public abstract_object + class pending_dividend_payout_balance_for_holder_object : public abstract_object { public: static const uint8_t space_id = implementation_ids; - static const uint8_t type_id = impl_pending_dividend_payout_balance_object_type; + static const uint8_t type_id = impl_pending_dividend_payout_balance_for_holder_object_type; account_id_type owner; asset_id_type dividend_holder_asset_type; @@ -397,40 +397,40 @@ namespace graphene { namespace chain { * @ingroup object_index */ typedef multi_index_container< - pending_dividend_payout_balance_object, + pending_dividend_payout_balance_for_holder_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, ordered_unique< tag, composite_key< - pending_dividend_payout_balance_object, - member, - member, - member + pending_dividend_payout_balance_for_holder_object, + member, + member, + member > >, ordered_unique< tag, composite_key< - pending_dividend_payout_balance_object, - member, - member, - member + pending_dividend_payout_balance_for_holder_object, + member, + member, + member > >, ordered_unique< tag, composite_key< - pending_dividend_payout_balance_object, - member, - member, - member + pending_dividend_payout_balance_for_holder_object, + member, + member, + member > > > - > pending_dividend_payout_balance_object_multi_index_type; + > pending_dividend_payout_balance_for_holder_object_multi_index_type; /** * @ingroup object_index */ - typedef generic_index pending_dividend_payout_balance_object_index; + typedef generic_index pending_dividend_payout_balance_for_holder_object_index; }} @@ -460,7 +460,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (pending_fees)(pending_vested_fees) ) -FC_REFLECT_DERIVED( graphene::chain::pending_dividend_payout_balance_object, +FC_REFLECT_DERIVED( graphene::chain::pending_dividend_payout_balance_for_holder_object, (graphene::db::object), (owner)(dividend_holder_asset_type)(dividend_payout_asset_type)(pending_balance) ) diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index c284e0f7..70f8443d 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -304,7 +304,7 @@ namespace graphene { namespace chain { // pending dividend payouts were calculated (last maintenance interval). // At each maintenance interval, we will compare the current balance to the // balance stored here to see how much was deposited during that interval. - class distributed_dividend_balance_object : public abstract_object + class total_distributed_dividend_balance_object : public abstract_object { public: static const uint8_t space_id = implementation_ids; @@ -316,19 +316,19 @@ namespace graphene { namespace chain { }; struct by_dividend_payout_asset{}; typedef multi_index_container< - distributed_dividend_balance_object, + total_distributed_dividend_balance_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, ordered_unique< tag, composite_key< - distributed_dividend_balance_object, - member, - member + total_distributed_dividend_balance_object, + member, + member > > > - > distributed_dividend_balance_object_multi_index_type; - typedef generic_index distributed_dividend_balance_object_index; + > total_distributed_dividend_balance_object_multi_index_type; + typedef generic_index total_distributed_dividend_balance_object_index; @@ -355,7 +355,7 @@ FC_REFLECT_DERIVED( graphene::chain::asset_dividend_data_object, (graphene::db:: (dividend_distribution_account) ) -FC_REFLECT_DERIVED( graphene::chain::distributed_dividend_balance_object, (graphene::db::object), +FC_REFLECT_DERIVED( graphene::chain::total_distributed_dividend_balance_object, (graphene::db::object), (dividend_holder_asset_type) (dividend_payout_asset_type) (balance_at_last_maintenance_interval) diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 948e22d9..402f12a3 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -157,7 +157,7 @@ namespace graphene { namespace chain { impl_buyback_object_type, impl_fba_accumulator_object_type, impl_asset_dividend_data_type, - impl_pending_dividend_payout_balance_object_type, + impl_pending_dividend_payout_balance_for_holder_object_type, impl_distributed_dividend_balance_data_type }; @@ -211,14 +211,14 @@ namespace graphene { namespace chain { class buyback_object; class fba_accumulator_object; class asset_dividend_data_object; - class pending_dividend_payout_balance_object; + class pending_dividend_payout_balance_for_holder_object; typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type; typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type; typedef object_id< implementation_ids, impl_asset_dynamic_data_type, asset_dynamic_data_object> asset_dynamic_data_id_type; typedef object_id< implementation_ids, impl_asset_bitasset_data_type, asset_bitasset_data_object> asset_bitasset_data_id_type; typedef object_id< implementation_ids, impl_asset_dividend_data_type, asset_dividend_data_object> asset_dividend_data_id_type; - typedef object_id< implementation_ids, impl_pending_dividend_payout_balance_object_type, pending_dividend_payout_balance_object> pending_dividend_payout_balance_object_type; + typedef object_id< implementation_ids, impl_pending_dividend_payout_balance_for_holder_object_type, pending_dividend_payout_balance_for_holder_object> pending_dividend_payout_balance_for_holder_object_type; typedef object_id< implementation_ids, impl_account_balance_object_type, account_balance_object> account_balance_id_type; typedef object_id< implementation_ids, impl_account_statistics_object_type,account_statistics_object> account_statistics_id_type; typedef object_id< implementation_ids, impl_transaction_object_type, transaction_object> transaction_obj_id_type; @@ -367,7 +367,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_buyback_object_type) (impl_fba_accumulator_object_type) (impl_asset_dividend_data_type) - (impl_pending_dividend_payout_balance_object_type) + (impl_pending_dividend_payout_balance_for_holder_object_type) (impl_distributed_dividend_balance_data_type) ) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index a69bfc30..6ecce113 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1040,8 +1040,8 @@ int64_t database_fixture::get_dividend_pending_payout_balance(asset_id_type divi account_id_type dividend_holder_account_id, asset_id_type dividend_payout_asset_type) const { - const pending_dividend_payout_balance_object_index& pending_payout_balance_index = - db.get_index_type(); + const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index = + db.get_index_type(); auto pending_payout_iter = pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id)); if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) From 908929210b38acb1c4d754b62920c554c29c240b Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Tue, 30 Aug 2016 14:18:21 -0400 Subject: [PATCH 34/34] Prevent creation of accounts with the same name as dividend distribution accounts --- libraries/chain/protocol/account.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/chain/protocol/account.cpp b/libraries/chain/protocol/account.cpp index b3ad9e00..ce6812da 100644 --- a/libraries/chain/protocol/account.cpp +++ b/libraries/chain/protocol/account.cpp @@ -22,7 +22,8 @@ * THE SOFTWARE. */ #include - +#include +#include namespace graphene { namespace chain { /** @@ -118,6 +119,12 @@ bool is_valid_name( const string& name ) break; begin = end+1; } + + // only dividend distribution accounts linked to a dividend asset can end in -dividend-distribution, and + // these can only be created as a side-effect of the asset_update_dividend_operation + if( boost::algorithm::ends_with(name, "-dividend-distribution") ) + return false; + return true; }