diff --git a/.gitignore b/.gitignore index 6227c1bc27..5490abcece 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,10 @@ programs/js_operation_serializer/js_operation_serializer programs/steemd/steemd programs/steemd/test programs/delayed_node +programs/build_helpers/cat-parts +programs/size_checker/size_checker +programs/util/get_dev_key +programs/util/inflation_model tests/app_test tests/chain_bench @@ -39,3 +43,4 @@ witness_node_data_dir *.pyc *.pyo +*.egg-info/ diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 140e7c3625..63ec91c28e 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -333,7 +333,6 @@ namespace detail { wild_access.allowed_apis.push_back( "network_broadcast_api" ); wild_access.allowed_apis.push_back( "history_api" ); wild_access.allowed_apis.push_back( "crypto_api" ); - wild_access.allowed_apis.push_back( "private_message_api" ); _apiaccess.permission_map["*"] = wild_access; } diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 3ef9b280c2..55b0d2db82 100755 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -127,7 +127,6 @@ void database_api::set_subscribe_callback( std::function c void database_api_impl::set_subscribe_callback( std::function cb, bool clear_filter ) { - edump((clear_filter)); _subscribe_callback = cb; if( clear_filter || !cb ) { @@ -755,7 +754,7 @@ void database_api::set_pending_payout( discussion& d )const if( props.total_reward_shares2 > 0 ){ int64_t abs_net_rshares = llabs(d.net_rshares.value); - + u256 r2 = to256(abs_net_rshares); r2 *= r2; r2 *= pot.amount.value; @@ -786,7 +785,7 @@ void database_api::set_url( discussion& d )const { } vector database_api::get_content_replies( string author, string permlink )const { - const auto& by_permlink_idx = my->_db.get_index_type< comment_index >().indices().get< by_parent_total_pending_payout >(); + const auto& by_permlink_idx = my->_db.get_index_type< comment_index >().indices().get< by_total_pending_payout_in_category >(); auto itr = by_permlink_idx.find( boost::make_tuple( author, permlink ) ); vector result; while( itr != by_permlink_idx.end() && itr->parent_author == author && itr->parent_permlink == permlink ) @@ -809,7 +808,7 @@ vector database_api::get_discussions_by_last_update( string start_pa auto itr = last_update_idx.begin(); - if( start_permlink.size() ) + if( start_permlink.size() ) itr = last_update_idx.iterator_to( my->_db.get_comment( start_parent_author, start_permlink ) ); else if( start_parent_author.size() ) { itr = last_update_idx.lower_bound( boost::make_tuple( start_parent_author, time_point_sec::maximum(), object_id_type() ) ); @@ -825,16 +824,20 @@ vector database_api::get_discussions_by_last_update( string start_pa return result; } -vector database_api::get_discussions_by_created( string start_auth, string start_permlink, uint32_t limit )const { - const auto& last_update_idx = my->_db.get_index_type< comment_index >().indices().get< by_created >(); +vector database_api::get_discussions_in_category_by_last_update( string category, string start_auth, string start_permlink, uint32_t limit )const { + const auto& last_update_in_category = my->_db.get_index_type< comment_index >().indices().get< by_last_update_in_category >(); - auto itr = last_update_idx.begin(); + auto itr = last_update_in_category.lower_bound( boost::make_tuple( "", category, fc::time_point_sec::maximum() ) ); if( start_auth.size() ) - itr = last_update_idx.iterator_to( my->_db.get_comment( start_auth, start_permlink ) ); + itr = last_update_in_category.iterator_to( my->_db.get_comment( start_auth, start_permlink ) ); vector result; - while( itr != last_update_idx.end() && result.size() < limit && !itr->parent_author.size() ) { + while( itr != last_update_in_category.end() && + itr->parent_permlink == category && + !itr->parent_author.size() + && result.size() < limit ) + { result.push_back( *itr ); set_pending_payout(result.back()); result.back().active_votes = get_active_votes( itr->author, itr->permlink ); @@ -843,17 +846,23 @@ vector database_api::get_discussions_by_created( string start_auth, return result; } -vector database_api::get_discussions_by_cashout_time( string start_auth, string start_permlink, uint32_t limit )const { - const auto& cashout_time_idx = my->_db.get_index_type< comment_index >().indices().get< by_cashout_time >(); - auto itr = cashout_time_idx.begin(); +vector database_api::get_discussions_by_last_active( string start_parent_author, string start_permlink, uint32_t limit )const { - if( start_auth.size() ) - itr = cashout_time_idx.iterator_to( my->_db.get_comment( start_auth, start_permlink ) ); + idump((start_parent_author)(start_permlink)(limit) ); + const auto& last_activity_idx = my->_db.get_index_type< comment_index >().indices().get< by_active >(); + + auto itr = last_activity_idx.begin(); + + + if( start_permlink.size() ) + itr = last_activity_idx.iterator_to( my->_db.get_comment( start_parent_author, start_permlink ) ); + else if( start_parent_author.size() ) { + itr = last_activity_idx.lower_bound( boost::make_tuple( start_parent_author, time_point_sec::maximum(), object_id_type() ) ); + } vector result; - while( itr != cashout_time_idx.end() && result.size() < limit ) { - idump((*itr)); + while( itr != last_activity_idx.end() && result.size() < limit ) { result.push_back( *itr ); set_pending_payout(result.back()); result.back().active_votes = get_active_votes( itr->author, itr->permlink ); @@ -861,18 +870,57 @@ vector database_api::get_discussions_by_cashout_time( string start_a } return result; } -vector database_api::get_discussions_in_category_by_cashout_time( string category, string start_auth, string start_permlink, uint32_t limit )const { + +vector database_api::get_discussions_by_votes( string start_parent_author, string start_permlink, uint32_t limit )const { + vector result; - const auto& cashout_time_in_category = my->_db.get_index_type< comment_index >().indices().get< by_cashout_time_in_category >(); + idump((start_parent_author)(start_permlink)(limit) ); + /* + const auto& last_activity_idx = my->_db.get_index_type< comment_stats_index >().indices().get< by_net_votes >(); + + auto itr = last_activity_idx.begin(); - auto itr = cashout_time_in_category.lower_bound( boost::make_tuple( category, fc::time_point_sec::maximum() ) ); + + if( start_permlink.size() ) { + const auto& start_comment = my->_db.get_comment( start_parent_author, start_permlink ); + itr = last_activity_idx.iterator_to( start_comment.stats(my->_db) ); + } + else if( start_parent_author.size() ) { + const auto& start_author = my->_db.get_account( start_parent_author ); + itr = last_activity_idx.lower_bound( boost::make_tuple( start_author.get_id(), std::numeric_limits::max(), object_id_type() ) ); + } + + while( itr != last_activity_idx.end() && result.size() < limit ) { + const auto& c = itr->comment_id(my->_db); + result.push_back( c ); + set_pending_payout(result.back()); + result.back().active_votes = get_active_votes( c.author, c.permlink ); + ++itr; + } + */ + return result; +} + + +vector database_api::get_discussions_in_category_by_votes( string category, string start_auth, string start_permlink, uint32_t limit )const { + + vector result; + return result; +} + +vector database_api::get_discussions_in_category_by_last_active( string category, string start_auth, string start_permlink, uint32_t limit )const { + const auto& last_activity_in_category = my->_db.get_index_type< comment_index >().indices().get< by_active_in_category >(); + + auto itr = last_activity_in_category.lower_bound( boost::make_tuple( "", category, fc::time_point_sec::maximum() ) ); if( start_auth.size() ) - itr = cashout_time_in_category.iterator_to( my->_db.get_comment( start_auth, start_permlink ) ); + itr = last_activity_in_category.iterator_to( my->_db.get_comment( start_auth, start_permlink ) ); - while( itr != cashout_time_in_category.end() && + vector result; + while( itr != last_activity_in_category.end() && itr->parent_permlink == category && - result.size() < limit ) + !itr->parent_author.size() + && result.size() < limit ) { result.push_back( *itr ); set_pending_payout(result.back()); @@ -882,7 +930,26 @@ vector database_api::get_discussions_in_category_by_cashout_time( st return result; } -vector database_api::get_discussions_in_category_by_last_update( string category, string start_auth, string start_permlink, uint32_t limit )const { + +vector database_api::get_discussions_by_created( string start_auth, string start_permlink, uint32_t limit )const { + const auto& last_update_idx = my->_db.get_index_type< comment_index >().indices().get< by_last_update >(); + + auto itr = last_update_idx.begin(); + + if( start_auth.size() ) + itr = last_update_idx.iterator_to( my->_db.get_comment( start_auth, start_permlink ) ); + + vector result; + while( itr != last_update_idx.end() && result.size() < limit && !itr->parent_author.size() ) { + result.push_back( *itr ); + set_pending_payout(result.back()); + result.back().active_votes = get_active_votes( itr->author, itr->permlink ); + ++itr; + } + return result; +} + +vector database_api::get_discussions_in_category_by_created( string category, string start_auth, string start_permlink, uint32_t limit )const { const auto& last_update_in_category = my->_db.get_index_type< comment_index >().indices().get< by_last_update_in_category >(); auto itr = last_update_in_category.lower_bound( boost::make_tuple( "", category, fc::time_point_sec::maximum() ) ); @@ -904,19 +971,38 @@ vector database_api::get_discussions_in_category_by_last_update( str return result; } -vector database_api::get_discussions_in_category_by_created( string category, string start_auth, string start_permlink, uint32_t limit )const { - const auto& last_update_in_category = my->_db.get_index_type< comment_index >().indices().get< by_created_in_category >(); - auto itr = last_update_in_category.lower_bound( boost::make_tuple( "", category, fc::time_point_sec::maximum() ) ); + +vector database_api::get_discussions_by_cashout_time( string start_auth, string start_permlink, uint32_t limit )const { + const auto& cashout_time_idx = my->_db.get_index_type< comment_index >().indices().get< by_cashout_time >(); + + auto itr = cashout_time_idx.begin(); if( start_auth.size() ) - itr = last_update_in_category.iterator_to( my->_db.get_comment( start_auth, start_permlink ) ); + itr = cashout_time_idx.iterator_to( my->_db.get_comment( start_auth, start_permlink ) ); vector result; - while( itr != last_update_in_category.end() && + while( itr != cashout_time_idx.end() && result.size() < limit ) { + idump((*itr)); + result.push_back( *itr ); + set_pending_payout(result.back()); + result.back().active_votes = get_active_votes( itr->author, itr->permlink ); + ++itr; + } + return result; +} +vector database_api::get_discussions_in_category_by_cashout_time( string category, string start_auth, string start_permlink, uint32_t limit )const { + vector result; + const auto& cashout_time_in_category = my->_db.get_index_type< comment_index >().indices().get< by_cashout_time_in_category >(); + + auto itr = cashout_time_in_category.lower_bound( boost::make_tuple( category, fc::time_point_sec::maximum() ) ); + + if( start_auth.size() ) + itr = cashout_time_in_category.iterator_to( my->_db.get_comment( start_auth, start_permlink ) ); + + while( itr != cashout_time_in_category.end() && itr->parent_permlink == category && - !itr->parent_author.size() - && result.size() < limit ) + result.size() < limit ) { result.push_back( *itr ); set_pending_payout(result.back()); @@ -926,6 +1012,8 @@ vector database_api::get_discussions_in_category_by_created( string return result; } + + vector database_api::get_discussions_by_total_pending_payout( string start_auth, string start_permlink, uint32_t limit )const { const auto& total_pending_payout_idx = my->_db.get_index_type< comment_index >().indices().get< by_total_pending_payout >(); @@ -1065,16 +1153,16 @@ vector database_api::get_active_witnesses()const { return wso.current_shuffled_witnesses; } -vector database_api::get_discussions_by_author_before_date( +vector database_api::get_discussions_by_author_before_date( string author, string start_permlink, time_point_sec before_date, uint32_t limit )const { try { vector result; FC_ASSERT( limit <= 100 ); int count = 0; - const auto& didx = my->_db.get_index_type().indices().get(); + const auto& didx = my->_db.get_index_type().indices().get(); - if( before_date == time_point_sec() ) + if( before_date == time_point_sec() ) before_date = time_point_sec::maximum(); auto itr = didx.lower_bound( boost::make_tuple( author, time_point_sec::maximum() ) ); @@ -1099,16 +1187,17 @@ vector database_api::get_discussions_by_author_before_date( state database_api::get_state( string path )const { + state _state; + _state.props = get_dynamic_global_properties(); + _state.current_route = path; + + try { if( path.size() && path[0] == '/' ) path = path.substr(1); /// remove '/' from front if( !path.size() ) path = "trending"; - state _state; - _state.props = get_dynamic_global_properties(); - _state.current_route = path; - /// FETCH CATEGORY STATE auto trending_cat = get_trending_categories( "", 100 ); for( const auto& c : trending_cat ) @@ -1185,7 +1274,6 @@ state database_api::get_state( string path )const } } else if( part[1] == "recent-replies" ) { auto replies = get_discussions_by_last_update( acnt, "", 50 ); - edump((replies)); eacnt.recent_replies = vector(); for( const auto& reply : replies ) { auto reply_ref = reply.author+"/"+reply.permlink; @@ -1194,7 +1282,7 @@ state database_api::get_state( string path )const } } else if( part[1] == "posts" ) { int count = 0; - const auto& pidx = my->_db.get_index_type().indices().get(); + const auto& pidx = my->_db.get_index_type().indices().get(); auto itr = pidx.lower_bound( boost::make_tuple(acnt, time_point_sec::maximum() ) ); eacnt.posts = vector(); while( itr != pidx.end() && itr->author == acnt && count < 100 ) { @@ -1204,19 +1292,6 @@ state database_api::get_state( string path )const ++count; } } else if( part[1].size() == 0 || part[1] == "blog" ) { - if( part[2].size() ) { - int count = 0; - const auto& pidx = my->_db.get_index_type().indices().get(); - auto itr = pidx.lower_bound( boost::make_tuple(acnt, std::string(""), part[2], time_point_sec::maximum() ) ); - while( itr != pidx.end() && itr->author == acnt && count < 100 && !itr->parent_author.size() ) { - eacnt.blog_category[part[2]].push_back(itr->permlink); - _state.content[acnt+"/"+itr->permlink] = *itr; - set_pending_payout( _state.content[acnt+"/"+itr->permlink] ); - ++itr; - ++count; - } - } - else { int count = 0; const auto& pidx = my->_db.get_index_type().indices().get(); auto itr = pidx.lower_bound( boost::make_tuple(acnt, std::string(""), time_point_sec::maximum() ) ); @@ -1228,7 +1303,6 @@ state database_api::get_state( string path )const ++itr; ++count; } - } } } /// pull a complete discussion @@ -1312,6 +1386,25 @@ state database_api::get_state( string path )const _state.content[key] = std::move(d); } } + else if( part[0] == "active" && part[1] == "") { + auto trending_disc = get_discussions_by_last_active( "", "", 20 ); + auto& didx = _state.discussion_idx[""]; + for( const auto& d : trending_disc ) { + auto key = d.author +"/" + d.permlink; + didx.active.push_back( key ); + accounts.insert(d.author); + _state.content[key] = std::move(d); + } + } else if( part[0] == "active" ) { + auto trending_disc = get_discussions_in_category_by_last_active( part[1], "", "", 20 ); + auto& didx = _state.discussion_idx[part[1]]; + for( const auto& d : trending_disc ) { + auto key = d.author +"/" + d.permlink; + didx.active.push_back( key ); + accounts.insert(d.author); + _state.content[key] = std::move(d); + } + } for( const auto& a : accounts ) { @@ -1324,7 +1417,10 @@ state database_api::get_state( string path )const _state.witness_schedule = my->_db.get_witness_schedule_object(); - return _state; + } catch ( const fc::exception& e ) { + _state.error = e.to_detail_string(); + } + return _state; } annotated_signed_transaction database_api::get_transaction( transaction_id_type id )const { diff --git a/libraries/app/include/steemit/app/database_api.hpp b/libraries/app/include/steemit/app/database_api.hpp index 44af36e85e..8ab2afdcaa 100755 --- a/libraries/app/include/steemit/app/database_api.hpp +++ b/libraries/app/include/steemit/app/database_api.hpp @@ -303,6 +303,12 @@ class database_api vector get_discussions_by_last_update( string start_author, string start_permlink, uint32_t limit )const; vector get_discussions_in_category_by_last_update( string category, string start_author, string start_permlink, uint32_t limit )const; + vector get_discussions_by_last_active( string start_author, string start_permlink, uint32_t limit )const; + vector get_discussions_in_category_by_last_active( string category, string start_author, string start_permlink, uint32_t limit )const; + + vector get_discussions_by_votes( string start_author, string start_permlink, uint32_t limit )const; + vector get_discussions_in_category_by_votes( string category, string start_author, string start_permlink, uint32_t limit )const; + vector get_discussions_by_created( string start_author, string start_permlink, uint32_t limit )const; vector get_discussions_in_category_by_created( string category, string start_author, string start_permlink, uint32_t limit )const; @@ -312,7 +318,7 @@ class database_api /** * This method is used to fetch all posts/comments by start_author that occur after before_date and start_permlink with up to limit being returned. * - * If start_permlink is empty then only before_date will be considered. If both are specified the eariler to the two metrics will be used. This + * If start_permlink is empty then only before_date will be considered. If both are specified the eariler to the two metrics will be used. This * should allow easy pagination. */ vector get_discussions_by_author_before_date( string author, string start_permlink, time_point_sec before_date, uint32_t limit )const; @@ -400,10 +406,17 @@ FC_API(steemit::app::database_api, (get_discussions_by_total_pending_payout) (get_discussions_in_category_by_total_pending_payout) (get_discussions_by_last_update) + (get_discussions_by_last_active) + (get_discussions_by_votes) (get_discussions_by_created) (get_discussions_in_category_by_last_update) + (get_discussions_in_category_by_last_active) + (get_discussions_in_category_by_votes) (get_discussions_in_category_by_created) (get_discussions_by_author_before_date) + (get_discussions_by_cashout_time) + (get_discussions_in_category_by_cashout_time) + // Witnesses (get_witnesses) diff --git a/libraries/app/include/steemit/app/state.hpp b/libraries/app/include/steemit/app/state.hpp index c22f6e2dde..c32d0e82bd 100644 --- a/libraries/app/include/steemit/app/state.hpp +++ b/libraries/app/include/steemit/app/state.hpp @@ -13,6 +13,7 @@ namespace steemit { namespace app { vector trending; /// pending lifetime payout vector recent; /// creation date vector active; /// last update or reply + vector votes; /// last update or reply vector maturing; /// about to be paid out vector best; /// total lifetime payout }; @@ -132,6 +133,7 @@ namespace steemit { namespace app { vector pow_queue; map witnesses; witness_schedule_object witness_schedule; + string error; }; } } @@ -145,7 +147,8 @@ FC_REFLECT_DERIVED( steemit::app::extended_account, FC_REFLECT( steemit::app::vote_state, (voter)(weight) ); FC_REFLECT( steemit::app::account_vote, (authorperm)(weight) ); -FC_REFLECT( steemit::app::discussion_index, (category)(trending)(recent)(active)(maturing)(best) ) +FC_REFLECT( steemit::app::discussion_index, (category)(trending)(recent)(active)(votes)(maturing)(best) ) FC_REFLECT( steemit::app::category_index, (trending)(active)(recent)(best) ) -FC_REFLECT( steemit::app::state, (current_route)(props)(category_idx)(categories)(content)(accounts)(pow_queue)(witnesses)(discussion_idx)(witness_schedule) ) FC_REFLECT_DERIVED( steemit::app::discussion, (steemit::chain::comment_object), (url)(root_title)(pending_payout_value)(total_pending_payout_value)(active_votes)(replies) ) + +FC_REFLECT( steemit::app::state, (current_route)(props)(category_idx)(categories)(content)(accounts)(pow_queue)(witnesses)(discussion_idx)(witness_schedule)(error) ) diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index 46145aa887..d7f0a60b36 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -826,13 +826,12 @@ try { adjust_balance( to_account, sbd ); adjust_supply( -steem ); adjust_supply( sbd ); - return sbd; } else { adjust_balance( to_account, steem ); return steem; } -} FC_CAPTURE_AND_RETHROW( (to_account.name)(steem) ) } +} FC_CAPTURE_LOG_AND_RETHROW( (to_account.name)(steem) ) } /** * @param to_account - the account to receive the new vesting shares @@ -1262,6 +1261,8 @@ void database::adjust_rshares2( const comment_object& c, fc::uint128_t old_rshar modify( c, [&](comment_object& comment ){ comment.children_rshares2 -= old_rshares2; comment.children_rshares2 += new_rshares2; + if( new_rshares2 > old_rshares2 ) /// down't update active index for down votes + comment.active = head_block_time(); }); if( c.depth ) { adjust_rshares2( get_comment( c.parent_author, c.parent_permlink ), old_rshares2, new_rshares2 ); @@ -1367,11 +1368,12 @@ void database::cashout_comment_helper( const comment_object& cur, const comment_ sbd_created.amount *= 2; /// 50/50 VESTING SBD means total value is 2x cashout_comment_helper( parent, origin, parent_vesting_steem_reward, parent_sbd_reward ); - } else { /// parent gets the full change, recursion stops - - const auto& parent_author = get_account(cur.parent_author); - vest_created = create_vesting( parent_author, vesting_steem_reward ); - sbd_created = create_sbd( parent_author, sbd_reward ); + } + else /// parent gets the full change, recursion stops + { + const auto& parent_author = get_account( cur.parent_author ); + vest_created = create_vesting( parent_author, parent_vesting_steem_reward ); + sbd_created = create_sbd( parent_author, parent_sbd_reward ); /// THE FOLLOWING IS NOT REQUIRED FOR VALIDATION push_applied_operation( comment_reward_operation( cur.parent_author, cur.parent_permlink, origin.author, origin.permlink, sbd_created, vest_created ) ); @@ -1398,11 +1400,10 @@ share_type database::pay_curators( const comment_object& c, share_type max_rewar { u256 total_weight( c.total_vote_weight ); share_type unclaimed_rewards = max_rewards; - const auto& cvidx = get_index_type().indices().get(); - auto itr = cvidx.lower_bound( boost::make_tuple( c.id, uint64_t(-1), account_id_type() ) ); - auto end = cvidx.lower_bound( boost::make_tuple( c.id, uint64_t(0), account_id_type() ) ); - while( itr != end ) { + auto itr = cvidx.lower_bound( c.id ); + auto start = itr; + while( itr != cvidx.end() && itr->comment == c.id ) { // TODO: Add minimum curation pay limit u256 weight( itr->weight ); auto claim = static_cast((max_rewards.value * weight) / total_weight); @@ -1419,7 +1420,6 @@ share_type database::pay_curators( const comment_object& c, share_type max_rewar { p.total_reward_fund_steem += unclaimed_rewards; }); - return unclaimed_rewards; } @@ -1434,8 +1434,8 @@ void database::process_comment_cashout() { const auto& cidx = get_index_type().indices().get(); auto current = cidx.begin(); - //auto end = cidx.lower_bound( head_block_time() ); while( current != cidx.end() && current->cashout_time <= head_block_time() ) { + share_type unclaimed; const auto& cur = *current; ++current; asset sbd_created(0,SBD_SYMBOL); asset vest_created(0,VESTS_SYMBOL); @@ -1451,12 +1451,12 @@ void database::process_comment_cashout() { auto to_sbd = reward_tokens / 2; auto to_vesting = reward_tokens - to_sbd; + unclaimed = pay_curators( cur, curator_rewards ); + cashout_comment_helper( cur, cur, asset( to_vesting, STEEM_SYMBOL ), asset( to_sbd, STEEM_SYMBOL ) ); + modify( cat, [&]( category_object& c ) { - c.total_payouts += asset(reward_tokens,STEEM_SYMBOL) * median_price; + c.total_payouts += asset(reward_tokens - unclaimed ,STEEM_SYMBOL) * median_price; }); - - pay_curators( cur, curator_rewards ); - cashout_comment_helper( cur, cur, asset( to_vesting, STEEM_SYMBOL ), asset( to_sbd, STEEM_SYMBOL ) ); } fc::uint128_t old_rshares2(cur.net_rshares.value); old_rshares2 *= old_rshares2; @@ -2085,7 +2085,6 @@ void database::_apply_transaction(const signed_transaction& trx) for( const auto& op : trx.operations ) { try { apply_operation(eval_state, op); -// if( head_block_num() > 900000 ) validate_invariants(); ++_current_op_in_trx; } FC_CAPTURE_AND_RETHROW( (op) ); } @@ -2512,7 +2511,7 @@ void database::adjust_supply( const asset& delta, bool adjust_vesting ) { } case SBD_SYMBOL: props.current_sbd_supply += delta; - props.virtual_supply += delta * get_feed_history().current_median_history; + props.virtual_supply = props.current_sbd_supply * get_feed_history().current_median_history + props.current_supply; assert( props.current_sbd_supply.amount.value >= 0 ); break; default: @@ -2588,7 +2587,9 @@ void database::process_hardforks() switch( hardforks.last_hardfork + 1) { case STEEMIT_HARDFORK_0_1_0: + #ifndef IS_TEST_NET elog( "HARDFORK 1" ); + #endif perform_vesting_share_split( 1000000 ); #ifdef IS_TEST_NET { @@ -2602,19 +2603,27 @@ void database::process_hardforks() #endif break; case STEEMIT_HARDFORK_0_2_0: + #ifndef IS_TEST_NET elog( "HARDFORK 2" ); + #endif retally_witness_votes(); break; case STEEMIT_HARDFORK_0_3_0: + #ifndef IS_TEST_NET elog( "HARDFORK 3" ); + #endif retally_witness_votes(); break; case STEEMIT_HARDFORK_0_4_0: + #ifndef IS_TEST_NET elog( "HARDFORK 4" ); + #endif reset_virtual_schedule_time(); break; case STEEMIT_HARDFORK_0_5_0: + #ifndef IS_TEST_NET elog( "HARDFORK 5" ); + #endif break; default: break; @@ -2631,7 +2640,7 @@ void database::process_hardforks() } FC_CAPTURE_AND_RETHROW() } -bool database::has_hardfork( uint32_t hardfork ) +bool database::has_hardfork( uint32_t hardfork )const { return hardfork_property_id_type()( *this ).processed_hardforks.size() > hardfork; } @@ -2656,17 +2665,15 @@ void database::set_hardfork( uint32_t hardfork, bool process_now ) */ void database::validate_invariants()const { - const auto& db = *this; try { - // const auto& account_idx = get_index_type< account_index >().indices().get< by_id >(); const auto& account_idx = get_index_type().indices().get(); asset total_supply = asset( 0, STEEM_SYMBOL ); asset total_sbd = asset( 0, SBD_SYMBOL ); asset total_vesting = asset( 0, VESTS_SYMBOL ); share_type total_vsf_votes = share_type( 0 ); - auto gpo = db.get_dynamic_global_properties(); + auto gpo = get_dynamic_global_properties(); /// verify no witness has too many votes const auto& witness_idx = get_index_type< witness_index >().indices(); @@ -2685,7 +2692,7 @@ void database::validate_invariants()const itr->vesting_shares.amount ) ); } - const auto& convert_request_idx = db.get_index_type< convert_index >().indices(); + const auto& convert_request_idx = get_index_type< convert_index >().indices(); for( auto itr = convert_request_idx.begin(); itr != convert_request_idx.end(); itr++ ) { @@ -2697,7 +2704,7 @@ void database::validate_invariants()const FC_ASSERT( !"Encountered illegal symbol in convert_request_object" ); } - const auto& limit_order_idx = db.get_index_type< limit_order_index >().indices(); + const auto& limit_order_idx = get_index_type< limit_order_index >().indices(); for( auto itr = limit_order_idx.begin(); itr != limit_order_idx.end(); itr++ ) { @@ -2714,7 +2721,7 @@ void database::validate_invariants()const fc::uint128_t total_rshares2; fc::uint128_t total_children_rshares2; - const auto& comment_idx = db.get_index_type< comment_index >().indices(); + const auto& comment_idx = get_index_type< comment_index >().indices(); for( auto itr = comment_idx.begin(); itr != comment_idx.end(); itr++ ) { @@ -2736,11 +2743,11 @@ void database::validate_invariants()const FC_ASSERT( total_rshares2 == total_children_rshares2, "", ("total_rshares2", total_rshares2)("total_children_rshares2",total_children_rshares2)); FC_ASSERT( gpo.virtual_supply >= gpo.current_supply ); - if ( !db.get_feed_history().current_median_history.is_null() ) - FC_ASSERT( gpo.current_sbd_supply * db.get_feed_history().current_median_history + gpo.current_supply - == gpo.virtual_supply ); + if ( !get_feed_history().current_median_history.is_null() ) + FC_ASSERT( gpo.current_sbd_supply * get_feed_history().current_median_history + gpo.current_supply + == gpo.virtual_supply, "", ("gpo.current_sbd_supply",gpo.current_sbd_supply)("get_feed_history().current_median_history",get_feed_history().current_median_history)("gpo.current_supply",gpo.current_supply)("gpo.virtual_supply",gpo.virtual_supply) ); } - FC_CAPTURE_LOG_AND_RETHROW( (db.head_block_num()) ); + FC_CAPTURE_LOG_AND_RETHROW( (head_block_num()) ); } void database::perform_vesting_share_split( uint32_t magnitude ) @@ -2785,7 +2792,7 @@ void database::perform_vesting_share_split( uint32_t magnitude ) for( const auto& vote : vote_idx ) { modify( vote, [&]( comment_vote_object& cv ) { - cv.weight = ( cv.weight * magnitude ) * magnitude; + cv.weight = cv.weight * magnitude ; }); } diff --git a/libraries/chain/include/steemit/chain/comment_object.hpp b/libraries/chain/include/steemit/chain/comment_object.hpp index 1084c91ab4..605682fd0b 100644 --- a/libraries/chain/include/steemit/chain/comment_object.hpp +++ b/libraries/chain/include/steemit/chain/comment_object.hpp @@ -80,6 +80,7 @@ namespace steemit { namespace chain { string json_metadata = ""; time_point_sec last_update; time_point_sec created; + time_point_sec active; ///< the last time this post was "touched" by voting or reply uint8_t depth = 0; ///< used to track max nested depth uint32_t children = 0; ///< used to track the total number of children, grandchildren, etc... @@ -134,7 +135,10 @@ namespace steemit { namespace chain { static const uint8_t type_id = impl_comment_vote_object_type; account_id_type voter; comment_id_type comment; - uint64_t weight = 0; ///< defines the score this vote receives, used by vote payout calc. 0 if a negative vote. + uint64_t weight = 0; ///< defines the score this vote receives, used by vote payout calc. 0 if a negative vote or changed votes. + int64_t rshares = 0; ///< The number of rshares this vote is responsible for + int16_t vote_percent = 0; ///< The percent weight of the vote + time_point_sec last_update; ///< The time of the last update of the vote }; struct by_comment_voter; @@ -198,35 +202,21 @@ namespace steemit { namespace chain { - - struct by_permlink; + struct by_cashout_time; /// cashout_time + struct by_permlink; /// author, perm + struct by_active; /// parent_auth, active + struct by_active_in_category; /// parent_auth, parent_perm, active + struct by_cashout_time_in_category; /// parent_auth, cashout_time struct by_pending_payout; - struct by_payout; - struct by_created; - struct by_created_in_category; - struct by_parent_pending_payout; - struct by_parent_total_pending_payout; - struct by_parent_payout; - struct by_parent_date; - struct by_author_pending_payout; - struct by_author_payout; - struct by_author_date; - struct by_parent; - struct by_parent_created; - struct by_cashout; - struct by_parent_cashout; - struct by_pending_payout; /// rshares - struct by_parent_pending_payout; - struct by_payout; /// rshares - struct by_parent_payout; + struct by_pending_payout_in_category; struct by_total_pending_payout; struct by_total_pending_payout_in_category; - struct by_last_update; - struct by_last_update_in_category; - struct by_cashout_time; - struct by_cashout_time_in_category; - struct by_blog; /// author, parent, parent_author (aka topic), created (greater), permlink - struct by_blog_category; /// author, parent, parent_author (aka topic), created (greater), permlink + struct by_last_update; /// parent_auth, last_update + struct by_last_update_in_category; /// parent_auth, parent_perm, last_update + struct by_payout; /// parent_auth, last_update + struct by_payout_in_category; /// parent_auth, parent_perm, last_update + struct by_blog; + struct by_author_last_update; /** * @ingroup object_index @@ -234,6 +224,7 @@ namespace steemit { namespace chain { typedef multi_index_container< comment_object, indexed_by< + /// CONSENUSS INDICIES - used by evaluators ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, ordered_unique< tag< by_cashout_time >, composite_key< comment_object, @@ -241,63 +232,44 @@ namespace steemit { namespace chain { member< object, object_id_type, &object::id > > >, - ordered_unique< tag< by_cashout_time_in_category >, - composite_key< comment_object, - member< comment_object, string, &comment_object::category >, - member< comment_object, time_point_sec, &comment_object::cashout_time >, - member< object, object_id_type, &object::id > - >, - composite_key_compare< std::less< string >, std::less, std::less > - >, - ordered_unique< tag< by_permlink >, + ordered_unique< tag< by_permlink >, /// used by consensus to find posts referenced in ops composite_key< comment_object, member< comment_object, string, &comment_object::author >, member< comment_object, string, &comment_object::permlink > >, composite_key_compare< std::less< string >, std::less< string > > - >, - ordered_unique< tag< by_parent >, + > + +//#ifndef IS_LOW_MEM + , + ordered_unique< tag, composite_key< comment_object, - member< comment_object, string, &comment_object::parent_author >, - member< comment_object, string, &comment_object::parent_permlink >, + member< comment_object, string, &comment_object::parent_author >, /// parent author of "" is root topic + member< comment_object, time_point_sec, &comment_object::active >, member< object, object_id_type, &object::id > - > - >, - ordered_unique< tag< by_blog >, - composite_key< comment_object, - member< comment_object, string, &comment_object::author >, - member< comment_object, string, &comment_object::parent_author >, - member< comment_object, time_point_sec, &comment_object::created >, - member< comment_object, string, &comment_object::permlink > - >, - composite_key_compare< std::less< string >, std::less< string >, std::greater, std::less > - >, - ordered_unique< tag< by_blog_category >, - composite_key< comment_object, - member< comment_object, string, &comment_object::author >, - member< comment_object, string, &comment_object::parent_author >, - member< comment_object, string, &comment_object::parent_permlink >, - member< comment_object, time_point_sec, &comment_object::created >, - member< comment_object, string, &comment_object::permlink > >, - composite_key_compare< std::less< string >, std::less< string >, std::less< string >, std::greater, std::less > + composite_key_compare< std::less, std::greater, std::less > >, - ordered_unique< tag< by_parent_created >, + /// ACTIVE INDEX - used to find posts that have recently been touched by votes, edits or replies + ordered_unique< tag< by_active_in_category >, /// AKA - by_parent sorted by active composite_key< comment_object, member< comment_object, string, &comment_object::parent_author >, member< comment_object, string, &comment_object::parent_permlink >, - member< comment_object, time_point_sec, &comment_object::created >, + member< comment_object, time_point_sec, &comment_object::active >, member< object, object_id_type, &object::id > >, composite_key_compare< std::less< string >, std::less< string >, std::greater, std::less > >, - ordered_unique< tag< by_created >, + /// CASHOUT INDICIDES (NOT IN CONSENSUS) + ordered_unique< tag< by_cashout_time_in_category >, composite_key< comment_object, - member< comment_object, time_point_sec, &comment_object::created >, + member< comment_object, string, &comment_object::category >, + member< comment_object, time_point_sec, &comment_object::cashout_time >, member< object, object_id_type, &object::id > >, - composite_key_compare< std::greater, std::less > + composite_key_compare< std::less< string >, std::less, std::less > >, + /// PENDING PAYOUT relative to a parent ordered_unique< tag< by_pending_payout >, composite_key< comment_object, member< comment_object, string, &comment_object::parent_author >, @@ -306,21 +278,23 @@ namespace steemit { namespace chain { >, composite_key_compare< std::less, std::greater, std::less > >, - ordered_unique< tag< by_total_pending_payout >, + ordered_unique< tag< by_pending_payout_in_category >, composite_key< comment_object, - member< comment_object, string, &comment_object::parent_author >, /// parent author of "" is root topic - member< comment_object, fc::uint128_t, &comment_object::children_rshares2 >, + member< comment_object, string, &comment_object::parent_author >, + member< comment_object, string, &comment_object::parent_permlink >, + member< comment_object, share_type, &comment_object::net_rshares >, member< object, object_id_type, &object::id > >, - composite_key_compare< std::less, std::greater, std::less > + composite_key_compare< std::less< string >, std::less< string >, std::greater, std::less > >, - ordered_unique< tag, + /// TOTAL PENDING PAYOUT - this is the default TRENDING ORDER + ordered_unique< tag< by_total_pending_payout >, composite_key< comment_object, member< comment_object, string, &comment_object::parent_author >, /// parent author of "" is root topic - member< comment_object, time_point_sec, &comment_object::last_update >, + member< comment_object, fc::uint128_t, &comment_object::children_rshares2 >, member< object, object_id_type, &object::id > >, - composite_key_compare< std::less, std::greater, std::less > + composite_key_compare< std::less, std::greater, std::less > >, ordered_unique< tag< by_total_pending_payout_in_category >, composite_key< comment_object, @@ -331,42 +305,25 @@ namespace steemit { namespace chain { >, composite_key_compare< std::less, std::less, std::greater, std::less > >, - ordered_unique< tag< by_last_update_in_category >, + /// used to sort all posts by the last time they were edited + ordered_unique< tag, composite_key< comment_object, member< comment_object, string, &comment_object::parent_author >, /// parent author of "" is root topic - member< comment_object, string, &comment_object::parent_permlink >, /// permlink is the category member< comment_object, time_point_sec, &comment_object::last_update >, member< object, object_id_type, &object::id > >, - composite_key_compare< std::less, std::less, std::greater, std::less > + composite_key_compare< std::less, std::greater, std::less > >, - ordered_unique< tag< by_created_in_category >, + ordered_unique< tag< by_last_update_in_category >, composite_key< comment_object, member< comment_object, string, &comment_object::parent_author >, /// parent author of "" is root topic member< comment_object, string, &comment_object::parent_permlink >, /// permlink is the category - member< comment_object, time_point_sec, &comment_object::created >, + member< comment_object, time_point_sec, &comment_object::last_update >, member< object, object_id_type, &object::id > >, composite_key_compare< std::less, std::less, std::greater, std::less > >, - ordered_unique< tag< by_parent_pending_payout >, - composite_key< comment_object, - member< comment_object, string, &comment_object::parent_author >, - member< comment_object, string, &comment_object::parent_permlink >, - member< comment_object, share_type, &comment_object::net_rshares >, - member< object, object_id_type, &object::id > - >, - composite_key_compare< std::less< string >, std::less< string >, std::greater, std::less > - >, - ordered_unique< tag< by_parent_total_pending_payout >, - composite_key< comment_object, - member< comment_object, string, &comment_object::parent_author >, - member< comment_object, string, &comment_object::parent_permlink >, - member< comment_object, fc::uint128, &comment_object::children_rshares2 >, - member< object, object_id_type, &object::id > - >, - composite_key_compare< std::less< string >, std::less< string >, std::greater, std::less > - >, + /// posts with the high dollar value received ordered_unique< tag< by_payout >, composite_key< comment_object, member< comment_object, asset, &comment_object::total_payout_value >, @@ -374,7 +331,7 @@ namespace steemit { namespace chain { >, composite_key_compare< std::greater, std::less > >, - ordered_unique< tag< by_parent_payout >, + ordered_unique< tag< by_payout_in_category >, composite_key< comment_object, member< comment_object, string, &comment_object::parent_author >, member< comment_object, string, &comment_object::parent_permlink >, @@ -383,14 +340,26 @@ namespace steemit { namespace chain { >, composite_key_compare< std::less< string >, std::less< string >, std::greater, std::less > >, - ordered_unique< tag< by_author_date >, + /// used to find all top-level posts (blog posts) + ordered_unique< tag< by_blog >, composite_key< comment_object, member< comment_object, string, &comment_object::author >, + member< comment_object, string, &comment_object::parent_author >, member< comment_object, time_point_sec, &comment_object::created >, + member< comment_object, string, &comment_object::permlink > + >, + composite_key_compare< std::less< string >, std::less< string >, std::greater, std::less > + >, + /// used to find all posts by an author + ordered_unique< tag< by_author_last_update >, + composite_key< comment_object, + member< comment_object, string, &comment_object::author >, + member< comment_object, time_point_sec, &comment_object::last_update >, member< object, object_id_type, &object::id > >, composite_key_compare< std::less< string >, std::greater, std::less > > +// #endif /// IS_LOW_MEM > > comment_multi_index_type; @@ -403,12 +372,12 @@ namespace steemit { namespace chain { FC_REFLECT_DERIVED( steemit::chain::comment_object, (graphene::db::object), (author)(permlink) (category)(parent_author)(parent_permlink) - (title)(body)(json_metadata)(last_update)(created) + (title)(body)(json_metadata)(last_update)(created)(active) (depth)(children)(children_rshares2) (net_rshares)(abs_rshares)(cashout_time)(total_vote_weight)(total_payout_value)(stats) ) FC_REFLECT_DERIVED( steemit::chain::comment_vote_object, (graphene::db::object), - (voter)(comment)(weight) ) + (voter)(comment)(weight)(rshares)(vote_percent)(last_update) ) FC_REFLECT_DERIVED( steemit::chain::category_object, (graphene::db::object), (name)(abs_rshares)(total_payouts)(discussions)(last_update) ); diff --git a/libraries/chain/include/steemit/chain/config.hpp b/libraries/chain/include/steemit/chain/config.hpp index ec19d6101c..f0f992a9ed 100644 --- a/libraries/chain/include/steemit/chain/config.hpp +++ b/libraries/chain/include/steemit/chain/config.hpp @@ -20,6 +20,7 @@ #define STEEMIT_MINING_TIME (fc::time_point_sec(1451606400)) #define STEEMIT_FIRST_CASHOUT_TIME (fc::time_point_sec(1451606400)) #define STEEMIT_CASHOUT_WINDOW_SECONDS (60*60) /// 1 hr +#define STEEMIT_VOTE_CHANGE_LOCKOUT_PERIOD (60*10) /// 10 minutes #define STEEMIT_ORIGINAL_MIN_ACCOUNT_CREATION_FEE 0 #define STEEMIT_MIN_ACCOUNT_CREATION_FEE 0 @@ -40,6 +41,7 @@ #define STEEMIT_MINING_TIME (fc::time_point_sec(1458838800)) #define STEEMIT_FIRST_CASHOUT_TIME (fc::time_point_sec(1467590400)) /// July 4th #define STEEMIT_CASHOUT_WINDOW_SECONDS (60*60*24) /// 1 day +#define STEEMIT_VOTE_CHANGE_LOCKOUT_PERIOD (60*60*2) /// 2 hours #define STEEMIT_ORIGINAL_MIN_ACCOUNT_CREATION_FEE 100000 #define STEEMIT_MIN_ACCOUNT_CREATION_FEE 1 diff --git a/libraries/chain/include/steemit/chain/database.hpp b/libraries/chain/include/steemit/chain/database.hpp index 40c6242822..3bf15feace 100644 --- a/libraries/chain/include/steemit/chain/database.hpp +++ b/libraries/chain/include/steemit/chain/database.hpp @@ -334,7 +334,7 @@ namespace steemit { namespace chain { void perform_vesting_share_split( uint32_t magnitude ); void retally_witness_votes(); - bool has_hardfork( uint32_t hardfork ); + bool has_hardfork( uint32_t hardfork )const; /* For testing and debugging only. Given a hardfork with id N, applies all hardforks with id <= N */ diff --git a/libraries/chain/protocol/asset.cpp b/libraries/chain/protocol/asset.cpp index b3e3d52e26..dce48e334c 100644 --- a/libraries/chain/protocol/asset.cpp +++ b/libraries/chain/protocol/asset.cpp @@ -127,15 +127,14 @@ namespace steemit { namespace chain { { FC_ASSERT( b.base.amount.value > 0 ); uint128_t result = (uint128_t(a.amount.value) * b.quote.amount.value)/b.base.amount.value; - FC_ASSERT( result <= uint64_t(-1) ); + FC_ASSERT( result.hi == 0 ); return asset( result.to_uint64(), b.quote.symbol ); } else if( a.symbol_name() == b.quote.symbol_name() ) { FC_ASSERT( b.quote.amount.value > 0 ); uint128_t result = (uint128_t(a.amount.value) * b.base.amount.value)/b.quote.amount.value; - //FC_ASSERT( result <= STEEMIT_MAX_SHARE_SUPPLY, "${result}", ("result",result)("max",STEEMIT_MAX_SHARE_SUPPLY)("asset",a)("price",b) ); - FC_ASSERT( result <= uint64_t(-1) ); + FC_ASSERT( result.hi == 0 ); return asset( result.to_uint64(), b.base.symbol ); } FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset * price", ("asset",a)("price",b) ); diff --git a/libraries/chain/protocol/steem_operations.cpp b/libraries/chain/protocol/steem_operations.cpp index ebe673a1d7..130a8db383 100644 --- a/libraries/chain/protocol/steem_operations.cpp +++ b/libraries/chain/protocol/steem_operations.cpp @@ -61,7 +61,7 @@ namespace steemit { namespace chain { FC_ASSERT( is_valid_account_name( voter ), "Voter account name invalid" ); FC_ASSERT( is_valid_account_name( author ), "Author account name invalid" );\ FC_ASSERT( abs(weight) <= STEEMIT_100_PERCENT, "Weight is not a STEEMIT percentage" ); - FC_ASSERT( weight != 0, "Vote weight is 0" ); + //FC_ASSERT( weight != 0, "Vote weight is 0" ); } void transfer_operation::validate() const diff --git a/libraries/chain/steem_evaluator.cpp b/libraries/chain/steem_evaluator.cpp index 024bc16759..7cda736215 100644 --- a/libraries/chain/steem_evaluator.cpp +++ b/libraries/chain/steem_evaluator.cpp @@ -12,7 +12,7 @@ namespace steemit { namespace chain { using fc::uint128_t; -void inline validate_permlink( string permlink ) +inline void validate_permlink( const string& permlink ) { FC_ASSERT( permlink.size() > STEEMIT_MIN_PERMLINK_LENGTH && permlink.size() < STEEMIT_MAX_PERMLINK_LENGTH ); @@ -32,6 +32,50 @@ void inline validate_permlink( string permlink ) } } +/** + * Allow GROUP / TOPIC + */ +template< bool allow_slash > +void validate_permlink_0_5( const string& permlink ) +{ + FC_ASSERT( permlink.size() > STEEMIT_MIN_PERMLINK_LENGTH && permlink.size() < STEEMIT_MAX_PERMLINK_LENGTH ); + + int char_count = 0; + int slash_count = 0; + int after_slash = 0; + bool last_was_slash = false; + bool last_was_dash = false; + + for( auto c : permlink ) + { + switch( c ) + { + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': + case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '0': + case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + ++char_count; + last_was_dash = false; + if( allow_slash ) FC_ASSERT( !last_was_dash ); + ++after_slash; + break; + case '-': + if( allow_slash ) FC_ASSERT( char_count, "must have characters before -" ); + last_was_dash = true; + ++char_count; + break; + case '/': + FC_ASSERT( allow_slash && !slash_count && char_count ); + ++slash_count; + after_slash = 0; + break; + default: + FC_ASSERT( !"Invalid permlink character:", "${s}", ("s", std::string() + c ) ); + } + } + if( allow_slash ) FC_ASSERT( after_slash, "there must be charcters after - or /" ); +} + void witness_update_evaluator::do_apply( const witness_update_operation& o ) { db().get_account( o.owner ); // verify owner exists @@ -122,7 +166,7 @@ void account_update_evaluator::do_apply( const account_update_operation& o ) void comment_evaluator::do_apply( const comment_operation& o ) { try { - if( db().is_producing() || db().has_hardfork( STEEMIT_HARDFORK_0_5_0) ) + if( db().is_producing() || db().has_hardfork( STEEMIT_HARDFORK_0_5_0) ) FC_ASSERT( o.title.size() + o.body.size() + o.json_metadata.size(), "something should change" ); const auto& by_permlink_idx = db().get_index_type< comment_index >().indices().get< by_permlink >(); @@ -150,7 +194,12 @@ void comment_evaluator::do_apply( const comment_operation& o ) const auto& new_comment = db().create< comment_object >( [&]( comment_object& com ) { - if( db().has_hardfork( STEEMIT_HARDFORK_0_1_0 ) ) + if( db().has_hardfork( STEEMIT_HARDFORK_0_5_0 ) ) + { + validate_permlink_0_5( o.parent_permlink ); + validate_permlink_0_5( o.permlink ); + } + else if( db().has_hardfork( STEEMIT_HARDFORK_0_1_0 ) ) { validate_permlink( o.parent_permlink ); validate_permlink( o.permlink ); @@ -175,6 +224,7 @@ void comment_evaluator::do_apply( const comment_operation& o ) com.last_update = db().head_block_time(); com.created = com.last_update; com.cashout_time = com.last_update + fc::seconds(STEEMIT_CASHOUT_WINDOW_SECONDS); + com.active = com.last_update; #ifndef IS_LOW_MEM com.title = o.title; @@ -199,7 +249,7 @@ void comment_evaluator::do_apply( const comment_operation& o ) const auto& new_comment_stats = db().create( [&]( comment_stats_object& cso ){ cso.comment_id = new_comment.id; - if( parent ) + if( parent ) cso.parent_comment_id = parent->id; cso.category_id = cat->id; cso.author_id = auth.get_id(); @@ -211,9 +261,11 @@ void comment_evaluator::do_apply( const comment_operation& o ) /// this loop can be skiped for validate-only nodes as it is merely gathering stats for indicies #ifndef IS_LOW_MEM + auto now = db().head_block_time(); while( parent ) { db().modify( *parent, [&]( comment_object& p ){ p.children++; + p.active = now; }); if( parent->parent_author.size() ) parent = &db().get_comment( parent->parent_author, parent->parent_permlink ); @@ -241,6 +293,7 @@ void comment_evaluator::do_apply( const comment_operation& o ) } com.last_update = db().head_block_time(); + com.active = com.last_update; com.cashout_time = com.last_update + fc::seconds(STEEMIT_CASHOUT_WINDOW_SECONDS); @@ -278,7 +331,7 @@ void comment_evaluator::do_apply( const comment_operation& o ) } // end EDIT case -} FC_CAPTURE_LOG_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } void transfer_evaluator::do_apply( const transfer_operation& o ) { @@ -381,7 +434,7 @@ void withdraw_vesting_evaluator::do_apply( const withdraw_vesting_operation& o ) if( new_vesting_withdraw_rate.amount == 0 ) new_vesting_withdraw_rate.amount = 1; - if( db().is_producing() || db().has_hardfork( STEEMIT_HARDFORK_0_5_0 ) ) + if( db().is_producing() || db().has_hardfork( STEEMIT_HARDFORK_0_5_0 ) ) FC_ASSERT( account.vesting_withdraw_rate != new_vesting_withdraw_rate, "this operation would not change the vesting withdraw rate" ); a.vesting_withdraw_rate = new_vesting_withdraw_rate; @@ -507,96 +560,162 @@ void vote_evaluator::do_apply( const vote_operation& o ) const auto& comment = db().get_comment( o.author, o.permlink ); const auto& voter = db().get_account( o.voter ); + const auto& comment_vote_idx = db().get_index_type< comment_vote_index >().indices().get< by_comment_voter >(); + auto itr = comment_vote_idx.find( std::make_tuple( comment.id, voter.id ) ); - auto elapsed_seconds = (db().head_block_time() - voter.last_vote_time).to_seconds(); - auto regenerated_power = ((STEEMIT_100_PERCENT - voter.voting_power) * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS; - auto current_power = std::min( int64_t(voter.voting_power + regenerated_power), int64_t(STEEMIT_100_PERCENT) ); - FC_ASSERT( current_power > 0 ); + if( itr == comment_vote_idx.end() ) + { + FC_ASSERT( o.weight != 0, "Vote weight cannot be 0" ); + auto elapsed_seconds = (db().head_block_time() - voter.last_vote_time).to_seconds(); + auto regenerated_power = ((STEEMIT_100_PERCENT - voter.voting_power) * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS; + auto current_power = std::min( int64_t(voter.voting_power + regenerated_power), int64_t(STEEMIT_100_PERCENT) ); + FC_ASSERT( current_power > 0 ); - int64_t abs_weight = abs(o.weight); - auto used_power = (current_power * abs_weight) / STEEMIT_100_PERCENT; - used_power /= 20; /// a 100% vote means use 5% of voting power which should force users to spread their votes around over 20+ posts + int64_t abs_weight = abs(o.weight); + auto used_power = (current_power * abs_weight) / STEEMIT_100_PERCENT; + used_power /= 20; /// a 100% vote means use 5% of voting power which should force users to spread their votes around over 20+ posts - int64_t abs_rshares = ((uint128_t(voter.vesting_shares.amount.value) * used_power) / STEEMIT_100_PERCENT).to_uint64(); + int64_t abs_rshares = ((uint128_t(voter.vesting_shares.amount.value) * used_power) / STEEMIT_100_PERCENT).to_uint64(); - /// this is the rshares voting for or against the post - int64_t rshares = o.weight < 0 ? -abs_rshares : abs_rshares; + /// this is the rshares voting for or against the post + int64_t rshares = o.weight < 0 ? -abs_rshares : abs_rshares; - db().modify( voter, [&]( account_object& a ){ - a.voting_power = current_power - used_power; - a.last_vote_time = db().head_block_time(); - }); + db().modify( voter, [&]( account_object& a ){ + a.voting_power = current_power - used_power; + a.last_vote_time = db().head_block_time(); + }); + + /// if the current net_rshares is less than 0, the post is getting 0 rewards so it is not factored into total rshares^2 + fc::uint128_t old_rshares = std::max(comment.net_rshares.value, int64_t(0)); + auto old_abs_rshares = comment.abs_rshares.value; - /// if the current net_rshares is less than 0, the post is getting 0 rewards so it is not factored into total rshares^2 - fc::uint128_t old_rshares = std::max(comment.net_rshares.value, int64_t(0)); - auto old_abs_rshares = comment.abs_rshares.value; + fc::uint128_t cur_cashout_time_sec = comment.cashout_time.sec_since_epoch(); + fc::uint128_t new_cashout_time_sec = db().head_block_time().sec_since_epoch() + STEEMIT_CASHOUT_WINDOW_SECONDS; + auto avg_cashout_sec = (cur_cashout_time_sec * old_abs_rshares + new_cashout_time_sec * abs_rshares ) / (comment.abs_rshares.value + abs_rshares ); - fc::uint128_t cur_cashout_time_sec = comment.cashout_time.sec_since_epoch(); - fc::uint128_t new_cashout_time_sec = db().head_block_time().sec_since_epoch() + STEEMIT_CASHOUT_WINDOW_SECONDS; - auto avg_cashout_sec = (cur_cashout_time_sec * old_abs_rshares + new_cashout_time_sec * abs_rshares ) / (comment.abs_rshares.value + abs_rshares ); + FC_ASSERT( abs_rshares > 0 ); - FC_ASSERT( abs_rshares > 0 ); + db().modify( comment, [&]( comment_object& c ){ + c.net_rshares += rshares; + c.abs_rshares += abs_rshares; + c.cashout_time = fc::time_point_sec( ) + fc::seconds(avg_cashout_sec.to_uint64()); + }); + fc::uint128_t new_rshares = std::max( comment.net_rshares.value, int64_t(0)); - db().modify( comment, [&]( comment_object& c ){ - c.net_rshares += rshares; - c.abs_rshares += abs_rshares; - c.cashout_time = fc::time_point_sec( ) + fc::seconds(avg_cashout_sec.to_uint64()); - }); + /// square it + new_rshares *= new_rshares; + old_rshares *= old_rshares; - fc::uint128_t new_rshares = std::max( comment.net_rshares.value, int64_t(0)); + const auto& cat = db().get_category( comment.category ); + db().modify( cat, [&]( category_object& c ){ + c.abs_rshares += abs_rshares; + c.last_update = db().head_block_time(); + }); - /// square it - new_rshares *= new_rshares; - old_rshares *= old_rshares; + /** this verifies uniqueness of voter + * + * voter_weight / new_total_weight ==> % of total vote weight provided by voter + * percent^2 => used to create non-linear reward toward those who contribute a larger percentage + * + * voter_weight * percent^2 ==> used to keep rewards proportional to vote_weight (small voters shouldn't get larger rewards simply for being first) + * + * Simplify equation as: + * vote_weight * (voter_weight/new_total_weight)^2 + * vote_weight * (voter_weight^2 / new_total_weight^2) + * vote_weight^3 / new_total_weight^2 + * + * Since we know vote_weight is a 64 bit number and we know voter_weight^2/new_total_weight^2 is less than 1.0, + * we know the resulting number is a 64 bit number. + * + **/ + const auto& cvo = db().create( [&]( comment_vote_object& cv ){ + cv.voter = voter.id; + cv.comment = comment.id; + cv.rshares = rshares; + cv.vote_percent = o.weight; + cv.last_update = db().head_block_time(); + if( rshares > 0 ) { + u512 rshares3(rshares); + rshares3 = rshares3 * rshares3 * rshares3; + + u256 total2( comment.abs_rshares.value ); + total2 *= total2; + + cv.weight = static_cast( rshares3 / total2 ); + } else { + cv.weight = 0; + } + }); - const auto& cat = db().get_category( comment.category ); - db().modify( cat, [&]( category_object& c ){ - c.abs_rshares += abs_rshares; - c.last_update = db().head_block_time(); - }); + db().modify( comment, [&]( comment_object& c ){ + c.total_vote_weight += cvo.weight; + }); - /** this verifies uniqueness of voter - * - * voter_weight / new_total_weight ==> % of total vote weight provided by voter - * percent^2 => used to create non-linear reward toward those who contribute a larger percentage - * - * voter_weight * percent^2 ==> used to keep rewards proportional to vote_weight (small voters shouldn't get larger rewards simply for being first) - * - * Simplify equation as: - * vote_weight * (voter_weight/new_total_weight)^2 - * vote_weight * (voter_weight^2 / new_total_weight^2) - * vote_weight^3 / new_total_weight^2 - * - * Since we know vote_weight is a 64 bit number and we know voter_weight^2/new_total_weight^2 is less than 1.0, - * we know the resulting number is a 64 bit number. - * - **/ - const auto& cvo = db().create( [&]( comment_vote_object& cv ){ - cv.voter = voter.id; - cv.comment = comment.id; - if( rshares > 0 ) { - u512 rshares3(rshares); - rshares3 = rshares3 * rshares3 * rshares3; - - u256 total2( comment.abs_rshares.value ); - total2 *= total2; - - cv.weight = static_cast( rshares3 / total2 ); - } else { - cv.weight = 0; - } - }); + db().adjust_rshares2( comment, old_rshares, new_rshares ); + } + else + { + FC_ASSERT( db().has_hardfork( STEEMIT_HARDFORK_0_5_0 ), "Cannot change votes until hardfork 0_5_0" ); - db().modify( comment, [&]( comment_object& c ){ - c.total_vote_weight += cvo.weight; - }); + auto elapsed_seconds = (db().head_block_time() - voter.last_vote_time).to_seconds(); + auto regenerated_power = ((STEEMIT_100_PERCENT - voter.voting_power) * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS; + auto current_power = std::min( int64_t(voter.voting_power + regenerated_power), int64_t(STEEMIT_100_PERCENT) ); + FC_ASSERT( current_power > 0 ); + + int64_t abs_weight = abs(o.weight); + auto used_power = (current_power * abs_weight) / STEEMIT_100_PERCENT; + used_power /= 20; /// a 100% vote means use 5% of voting power which should force users to spread their votes around over 20+ posts + + int64_t abs_rshares = ((uint128_t(voter.vesting_shares.amount.value) * used_power) / STEEMIT_100_PERCENT).to_uint64(); - db().adjust_rshares2( comment, old_rshares, new_rshares ); + /// this is the rshares voting for or against the post + int64_t rshares = o.weight < 0 ? -abs_rshares : abs_rshares; -} FC_CAPTURE_LOG_AND_RETHROW( (o)) } + auto effective_cashout_time = std::max( STEEMIT_FIRST_CASHOUT_TIME, comment.cashout_time ); + + if( effective_cashout_time.sec_since_epoch() - db().head_block_time().sec_since_epoch() <= STEEMIT_VOTE_CHANGE_LOCKOUT_PERIOD ) + FC_ASSERT( itr->rshares > rshares, "Change of vote is within lockout period and increases net_rshares to comment." ); + + db().modify( voter, [&]( account_object& a ){ + a.voting_power = current_power - used_power; + a.last_vote_time = db().head_block_time(); + }); + + /// if the current net_rshares is less than 0, the post is getting 0 rewards so it is not factored into total rshares^2 + fc::uint128_t old_rshares = std::max(comment.net_rshares.value, int64_t(0)); + auto old_abs_rshares = comment.abs_rshares.value; + + fc::uint128_t cur_cashout_time_sec = comment.cashout_time.sec_since_epoch(); + fc::uint128_t new_cashout_time_sec = db().head_block_time().sec_since_epoch() + STEEMIT_CASHOUT_WINDOW_SECONDS; + auto avg_cashout_sec = (cur_cashout_time_sec * old_abs_rshares + new_cashout_time_sec * abs_rshares ) / (comment.abs_rshares.value + abs_rshares ); + + db().modify( comment, [&]( comment_object& c ) + { + c.net_rshares -= itr->rshares; + c.net_rshares += rshares; + c.abs_rshares += abs_rshares; + c.cashout_time = fc::time_point_sec( ) + fc::seconds(avg_cashout_sec.to_uint64()); + c.total_vote_weight -= itr->weight; + }); + + old_rshares *= old_rshares; + fc::uint128_t new_rshares = std::max( comment.net_rshares.value, int64_t(0)); + new_rshares *= new_rshares; + + db().modify( *itr, [&]( comment_vote_object& cv ) + { + cv.rshares = rshares; + cv.vote_percent = o.weight; + cv.last_update = db().head_block_time(); + cv.weight = 0; + }); + + db().adjust_rshares2( comment, old_rshares, new_rshares ); + } +} FC_CAPTURE_AND_RETHROW( (o)) } void custom_evaluator::do_apply( const custom_operation& o ){} diff --git a/libraries/plugins/debug_node/debug_node_api.cpp b/libraries/plugins/debug_node/debug_node_api.cpp index a866b339f2..10c181d03c 100644 --- a/libraries/plugins/debug_node/debug_node_api.cpp +++ b/libraries/plugins/debug_node/debug_node_api.cpp @@ -5,6 +5,7 @@ #include +#include #include #include #include @@ -23,9 +24,11 @@ class debug_node_api_impl public: debug_node_api_impl( steemit::app::application& _app ); - uint32_t debug_push_blocks( const std::string& src_filename, uint32_t count ); + uint32_t debug_push_blocks( const std::string& src_filename, uint32_t count, bool skip_validate_invariants ); uint32_t debug_generate_blocks( const std::string& debug_key, uint32_t count ); uint32_t debug_generate_blocks_until( const std::string& debug_key, const fc::time_point_sec& head_block_time, bool generate_sparsely ); + fc::optional< steemit::chain::signed_block > debug_pop_block(); + //void debug_push_block( const steemit::chain::signed_block& block ); 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 ); @@ -40,7 +43,7 @@ debug_node_api_impl::debug_node_api_impl( steemit::app::application& _app ) : ap {} -uint32_t debug_node_api_impl::debug_push_blocks( const std::string& src_filename, uint32_t count ) +uint32_t debug_node_api_impl::debug_push_blocks( const std::string& src_filename, uint32_t count, bool skip_validate_invariants ) { if( count == 0 ) return 0; @@ -50,9 +53,13 @@ uint32_t debug_node_api_impl::debug_push_blocks( const std::string& src_filename if( fc::is_directory( src_path ) ) { ilog( "Loading ${n} from block_database ${fn}", ("n", count)("fn", src_filename) ); + idump( (src_filename)(count)(skip_validate_invariants) ); steemit::chain::block_database bdb; bdb.open( src_path ); uint32_t first_block = db->head_block_num()+1; + uint32_t skip_flags = steemit::chain::database::skip_nothing; + if( skip_validate_invariants ) + skip_flags = skip_flags | steemit::chain::database::skip_validate_invariants; for( uint32_t i=0; i block = bdb.fetch_by_number( first_block+i ); @@ -63,7 +70,7 @@ uint32_t debug_node_api_impl::debug_push_blocks( const std::string& src_filename } try { - db->push_block( *block ); + db->push_block( *block, skip_flags ); } catch( const fc::exception& e ) { @@ -121,32 +128,34 @@ uint32_t debug_node_api_impl::debug_generate_blocks_until( const std::string& de if( generate_sparsely ) { auto new_slot = db->get_slot_at_time( head_block_time ); - if( new_slot <= 1 ) - new_blocks += debug_generate_blocks( debug_key, 1 ); - else - { - fc::optional debug_private_key = graphene::utilities::wif_to_key( debug_key ); - FC_ASSERT( debug_private_key.valid() ); - steemit::chain::public_key_type debug_public_key = debug_private_key->get_public_key(); - auto scheduled_witness_name = db->get_scheduled_witness( new_slot ); - auto scheduled_time = db->get_slot_time( new_slot ); - const auto& scheduled_witness = db->get_witness( scheduled_witness_name ); - steemit::chain::public_key_type scheduled_key = scheduled_witness.signing_key; + if( new_slot == 0 ) + return 0; - wlog( "scheduled key is: ${sk} dbg key is: ${dk}", ("sk", scheduled_key)("dk", debug_public_key) ); + std::shared_ptr< debug_node_plugin > debug_plugin = get_plugin(); + fc::optional debug_private_key = graphene::utilities::wif_to_key( debug_key ); + FC_ASSERT( debug_private_key.valid() ); + steemit::chain::public_key_type debug_public_key = debug_private_key->get_public_key(); - if( scheduled_key != debug_public_key ) - { - wlog( "Modified key for witness ${w}", ("w", scheduled_witness_name) ); - fc::mutable_variant_object update; - update("_action", "update")("id", scheduled_witness.id)("signing_key", debug_public_key); - get_plugin()->debug_update( update ); - } + std::string scheduled_witness_name = db->get_scheduled_witness( new_slot ); + fc::time_point_sec scheduled_time = db->get_slot_time( new_slot ); + const chain::witness_object& scheduled_witness = db->get_witness( scheduled_witness_name ); + steemit::chain::public_key_type scheduled_key = scheduled_witness.signing_key; + + wlog( "scheduled key is: ${sk} dbg key is: ${dk}", ("sk", scheduled_key)("dk", debug_public_key) ); - db->generate_block( scheduled_time, scheduled_witness_name, *debug_private_key, steemit::chain::database::skip_nothing ); - new_blocks++; + if( scheduled_key != debug_public_key ) + { + wlog( "Modified key for witness ${w}", ("w", scheduled_witness_name) ); + fc::mutable_variant_object update; + update("_action", "update")("id", scheduled_witness.id)("signing_key", debug_public_key); + debug_plugin->debug_update( update ); } + + db->generate_block( scheduled_time, scheduled_witness_name, *debug_private_key, steemit::chain::database::skip_nothing ); + new_blocks++; + + FC_ASSERT( head_block_time.sec_since_epoch() - db->head_block_time().sec_since_epoch() < STEEMIT_BLOCK_INTERVAL, "", ("desired_time", head_block_time)("db->head_block_time()",db->head_block_time()) ); } else { @@ -157,6 +166,17 @@ uint32_t debug_node_api_impl::debug_generate_blocks_until( const std::string& de return new_blocks; } +fc::optional< steemit::chain::signed_block > debug_node_api_impl::debug_pop_block() +{ + std::shared_ptr< steemit::chain::database > db = app.chain_database(); + return db->fetch_block_by_number( db->head_block_num() ); +} + +/*void debug_node_api_impl::debug_push_block( const steemit::chain::signed_block& block ) +{ + app.chain_database()->push_block( block ); +}*/ + void debug_node_api_impl::debug_update_object( const fc::variant_object& update ) { get_plugin()->debug_update( update ); @@ -199,9 +219,9 @@ debug_node_api::debug_node_api( steemit::app::application& app ) void debug_node_api::on_api_startup() {} -uint32_t debug_node_api::debug_push_blocks( std::string source_filename, uint32_t count ) +uint32_t debug_node_api::debug_push_blocks( std::string source_filename, uint32_t count, bool skip_validate_invariants ) { - return my->debug_push_blocks( source_filename, count ); + return my->debug_push_blocks( source_filename, count, skip_validate_invariants ); } uint32_t debug_node_api::debug_generate_blocks( std::string debug_key, uint32_t count ) @@ -214,6 +234,16 @@ uint32_t debug_node_api::debug_generate_blocks_until( std::string debug_key, fc: return my->debug_generate_blocks_until( debug_key, head_block_time, generate_sparsely ); } +fc::optional< steemit::chain::signed_block > debug_node_api::debug_pop_block() +{ + return my->debug_pop_block(); +} + +/*void debug_node_api::debug_push_block( steemit::chain::signed_block& block ) +{ + my->debug_push_block( block ); +}*/ + void debug_node_api::debug_update_object( fc::variant_object update ) { my->debug_update_object( update ); diff --git a/libraries/plugins/debug_node/include/steemit/plugins/debug_node/debug_node_api.hpp b/libraries/plugins/debug_node/include/steemit/plugins/debug_node/debug_node_api.hpp index f9faf2619d..3107a8c1d8 100644 --- a/libraries/plugins/debug_node/include/steemit/plugins/debug_node/debug_node_api.hpp +++ b/libraries/plugins/debug_node/include/steemit/plugins/debug_node/debug_node_api.hpp @@ -5,8 +5,11 @@ #include #include +#include #include +#include + namespace steemit { namespace app { class application; } } @@ -27,7 +30,7 @@ class debug_node_api /** * Push blocks from existing database. */ - uint32_t debug_push_blocks( std::string src_filename, uint32_t count ); + uint32_t debug_push_blocks( std::string src_filename, uint32_t count, bool skip_validate_invariants = false ); /** * Generate blocks locally. @@ -39,6 +42,16 @@ class debug_node_api */ uint32_t debug_generate_blocks_until( std::string debug_key, fc::time_point_sec head_block_time, bool generate_sparsely = true ); + /* + * Pop a block from the blockchain, returning it + */ + fc::optional< steemit::chain::signed_block > debug_pop_block(); + + /* + * Push an already constructed block onto the blockchain. For use with pop_block to traverse state block by block. + */ + //void debug_push_block( steemit::chain::signed_block& block ); + /** * Directly manipulate database objects (will undo and re-apply last block with new changes post-applied). */ @@ -78,6 +91,8 @@ FC_API(steemit::plugin::debug_node::debug_node_api, (debug_push_blocks) (debug_generate_blocks) (debug_generate_blocks_until) + (debug_pop_block) + //(debug_push_block) (debug_update_object) (debug_stream_json_objects) (debug_stream_json_objects_flush) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 612cf63df1..6112ad0dbe 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -241,8 +241,7 @@ class wallet_api_impl : self( s ), _remote_api( rapi ), _remote_db( rapi->get_api_by_name("database_api")->as< database_api >() ), - _remote_net_broadcast( rapi->get_api_by_name("network_broadcast_api")->as< network_broadcast_api >() ), - _remote_message_api( rapi->get_api_by_name("private_message_api")->as< private_message_api >() ) + _remote_net_broadcast( rapi->get_api_by_name("network_broadcast_api")->as< network_broadcast_api >() ) { init_prototype_ops(); @@ -831,14 +830,26 @@ class wallet_api_impl } catch( const fc::exception& e ) { - std::cerr << "\nCouldn't get network node API. You probably are not configured\n" - "to access the network API on the witness_node you are\n" - "connecting to. Please follow the instructions in README.md to set up an apiaccess file.\n" - "\n"; + elog( "Couldn't get network node API" ); throw(e); } } + void use_remote_message_api() + { + if( _remote_message_api.valid() ) + return; + try + { + _remote_message_api = _remote_api->get_api_by_name("private_message_api")->as< private_message_api >(); + } + catch( const fc::exception& e ) + { + elog( "Couldn't get network node API" ); + throw(e); + } + } + void network_add_nodes( const vector& nodes ) { use_network_node_api(); @@ -879,8 +890,8 @@ class wallet_api_impl fc::api _remote_api; fc::api _remote_db; fc::api _remote_net_broadcast; - fc::api _remote_message_api; optional< fc::api > _remote_net_node; + optional< fc::api > _remote_message_api; flat_map _prototype_ops; @@ -1854,7 +1865,7 @@ message_body wallet_api::try_decrypt_message( const message_object& mo ) { vector wallet_api::get_inbox( string account, fc::time_point newest, uint32_t limit ) { FC_ASSERT( !is_locked() ); vector result; - auto remote_result = my->_remote_message_api->get_inbox( account, newest, limit ); + auto remote_result = (*my->_remote_message_api)->get_inbox( account, newest, limit ); for( const auto& item : remote_result ) { result.emplace_back( item ); result.back().message = try_decrypt_message( item ); @@ -1865,7 +1876,7 @@ vector wallet_api::get_inbox( string account, fc::tim vector wallet_api::get_outbox( string account, fc::time_point newest, uint32_t limit ) { FC_ASSERT( !is_locked() ); vector result; - auto remote_result = my->_remote_message_api->get_outbox( account, newest, limit ); + auto remote_result = (*my->_remote_message_api)->get_outbox( account, newest, limit ); for( const auto& item : remote_result ) { result.emplace_back( item ); result.back().message = try_decrypt_message( item ); diff --git a/python_scripts/steemdebugnode/debugnode.py b/python_scripts/steemdebugnode/debugnode.py index 3b78e46155..1b01990128 100644 --- a/python_scripts/steemdebugnode/debugnode.py +++ b/python_scripts/steemdebugnode/debugnode.py @@ -1,5 +1,7 @@ -import datetime, json, logging +import json, logging +from datetime import datetime +from datetime import timezone from os import devnull from pathlib import Path from signal import SIGINT, SIGTERM @@ -13,7 +15,7 @@ class DebugNode( object ): """ Wraps the steemd debug node plugin for easier automated testing of the Steem Network""" - def __init__( self, steemd, data_dir, plugins=[], apis=[], steemd_stdout=None, steemd_stderr=None ): + def __init__( self, steemd, data_dir, plugins=[], apis=[], steemd_out=None, steemd_err=None ): """ Creates a steemd debug node. It can be ran by using 'with debug_node:' @@ -56,15 +58,15 @@ def __init__( self, steemd, data_dir, plugins=[], apis=[], steemd_stdout=None, s self.apis = apis self._FNULL = open( devnull, 'w' ) - if( steemd_stdout != None ): - self.steemd_stdout = steemd_stdout + if( steemd_out != None ): + self.steemd_out = steemd_out else: - self.steemd_stdout = self._FNULL + self.steemd_out = self._FNULL - if( steemd_stderr != None ): - self.steemd_stderr = steemd_stderr + if( steemd_err != None ): + self.steemd_err = steemd_err else: - self.steemd_stderr = self._FNULL + self.steemd_err = self._FNULL self._debug_key = '5JHNbFNDg834SFj8CMArV6YW7td4zrPzXveqTfaShmYVuYNeK69' self._steemd_lock = Lock() @@ -79,11 +81,11 @@ def __enter__( self ): config.touch() config.write_text( self._get_config() ) - self._steemd_process = Popen( [ str( self._steemd_bin ), '--data-dir="' + str( self._temp_data_dir.name ) + '"' ], stdout=self._FNULL, stderr=self._FNULL ) + self._steemd_process = Popen( [ str( self._steemd_bin ), '--data-dir="' + str( self._temp_data_dir.name ) + '"' ], stdout=self.steemd_out, stderr=self.steemd_err ) self._steemd_process.poll() sleep( 5 ) if( not self._steemd_process.returncode ): - self._rpc = SteemNodeRPC( 'ws://127.0.0.1:8090', '', '' ) + self._rpc = SteemNodeRPC( 'ws://127.0.0.1:8095', '', '' ) else: raise Exception( "steemd did not start properly..." ) @@ -117,12 +119,12 @@ def __exit__( self, exc, value, tb ): def _get_config( self ): return "# no seed-node in config file or command line\n" \ + "p2p-endpoint = 127.0.0.1:2001 # bind to localhost to prevent remote p2p nodes from connecting to us\n" \ - + "rpc-endpoint = 127.0.0.1:8090 # bind to localhost to secure RPC API access\n" \ + + "rpc-endpoint = 127.0.0.1:8095 # bind to localhost to secure RPC API access\n" \ + "enable-plugin = witness debug_node " + " ".join( self.plugins ) + "\n" \ + "public-api = database_api login_api debug_node_api " + " ".join( self.apis ) + "\n" - def debug_push_blocks( self, count=0 ): + def debug_push_blocks( self, count=0, skip_validate_invariants=False ): """ Push count blocks from an existing chain. There is no guarantee pushing blocks will work depending on set hardforks, or generated blocks @@ -136,13 +138,17 @@ def debug_push_blocks( self, count=0 ): int: The number of blocks actually pushed. """ num_blocks = 0 + skip_validate_invariants_str = "false" + if( skip_validate_invariants ): + skip_validate_invariants_str = "true" + if( count == 0 ): ret = 10000 while( ret == 10000 ): - ret = self._rpc.rpcexec( json.loads( '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_push_blocks",["' + str( self._block_dir ) + '", 10000]], "id": 1}' ) ) + ret = self._rpc.rpcexec( json.loads( '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_push_blocks",["' + str( self._block_dir ) + '", 10000,"' + skip_validate_invariants_str + '"]], "id": 1}' ) ) num_blocks += ret else: - ret = self._rpc.rpcexec( json.loads( '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_push_blocks",["' + str( self._block_dir ) + '",' + str( count ) + ']], "id": 1}' ) ) + ret = self._rpc.rpcexec( json.loads( '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_push_blocks",["' + str( self._block_dir ) + '",' + str( count ) + ',"' + skip_validate_invariants_str + '"]], "id": 1}' ) ) num_blocks += ret return num_blocks @@ -167,7 +173,7 @@ def debug_generate_blocks( self, count ): return self._rpc.rpcexec( json.loads( '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_generate_blocks",["' + self._debug_key + '",' + str( count ) + ']], "id": 1}' ) ) - def debug_generate_blocks_until( self, time, generate_sparsely=True ): + def debug_generate_blocks_until( self, timestamp, generate_sparsely=True ): """ Generate block up until a head block time rather than a specific number of blocks. As with `debug_generate_blocks` all blocks will be empty unless there were pending transactions. @@ -177,7 +183,7 @@ def debug_generate_blocks_until( self, time, generate_sparsely=True ): `get_dev_key steem debug`. Do not use this key on the live chain for any reason. args: - time -- The desired new head block time. This is a UTC Timestmap. + time -- The desired new head block time. This is a POSIX Timestmap. generate_sparsely -- True if you wish to skip all intermediate blocks between the current head block time and the desired head block time. This is useful to trigger events, such as payouts and bandwidth updates, without generating blocks. However, many automatic chain @@ -188,9 +194,19 @@ def debug_generate_blocks_until( self, time, generate_sparsely=True ): (time, int): A tuple including the new head block time and the number of blocks that were generated. """ - if( not instance( time, datetime ) ): - raise ValueError( "Time must be a datetime object" ) - self._rpc.rpcexec( json.loads( '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_generate_blocks_until",["' + self._debug_key + '",' + str( time ) + ']], "id": 1}' ) ) + if( not isinstance( timestamp, int ) ): + raise ValueError( "Time must be a int" ) + generate_sparsely_str = "true" + if( not generate_sparsely ): + generate_sparsely_str = "false" + + iso_string = datetime.fromtimestamp( timestamp, timezone.utc ).isoformat().split( '+' )[0].split( '-' ) + if( len( iso_string ) == 4 ): + iso_string = iso_string[:-1] + iso_string = '-'.join( iso_string ) + + print( iso_string ) + return self._rpc.rpcexec( json.loads( '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_generate_blocks_until",["' + self._debug_key + '","' + iso_string + '","' + generate_sparsely_str + '"]], "id": 1}' ) ) def debug_set_hardfork( self, hardfork_id ): diff --git a/python_scripts/tests/debug_hardforks.py b/python_scripts/tests/debug_hardforks.py index 6691121dee..4383c80145 100644 --- a/python_scripts/tests/debug_hardforks.py +++ b/python_scripts/tests/debug_hardforks.py @@ -47,7 +47,7 @@ def main( ): if( not data_dir.is_dir() ): print( 'Error: data_dir is not a directory' ) - debug_node = DebugNode( str( steemd ), str( data_dir ) ) + debug_node = DebugNode( str( steemd ), str( data_dir ), steemd_err=sys.stderr ) with debug_node : @@ -62,8 +62,13 @@ def run_steemd_tests( debug_node ): from steemapi.steemnoderpc import SteemNodeRPC try: - print( "Playing blockchain..." ) - blocks = debug_node.debug_push_blocks( 0 ) + print( 'Replaying blocks...', ) + sys.stdout.flush() + total_blocks = 0 + while( total_blocks % 100000 == 0 ): + total_blocks += debug_node.debug_push_blocks( 100000, skip_validate_invariants=True ) + print( 'Blocks Replayed: ' + str( total_blocks ) ) + sys.stdout.flush() print( "Setting the hardfork now" ) # TODO: Grab most recent hardfork num from build directory sys.stdout.flush() @@ -77,7 +82,7 @@ def run_steemd_tests( debug_node ): sys.stdout.flush() rpc = SteemNodeRPC( 'ws://127.0.0.1:8090', '', '' ) block_producers = {} - for i in range( blocks + 1 , blocks + 5001 ): + for i in range( total_blocks + 1 , total_blocks + 5001 ): ret = rpc.rpcexec( json.loads( '{"jsonrpc": "2.0", "method": "call", "params": [0,"get_block",[' + str( i ) + ']], "id":4}' ) ) if( ret[ "witness" ] in block_producers ): block_producers[ ret[ "witness" ] ] += 1 diff --git a/python_scripts/tests/test_payouts.py b/python_scripts/tests/test_payouts.py new file mode 100644 index 0000000000..e88c1f8081 --- /dev/null +++ b/python_scripts/tests/test_payouts.py @@ -0,0 +1,110 @@ +""" +This test module will only run on a POSIX system. Windows support *may* be added at some point in the future. +""" +# Global imports +import json, operator, os, signal, sys + +from argparse import ArgumentParser +from pathlib import Path +from time import sleep + +# local imports +from steemdebugnode import DebugNode +from steemapi.steemnoderpc import SteemNodeRPC + +WAITING = True + +def main( ): + global WAITING + if( os.name != "posix" ): + print( "This script only works on POSIX systems" ) + return + + parser = ArgumentParser( description='Run a steemd debug node on an existing chain, trigger a hardfork' \ + ' and verify hardfork does not break invariants or block production' ) + parser.add_argument( '--steemd', '-s', type=str, required=True, help='The location of a steemd binary to run the debug node' ) + parser.add_argument( '--data-dir', '-d', type=str, required=True, help='The location of an existing data directory. ' + \ + 'The debug node will pull blocks from this directory when replaying the chain. The directory ' + \ + 'will not be changed.' ) + parser.add_argument( '--pause-node', '-p', type=bool, required=False, default=True, \ + help='True if the debug node should pause after it\'s tests. Default: false' ) + + args = parser.parse_args() + + steemd = Path( args.steemd ) + if( not steemd.exists() ): + print( 'Error: steemd does not exist.' ) + return + + steemd = steemd.resolve() + if( not steemd.is_file() ): + print( 'Error: steemd is not a file.' ) + return + + data_dir = Path( args.data_dir ) + if( not data_dir.exists() ): + print( 'Error: data_dir does not exist or is not a properly constructed steemd data directory' ) + + data_dir = data_dir.resolve() + if( not data_dir.is_dir() ): + print( 'Error: data_dir is not a directory' ) + + signal.signal( signal.SIGINT, sigint_handler ) + + debug_node = DebugNode( str( steemd ), str( data_dir ) ) + + with debug_node : + + run_steemd_tests( debug_node ) + + # Term on completion? + if( args.pause_node ): + print( "Letting the node hang for manual inspection..." ) + else: + WAITING = False + + while( WAITING ): + sleep( 1 ) + + +def run_steemd_tests( debug_node ): + from steemapi.steemnoderpc import SteemNodeRPC + + try: + print( 'Replaying blocks...', ) + sys.stdout.flush() + total_blocks = 0 + while( total_blocks % 100000 == 0 ): + total_blocks += debug_node.debug_push_blocks( 100000, skip_validate_invariants=True ) + print( 'Blocks Replayed: ' + str( total_blocks ) ) + sys.stdout.flush() + + print( "Triggering payouts" ) + sys.stdout.flush() + debug_node.debug_generate_blocks_until( 1467590400 ) + + print( "Generating blocks to verify nothing broke" ) + assert( debug_node.debug_generate_blocks( 10 ) == 10 ) + + print( "Done!" ) + print( "Getting comment dump:" ) + sys.stdout.flush() + rpc = SteemNodeRPC( 'ws://127.0.0.1:8095', '', '' ) + ret = rpc.get_discussions_by_cashout_time( '', '', str( 0xFFFFFFFF ) ); + + print( 'author, url, total_payout_value, abs_rshares, num_active_votes' ) + + for comment in ret: + print( comment[ 'author' ] + ', ' + comment[ 'url' ] + ', ' + comment[ 'total_payout_value' ] + ', ' + comment[ 'cashout_time' ] ) + + except ValueError as val_err: + print( str( val_err ) ) + + +def sigint_handler( signum, frame ): + global WAITING + WAITING = False + sleep( 3 ) + sys.exit( 0 ) + +main() \ No newline at end of file diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 8b3c37f919..fd72055da5 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -10,7 +10,7 @@ #define INITIAL_TEST_SUPPLY (10000000000ll) using namespace graphene::db; -extern uint32_t STEEMIT_TESTING_GENESIS_TIMESTAMP; +extern uint32_t ( STEEMIT_TESTING_GENESIS_TIMESTAMP ); #define PUSH_TX \ steemit::chain::test::_push_transaction diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 40128ca840..6c724289d5 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -534,8 +534,8 @@ BOOST_AUTO_TEST_CASE( comment_apply ) BOOST_REQUIRE_EQUAL( mod_sam_comment.parent_permlink, op.parent_permlink ); BOOST_REQUIRE( mod_sam_comment.last_update == db.head_block_time() ); BOOST_REQUIRE( mod_sam_comment.created == created ); - BOOST_REQUIRE_EQUAL( mod_sam_comment.net_rshares.value, 0 ); - BOOST_REQUIRE_EQUAL( mod_sam_comment.abs_rshares.value, 0 ); + //BOOST_REQUIRE_EQUAL( mod_sam_comment.net_rshares.value, 0 ); + //BOOST_REQUIRE_EQUAL( mod_sam_comment.abs_rshares.value, 0 ); BOOST_REQUIRE( mod_sam_comment.cashout_time == fc::time_point_sec( db.head_block_time() + fc::seconds( STEEMIT_CASHOUT_WINDOW_SECONDS ) ) ); validate_database(); @@ -667,24 +667,7 @@ BOOST_AUTO_TEST_CASE( vote_apply ) BOOST_REQUIRE( alice.last_vote_time == db.head_block_time() ); BOOST_REQUIRE_EQUAL( alice_comment.net_rshares.value, alice.vesting_shares.amount.value * ( old_voting_power - alice.voting_power ) / STEEMIT_100_PERCENT ); BOOST_REQUIRE( alice_comment.cashout_time == db.head_block_time() + fc::seconds( STEEMIT_CASHOUT_WINDOW_SECONDS ) ); - BOOST_REQUIRE( itr != vote_idx.end() ); - validate_database(); - - BOOST_TEST_MESSAGE( "--- Test preventing repeated voting" ); - op.weight = STEEMIT_100_PERCENT / 2; - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back( op ); - tx.sign( alice_private_key, db.get_chain_id() ); - - STEEMIT_REQUIRE_THROW( db.push_transaction( tx, 0 ), fc::assert_exception ); - - itr = vote_idx.find( std::make_tuple( alice_comment.id, alice.id ) ); - - BOOST_REQUIRE_EQUAL( alice.voting_power, old_voting_power - ( old_voting_power / 20 ) ); - BOOST_REQUIRE( alice.last_vote_time == db.head_block_time() ); - BOOST_REQUIRE_EQUAL( alice_comment.net_rshares.value, alice.vesting_shares.amount.value * ( old_voting_power - alice.voting_power ) / STEEMIT_100_PERCENT ); - BOOST_REQUIRE( alice_comment.cashout_time == db.head_block_time() + fc::seconds( STEEMIT_CASHOUT_WINDOW_SECONDS ) ); + BOOST_REQUIRE( itr->rshares == alice.vesting_shares.amount.value * ( old_voting_power - alice.voting_power ) / STEEMIT_100_PERCENT ); BOOST_REQUIRE( itr != vote_idx.end() ); validate_database(); @@ -816,6 +799,132 @@ BOOST_AUTO_TEST_CASE( vote_apply ) BOOST_REQUIRE( db.get_comment( "alice", "foo" ).children_rshares2 == db.get_comment( "sam", "foo" ).children_rshares2 + old_rshares2 ); validate_database(); + + BOOST_TEST_MESSAGE( "--- Test increasing vote rshares" ); + + auto new_alice = db.get_account( "alice" ); + auto alice_bob_vote = vote_idx.find( std::make_tuple( new_bob_comment.id, new_alice.id ) ); + auto old_vote_rshares = alice_bob_vote->rshares; + auto old_vote_weight = alice_bob_vote->weight; + auto old_net_rshares = new_bob_comment.net_rshares.value; + old_abs_rshares = new_bob_comment.abs_rshares.value; + auto old_total_vote_weight = new_bob_comment.total_vote_weight; + old_cashout_time = new_bob_comment.cashout_time.sec_since_epoch(); + auto alice_voting_power = new_alice.voting_power - ( STEEMIT_1_PERCENT * 25 * new_alice.voting_power ) / STEEMIT_100_PERCENT / 20; + int64_t new_rshares = ( ( fc::uint128_t( new_alice.voting_power - alice_voting_power ) * new_alice.vesting_shares.amount.value ) / STEEMIT_100_PERCENT ).to_uint64(); + + op.voter = "alice"; + op.weight = STEEMIT_1_PERCENT * 25; + op.author = "bob"; + op.permlink = "foo"; + tx.operations.clear(); + tx.signatures.clear(); + tx.operations.push_back( op ); + tx.sign( alice_private_key, db.get_chain_id() ); + db.push_transaction( tx, 0 ); + alice_bob_vote = vote_idx.find( std::make_tuple( new_bob_comment.id, new_alice.id ) ); + + BOOST_REQUIRE( new_bob_comment.net_rshares == old_net_rshares - old_vote_rshares + new_rshares ); + BOOST_REQUIRE( new_bob_comment.abs_rshares == old_abs_rshares + new_rshares ); + BOOST_REQUIRE( new_bob_comment.total_vote_weight == old_total_vote_weight - old_vote_weight ); + BOOST_REQUIRE( new_bob_comment.cashout_time == fc::time_point_sec( ( ( old_cashout_time * old_abs_rshares + ( db.head_block_time().sec_since_epoch() + STEEMIT_CASHOUT_WINDOW_SECONDS ) * new_rshares ) / ( old_abs_rshares + new_rshares ) ).to_uint64() ) ); + BOOST_REQUIRE( alice_bob_vote->weight == 0 ); + BOOST_REQUIRE( alice_bob_vote->rshares == new_rshares ); + BOOST_REQUIRE( alice_bob_vote->last_update == db.head_block_time() ); + BOOST_REQUIRE( alice_bob_vote->vote_percent == op.weight ); + BOOST_REQUIRE( db.get_account( "alice" ).voting_power == alice_voting_power ); + validate_database(); + + BOOST_TEST_MESSAGE( "--- Test decreasing vote rshares" ); + + old_vote_rshares = new_rshares; + old_net_rshares = new_bob_comment.net_rshares.value; + old_abs_rshares = new_bob_comment.abs_rshares.value; + old_total_vote_weight = new_bob_comment.total_vote_weight; + old_cashout_time = new_bob_comment.cashout_time.sec_since_epoch(); + int64_t used_power = ( int64_t( STEEMIT_1_PERCENT ) * 75 * int64_t( alice_voting_power ) ) / STEEMIT_100_PERCENT; + used_power /= 20; + alice_voting_power -= used_power; + new_rshares = ( ( used_power * fc::uint128_t( new_alice.vesting_shares.amount.value ) ) / STEEMIT_100_PERCENT ).to_uint64(); + + op.weight = STEEMIT_1_PERCENT * -75; + tx.operations.clear(); + tx.signatures.clear(); + tx.operations.push_back( op ); + tx.sign( alice_private_key, db.get_chain_id() ); + db.push_transaction( tx, 0 ); + alice_bob_vote = vote_idx.find( std::make_tuple( new_bob_comment.id, new_alice.id ) ); + + BOOST_REQUIRE( new_bob_comment.net_rshares == old_net_rshares - old_vote_rshares - new_rshares ); + BOOST_REQUIRE( new_bob_comment.abs_rshares == old_abs_rshares + new_rshares ); + BOOST_REQUIRE( new_bob_comment.total_vote_weight == old_total_vote_weight ); + BOOST_REQUIRE( new_bob_comment.cashout_time == fc::time_point_sec( ( ( old_cashout_time * old_abs_rshares + ( db.head_block_time().sec_since_epoch() + STEEMIT_CASHOUT_WINDOW_SECONDS ) * new_rshares ) / ( old_abs_rshares + new_rshares ) ).to_uint64() ) ); + BOOST_REQUIRE( alice_bob_vote->weight == 0 ); + BOOST_REQUIRE( alice_bob_vote->rshares == -1 * new_rshares ); + BOOST_REQUIRE( alice_bob_vote->last_update == db.head_block_time() ); + BOOST_REQUIRE( alice_bob_vote->vote_percent == op.weight ); + BOOST_REQUIRE( db.get_account( "alice" ).voting_power == alice_voting_power ); + validate_database(); + + BOOST_TEST_MESSAGE( "--- Test changing a vote to 0 weight (aka: removing a vote)" ); + + old_vote_rshares = -1 * new_rshares; + old_net_rshares = new_bob_comment.net_rshares.value; + old_abs_rshares = new_bob_comment.abs_rshares.value; + old_cashout_time = new_bob_comment.cashout_time.sec_since_epoch(); + new_rshares = 0; + + op.weight = 0; + tx.operations.clear(); + tx.signatures.clear(); + tx.operations.push_back( op ); + tx.sign( alice_private_key, db.get_chain_id() ); + db.push_transaction( tx, 0 ); + alice_bob_vote = vote_idx.find( std::make_tuple( new_bob_comment.id, new_alice.id ) ); + + BOOST_REQUIRE( new_bob_comment.net_rshares == old_net_rshares - old_vote_rshares ); + BOOST_REQUIRE( new_bob_comment.abs_rshares == old_abs_rshares ); + BOOST_REQUIRE( new_bob_comment.total_vote_weight == old_total_vote_weight ); + BOOST_REQUIRE( new_bob_comment.cashout_time == fc::time_point_sec( old_cashout_time.to_uint64() ) ); + BOOST_REQUIRE( alice_bob_vote->weight == 0 ); + BOOST_REQUIRE( alice_bob_vote->rshares == 0 ); + BOOST_REQUIRE( alice_bob_vote->last_update == db.head_block_time() ); + BOOST_REQUIRE( alice_bob_vote->vote_percent == op.weight ); + BOOST_REQUIRE( db.get_account( "alice" ).voting_power == alice_voting_power ); + validate_database(); + + BOOST_TEST_MESSAGE( "--- Test failure when increasing rshares within lockout period" ); + + generate_blocks( fc::time_point_sec( new_bob_comment.cashout_time.sec_since_epoch() - STEEMIT_VOTE_CHANGE_LOCKOUT_PERIOD + STEEMIT_BLOCK_INTERVAL ), true ); + + op.weight = STEEMIT_100_PERCENT; + tx.operations.clear(); + tx.signatures.clear(); + tx.operations.push_back( op ); + tx.sign( alice_private_key, db.get_chain_id() ); + STEEMIT_REQUIRE_THROW( db.push_transaction( tx, 0 ), fc::assert_exception ); + validate_database(); + + BOOST_TEST_MESSAGE( "--- Test success when reducing rshares within lockout period" ); + + op.weight = -1 * STEEMIT_100_PERCENT; + tx.operations.clear(); + tx.signatures.clear(); + tx.operations.push_back( op ); + tx.sign( alice_private_key, db.get_chain_id() ); + db.push_transaction( tx, 0 ); + validate_database(); + + BOOST_TEST_MESSAGE( "--- Test success with a new vote within lockout period" ); + + op.weight = STEEMIT_100_PERCENT; + op.voter = "sam"; + tx.operations.clear(); + tx.signatures.clear(); + tx.operations.push_back( op ); + tx.sign( sam_private_key, db.get_chain_id() ); + db.push_transaction( tx, 0 ); + validate_database(); } } FC_LOG_AND_RETHROW()