diff --git a/libraries/api/chain_api_properties.cpp b/libraries/api/chain_api_properties.cpp index 74c85d0d8a..19b9ea704b 100644 --- a/libraries/api/chain_api_properties.cpp +++ b/libraries/api/chain_api_properties.cpp @@ -19,6 +19,10 @@ namespace golos { namespace api { max_referral_interest_rate = src.max_referral_interest_rate; max_referral_term_sec = src.max_referral_term_sec; max_referral_break_fee = src.max_referral_break_fee; + comments_window = src.comments_window; + comments_per_window = src.comments_per_window; + votes_window = src.votes_window; + votes_per_window = src.votes_per_window; } } diff --git a/libraries/api/include/golos/api/chain_api_properties.hpp b/libraries/api/include/golos/api/chain_api_properties.hpp index 401c86a856..8f0c01a832 100644 --- a/libraries/api/include/golos/api/chain_api_properties.hpp +++ b/libraries/api/include/golos/api/chain_api_properties.hpp @@ -24,6 +24,11 @@ namespace golos { namespace api { fc::optional max_referral_interest_rate; fc::optional max_referral_term_sec; fc::optional max_referral_break_fee; + + fc::optional comments_window; + fc::optional comments_per_window; + fc::optional votes_window; + fc::optional votes_per_window; }; } } // golos::api @@ -33,4 +38,5 @@ FC_REFLECT( (account_creation_fee)(maximum_block_size)(sbd_interest_rate) (create_account_min_golos_fee)(create_account_min_delegation) (create_account_delegation_time)(min_delegation) - (max_referral_interest_rate)(max_referral_term_sec)(max_referral_break_fee)) + (max_referral_interest_rate)(max_referral_term_sec)(max_referral_break_fee) + (comments_window)(comments_per_window)(votes_window)(votes_per_window)) diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index 416b5a87bc..69f0ee48b3 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -1878,14 +1878,29 @@ namespace golos { namespace chain { chain_properties_19 median_props; + auto median = active.size() / 2; + auto calc_median = [&](auto&& param) { std::nth_element( - active.begin(), active.begin() + active.size() / 2, active.end(), + active.begin(), active.begin() + median, active.end(), [&](const auto* a, const auto* b) { return a->props.*param < b->props.*param; } ); - median_props.*param = active[active.size() / 2]->props.*param; + median_props.*param = active[median]->props.*param; + }; + + auto calc_median_battery = [&](auto&& window, auto&& items) { + std::nth_element( + active.begin(), active.begin() + median, active.end(), + [&](const auto* a, const auto* b) { + auto a_consumption = a->props.*window / a->props.*items; + auto b_consumption = b->props.*window / b->props.*items; + return std::tie(a_consumption, a->props.*items) < std::tie(b_consumption, b->props.*items); + } + ); + median_props.*window = active[median]->props.*window; + median_props.*items = active[median]->props.*items; }; calc_median(&chain_properties_17::account_creation_fee); @@ -1898,6 +1913,8 @@ namespace golos { namespace chain { calc_median(&chain_properties_19::max_referral_interest_rate); calc_median(&chain_properties_19::max_referral_term_sec); calc_median(&chain_properties_19::max_referral_break_fee); + calc_median_battery(&chain_properties_19::comments_window, &chain_properties_19::comments_per_window); + calc_median_battery(&chain_properties_19::votes_window, &chain_properties_19::votes_per_window); modify(wso, [&](witness_schedule_object &_wso) { _wso.median_props = median_props; diff --git a/libraries/chain/hardfork.d/0_19.hf b/libraries/chain/hardfork.d/0_19.hf index 495f15388c..37527ca128 100644 --- a/libraries/chain/hardfork.d/0_19.hf +++ b/libraries/chain/hardfork.d/0_19.hf @@ -1,6 +1,7 @@ #ifndef STEEMIT_HARDFORK_0_19 #define STEEMIT_HARDFORK_0_19 19 #define STEEMIT_HARDFORK_0_19__295 (STEEMIT_HARDFORK_0_19) // Referral program implemented +#define STEEMIT_HARDFORK_0_19__533 (STEEMIT_HARDFORK_0_19) // Leaky algorithm for comment and vote bandwidth #ifdef STEEMIT_BUILD_TESTNET #define STEEMIT_HARDFORK_0_19_TIME 1534755600 // 20 aug 2018 12:00:00 MSK diff --git a/libraries/chain/include/golos/chain/account_object.hpp b/libraries/chain/include/golos/chain/account_object.hpp index 632aa04214..9e8c5db171 100644 --- a/libraries/chain/include/golos/chain/account_object.hpp +++ b/libraries/chain/include/golos/chain/account_object.hpp @@ -50,6 +50,8 @@ class account_object bool can_vote = true; uint16_t voting_power = STEEMIT_100_PERCENT; ///< current voting power of this account, it falls after every vote + uint16_t comments_capacity = STEEMIT_COMMENTS_WINDOW; + uint16_t voting_capacity = STEEMIT_VOTES_WINDOW; time_point_sec last_vote_time; ///< used to increase the voting power of this account the longer it goes without voting. asset balance = asset(0, STEEM_SYMBOL); ///< total liquid shares held by this account diff --git a/libraries/chain/steem_evaluator.cpp b/libraries/chain/steem_evaluator.cpp index b0eb6c6b00..88e95e0c0f 100644 --- a/libraries/chain/steem_evaluator.cpp +++ b/libraries/chain/steem_evaluator.cpp @@ -587,6 +587,8 @@ namespace golos { namespace chain { logic_exception::cannot_update_comment_because_nothing_changed, "Cannot update comment because nothing appears to be changing."); + const auto& mprops = _db.get_witness_schedule_object().median_props; + const auto &by_permlink_idx = _db.get_index().indices().get(); auto itr = by_permlink_idx.find(boost::make_tuple(o.author, o.permlink)); @@ -631,7 +633,29 @@ namespace golos { namespace chain { }); } - if (_db.has_hardfork(STEEMIT_HARDFORK_0_12__176)) { + auto elapsed_seconds = (now - auth.last_post).to_seconds(); + + if (_db.has_hardfork(STEEMIT_HARDFORK_0_19__533)) { + auto consumption = mprops.comments_window / mprops.comments_per_window; + + auto regenerated_capacity = std::min(uint32_t(mprops.comments_window), uint32_t(elapsed_seconds)); + auto current_capacity = std::min(uint16_t(auth.comments_capacity + regenerated_capacity), mprops.comments_window); + + if (o.parent_author == STEEMIT_ROOT_POST_PARENT) { + GOLOS_CHECK_BANDWIDTH(now, band->last_bandwidth_update + STEEMIT_MIN_ROOT_COMMENT_INTERVAL, + bandwidth_exception::post_bandwidth, + "You may only post once every 5 minutes."); + } else { + GOLOS_CHECK_BANDWIDTH(current_capacity, consumption, + bandwidth_exception::comment_bandwidth, + "You may only comment ${comments_per_window} times in ${comments_window} seconds.", + ("comments_per_window", mprops.comments_per_window)("comments_window", mprops.comments_window)); + } + + db().modify(auth, [&](account_object &a) { + a.comments_capacity = current_capacity - consumption; + }); + } else if (_db.has_hardfork(STEEMIT_HARDFORK_0_12__176)) { if (o.parent_author == STEEMIT_ROOT_POST_PARENT) GOLOS_CHECK_BANDWIDTH(now, band->last_bandwidth_update + STEEMIT_MIN_ROOT_COMMENT_INTERVAL, bandwidth_exception::post_bandwidth, @@ -684,7 +708,11 @@ namespace golos { namespace chain { db().modify(auth, [&](account_object &a) { a.last_post = now; - a.post_count++; + if (o.parent_author != STEEMIT_ROOT_POST_PARENT) { + a.comment_count++; + } else { + a.post_count++; + } }); bool referrer_to_delete = false; @@ -1249,6 +1277,8 @@ namespace golos { namespace chain { const auto& comment = _db.get_comment(o.author, o.permlink); const auto& voter = _db.get_account(o.voter); + const auto& mprops = _db.get_witness_schedule_object().median_props; + GOLOS_CHECK_LOGIC(!(voter.owner_challenged || voter.active_challenged), logic_exception::account_is_currently_challenged, "Account \"${account}\" is currently challenged", ("account", voter.name)); @@ -1285,10 +1315,26 @@ namespace golos { namespace chain { const auto& comment_vote_idx = _db.get_index().indices().get(); auto itr = comment_vote_idx.find(std::make_tuple(comment.id, voter.id)); - int64_t elapsed_seconds = (_db.head_block_time() - voter.last_vote_time).to_seconds(); + auto elapsed_seconds = (_db.head_block_time() - voter.last_vote_time).to_seconds(); + + if (_db.has_hardfork(STEEMIT_HARDFORK_0_19__533)) { + auto consumption = mprops.votes_window / mprops.votes_per_window; + + auto regenerated_capacity = std::min(uint32_t(mprops.votes_window), uint32_t(elapsed_seconds)); + auto current_capacity = std::min(uint16_t(voter.voting_capacity + regenerated_capacity), mprops.votes_window); + + GOLOS_CHECK_BANDWIDTH(current_capacity, consumption, + bandwidth_exception::vote_bandwidth, + "Can only vote ${votes_per_window} times in ${votes_window} seconds.", + ("votes_per_window", mprops.votes_per_window)("votes_window", mprops.votes_window)); - GOLOS_CHECK_BANDWIDTH(_db.head_block_time(), voter.last_vote_time + STEEMIT_MIN_VOTE_INTERVAL_SEC-1, + _db.modify(voter, [&](account_object &a) { + a.voting_capacity = current_capacity - consumption; + }); + } else { + GOLOS_CHECK_BANDWIDTH(_db.head_block_time(), voter.last_vote_time + STEEMIT_MIN_VOTE_INTERVAL_SEC-1, bandwidth_exception::vote_bandwidth, "Can only vote once every 3 seconds."); + } int64_t regenerated_power = (STEEMIT_100_PERCENT * elapsed_seconds) / @@ -1641,7 +1687,6 @@ namespace golos { namespace chain { _db.adjust_rshares2(comment, old_rshares, new_rshares); } - } FC_CAPTURE_AND_RETHROW((o)) } diff --git a/libraries/protocol/include/golos/protocol/config.hpp b/libraries/protocol/include/golos/protocol/config.hpp index 4efb17976d..919f8da31b 100644 --- a/libraries/protocol/include/golos/protocol/config.hpp +++ b/libraries/protocol/include/golos/protocol/config.hpp @@ -80,6 +80,11 @@ #define STEEMIT_POST_MAX_BANDWIDTH (4*STEEMIT_100_PERCENT) // 2 posts per 1 days, average 1 every 12 hours #define STEEMIT_POST_WEIGHT_CONSTANT (uint64_t(STEEMIT_POST_MAX_BANDWIDTH) * STEEMIT_POST_MAX_BANDWIDTH) +#define STEEMIT_COMMENTS_WINDOW 200 +#define STEEMIT_COMMENTS_PER_WINDOW 10 +#define STEEMIT_VOTES_WINDOW 200 // For testnet +#define STEEMIT_VOTES_PER_WINDOW 10 + #define STEEMIT_MAX_ACCOUNT_WITNESS_VOTES 30 #define STEEMIT_100_PERCENT 10000 @@ -295,6 +300,11 @@ #define STEEMIT_POST_MAX_BANDWIDTH (4*STEEMIT_100_PERCENT) // 2 posts per 1 days, average 1 every 12 hours #define STEEMIT_POST_WEIGHT_CONSTANT (uint64_t(STEEMIT_POST_MAX_BANDWIDTH) * STEEMIT_POST_MAX_BANDWIDTH) +#define STEEMIT_COMMENTS_WINDOW 200 +#define STEEMIT_COMMENTS_PER_WINDOW 10 +#define STEEMIT_VOTES_WINDOW 15 +#define STEEMIT_VOTES_PER_WINDOW 5 + #define STEEMIT_MAX_ACCOUNT_WITNESS_VOTES 30 #define STEEMIT_100_PERCENT 10000 diff --git a/libraries/protocol/include/golos/protocol/steem_operations.hpp b/libraries/protocol/include/golos/protocol/steem_operations.hpp index 048831c648..15a551a99a 100644 --- a/libraries/protocol/include/golos/protocol/steem_operations.hpp +++ b/libraries/protocol/include/golos/protocol/steem_operations.hpp @@ -532,6 +532,26 @@ namespace golos { namespace protocol { */ asset max_referral_break_fee = GOLOS_DEFAULT_REFERRAL_BREAK_FEE; + /** + * Time window for commenting by account + */ + uint16_t comments_window = STEEMIT_COMMENTS_WINDOW; + + /** + * Maximum count of comments per one window by account + */ + uint16_t comments_per_window = STEEMIT_COMMENTS_PER_WINDOW; + + /** + * Time window for voting by account + */ + uint16_t votes_window = STEEMIT_VOTES_WINDOW; + + /** + * Maximum count of votes per one window by account + */ + uint16_t votes_per_window = STEEMIT_VOTES_PER_WINDOW; + void validate() const; chain_properties_19& operator=(const chain_properties_17& src) { @@ -1210,7 +1230,8 @@ FC_REFLECT_DERIVED( (create_account_delegation_time)(min_delegation)) FC_REFLECT_DERIVED( (golos::protocol::chain_properties_19), ((golos::protocol::chain_properties_18)), - (max_referral_interest_rate)(max_referral_term_sec)(max_referral_break_fee)) + (max_referral_interest_rate)(max_referral_term_sec)(max_referral_break_fee) + (comments_window)(comments_per_window)(votes_window)(votes_per_window)) FC_REFLECT_TYPENAME((golos::protocol::versioned_chain_properties)) diff --git a/libraries/protocol/steem_operations.cpp b/libraries/protocol/steem_operations.cpp index e2477aed60..2e6027ccb7 100644 --- a/libraries/protocol/steem_operations.cpp +++ b/libraries/protocol/steem_operations.cpp @@ -255,6 +255,10 @@ namespace golos { namespace protocol { GOLOS_CHECK_VALUE_LE(max_referral_interest_rate, GOLOS_MAX_REFERRAL_INTEREST_RATE); GOLOS_CHECK_VALUE_LE(max_referral_term_sec, GOLOS_MAX_REFERRAL_TERM_SEC); GOLOS_CHECK_VALUE_LEGE(max_referral_break_fee.amount, 0, GOLOS_MAX_REFERRAL_BREAK_FEE.amount); + GOLOS_CHECK_VALUE_LEGE(comments_window, 1, std::numeric_limits::max() / 2); + GOLOS_CHECK_VALUE_LEGE(comments_per_window, 1, comments_window); + GOLOS_CHECK_VALUE_LEGE(votes_window, 1, std::numeric_limits::max() / 2); + GOLOS_CHECK_VALUE_LEGE(votes_per_window, 1, votes_window); } void witness_update_operation::validate() const { diff --git a/libraries/wallet/include/golos/wallet/wallet.hpp b/libraries/wallet/include/golos/wallet/wallet.hpp index 6b890dd62d..4e345d9f4f 100644 --- a/libraries/wallet/include/golos/wallet/wallet.hpp +++ b/libraries/wallet/include/golos/wallet/wallet.hpp @@ -48,6 +48,11 @@ namespace golos { namespace wallet { fc::optional max_referral_interest_rate; fc::optional max_referral_term_sec; fc::optional max_referral_break_fee; + + fc::optional comments_window; + fc::optional comments_per_window; + fc::optional votes_window; + fc::optional votes_per_window; }; struct optional_private_box_query { @@ -1522,7 +1527,8 @@ FC_REFLECT((golos::wallet::optional_chain_props), (account_creation_fee)(maximum_block_size)(sbd_interest_rate) (create_account_min_golos_fee)(create_account_min_delegation) (create_account_delegation_time)(min_delegation) - (max_referral_interest_rate)(max_referral_term_sec)(max_referral_break_fee)) + (max_referral_interest_rate)(max_referral_term_sec)(max_referral_break_fee) + (comments_window)(comments_per_window)(votes_window)(votes_per_window)) FC_REFLECT( (golos::wallet::message_body), diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 85a3725a6c..55ba937b26 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -330,6 +330,10 @@ namespace golos { namespace wallet { result["max_referral_interest_rate"] = median_props.max_referral_interest_rate; result["max_referral_term_sec"] = median_props.max_referral_term_sec; result["max_referral_break_fee"] = median_props.max_referral_break_fee; + result["comments_window"] = median_props.comments_window; + result["comments_per_window"] = median_props.comments_per_window; + result["votes_window"] = median_props.votes_window; + result["votes_per_window"] = median_props.votes_per_window; } return result; @@ -2253,12 +2257,17 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st op.props = p; auto hf = my->_remote_database_api->get_hardfork_version(); if (hf >= hardfork_version(0, STEEMIT_HARDFORK_0_19) || !!props.max_referral_interest_rate - || !!props.max_referral_term_sec || !!props.max_referral_break_fee) { + || !!props.max_referral_term_sec || !!props.max_referral_break_fee || !!props.comments_window + || !!props.comments_per_window || !!props.votes_window || !!props.votes_per_window) { chain_properties_19 p19; p19 = p; SET_PROP(p19, max_referral_interest_rate); SET_PROP(p19, max_referral_term_sec); SET_PROP(p19, max_referral_break_fee); + SET_PROP(p19, comments_window); + SET_PROP(p19, comments_per_window); + SET_PROP(p19, votes_window); + SET_PROP(p19, votes_per_window); op.props = p19; } #undef SET_PROP