/* * Copyright (c) 2015 Cryptonomex, Inc., and contributors. * * The MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace graphene { namespace chain { template vector> database::sort_votable_objects(size_t count) const { using ObjectType = typename Index::object_type; const auto& all_objects = get_index_type().indices(); count = std::min(count, all_objects.size()); vector> refs; refs.reserve(all_objects.size()); std::transform(all_objects.begin(), all_objects.end(), std::back_inserter(refs), [](const ObjectType& o) { return std::cref(o); }); std::partial_sort(refs.begin(), refs.begin() + count, refs.end(), [this](const ObjectType& a, const ObjectType& b)->bool { share_type oa_vote = _vote_tally_buffer[a.vote_id]; share_type ob_vote = _vote_tally_buffer[b.vote_id]; if( oa_vote != ob_vote ) return oa_vote > ob_vote; return a.vote_id < b.vote_id; }); refs.resize(count, refs.front()); return refs; } template<> vector> database::sort_votable_objects(sidechain_type sidechain, size_t count) const { const auto& all_sons = get_index_type().indices().get< by_id >(); std::vector> refs; for( auto& son : all_sons ) { if(son.has_valid_config(head_block_time(), sidechain) && son.statuses.at(sidechain) != son_status::deregistered) { refs.push_back(std::cref(son)); } } count = std::min(count, refs.size()); std::partial_sort(refs.begin(), refs.begin() + count, refs.end(), [this, sidechain](const son_object& a, const son_object& b)->bool { FC_ASSERT(sidechain == sidechain_type::bitcoin || sidechain == sidechain_type::ethereum || sidechain == sidechain_type::hive, "Unexpected sidechain type"); FC_ASSERT(a.get_sidechain_vote_id(sidechain).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", sidechain)("son", a)); FC_ASSERT(b.get_sidechain_vote_id(sidechain).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", sidechain)("son", b)); const share_type oa_vote = _vote_tally_buffer.size() > *a.get_sidechain_vote_id(sidechain) ? _vote_tally_buffer[*a.get_sidechain_vote_id(sidechain)] : 0; const share_type ob_vote = _vote_tally_buffer.size() > *b.get_sidechain_vote_id(sidechain) ? _vote_tally_buffer[*b.get_sidechain_vote_id(sidechain)] : 0; if( oa_vote != ob_vote ) return oa_vote > ob_vote; return a.get_sidechain_vote_id(sidechain) < b.get_sidechain_vote_id(sidechain); }); refs.resize(count, refs.front()); return refs; } template void database::perform_account_maintenance(Type tally_helper) { const auto& bal_idx = get_index_type< account_balance_index >().indices().get< by_maintenance_flag >(); if( bal_idx.begin() != bal_idx.end() ) { auto bal_itr = bal_idx.rbegin(); while( bal_itr->maintenance_flag ) { const account_balance_object& bal_obj = *bal_itr; modify( get_account_stats_by_owner( bal_obj.owner ), [&bal_obj](account_statistics_object& aso) { aso.core_in_balance = bal_obj.balance; }); modify( bal_obj, []( account_balance_object& abo ) { abo.maintenance_flag = false; }); bal_itr = bal_idx.rbegin(); } } const auto& stats_idx = get_index_type< account_stats_index >().indices().get< by_maintenance_seq >(); auto stats_itr = stats_idx.lower_bound( true ); while( stats_itr != stats_idx.end() ) { const account_statistics_object& acc_stat = *stats_itr; const account_object& acc_obj = acc_stat.owner( *this ); ++stats_itr; if( acc_stat.has_some_core_voting() ) tally_helper( acc_obj, acc_stat ); if( acc_stat.has_pending_fees() ) acc_stat.process_fees( acc_obj, *this ); } } /// @brief A visitor for @ref worker_type which calls pay_worker on the worker within struct worker_pay_visitor { private: share_type pay; database& db; public: worker_pay_visitor(share_type pay, database& db) : pay(pay), db(db) {} typedef void result_type; template void operator()(W& worker)const { worker.pay_worker(pay, db); } }; void database::update_worker_votes() { auto& idx = get_index_type(); auto itr = idx.indices().get().begin(); bool allow_negative_votes = (head_block_time() < HARDFORK_607_TIME); while( itr != idx.indices().get().end() ) { modify( *itr, [&]( worker_object& obj ){ obj.total_votes_for = _vote_tally_buffer[obj.vote_for]; obj.total_votes_against = allow_negative_votes ? _vote_tally_buffer[obj.vote_against] : 0; }); ++itr; } } void database::pay_sons_before_hf_ethereum() { const auto now = head_block_time(); const dynamic_global_property_object& dpo = get_dynamic_global_properties(); // Current requirement is that we have to pay every 24 hours, so the following check if( dpo.son_budget.value > 0 && ((now - dpo.last_son_payout_time) >= fc::seconds(get_global_properties().parameters.son_pay_time()))) { const sidechain_type st = sidechain_type::bitcoin; const auto sons = sort_votable_objects(st, get_global_properties().parameters.maximum_son_count()); // After SON2 HF uint64_t total_votes = 0; for( const son_object& son : sons ) { FC_ASSERT(son.get_sidechain_vote_id(st).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", st)("son", son)); total_votes += _vote_tally_buffer[*son.get_sidechain_vote_id(st)]; } const int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); auto get_weight = [&bits_to_drop]( uint64_t son_votes ) { const uint16_t weight = std::max((son_votes >> bits_to_drop), uint64_t(1) ); return weight; }; // Before SON2 HF auto get_weight_before_son2_hf = []( uint64_t son_votes ) { const int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(son_votes)) - 15, 0); const uint16_t weight = std::max((son_votes >> bits_to_drop), uint64_t(1) ); return weight; }; uint64_t weighted_total_txs_signed = 0; const share_type son_budget = dpo.son_budget; get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &get_weight, &now, &get_weight_before_son2_hf, &st](const object& o) { const son_statistics_object& s = static_cast(o); const auto& idx = get_index_type().indices().get(); const auto son_obj = idx.find( s.owner ); uint16_t son_weight = 0; FC_ASSERT(son_obj->get_sidechain_vote_id(st).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", st)("son", *son_obj)); if( now >= HARDFORK_SON2_TIME ) { son_weight += get_weight(_vote_tally_buffer[*son_obj->get_sidechain_vote_id(st)]); } else { son_weight += get_weight_before_son2_hf(_vote_tally_buffer[*son_obj->get_sidechain_vote_id(st)]); } const uint64_t txs_signed_bitcoin = s.txs_signed.contains(sidechain_type::bitcoin) ? s.txs_signed.at(sidechain_type::bitcoin) : 0; const uint64_t txs_signed_hive = s.txs_signed.contains(sidechain_type::hive) ? s.txs_signed.at(sidechain_type::hive) : 0; weighted_total_txs_signed += ((txs_signed_bitcoin + txs_signed_hive) * son_weight); }); // Now pay off each SON proportional to the number of transactions signed. get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &dpo, &son_budget, &get_weight, &get_weight_before_son2_hf, &now, &st](const object& o) { const son_statistics_object& s = static_cast(o); const uint64_t txs_signed_bitcoin = s.txs_signed.contains(sidechain_type::bitcoin) ? s.txs_signed.at(sidechain_type::bitcoin) : 0; const uint64_t txs_signed_hive = s.txs_signed.contains(sidechain_type::hive) ? s.txs_signed.at(sidechain_type::hive) : 0; if(txs_signed_bitcoin > 0 || txs_signed_hive > 0) { const auto& idx = get_index_type().indices().get(); auto son_obj = idx.find( s.owner ); uint16_t son_weight = 0; FC_ASSERT(son_obj->get_sidechain_vote_id(st).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", st)("son", *son_obj)); if( now >= HARDFORK_SON2_TIME ) { son_weight += get_weight(_vote_tally_buffer[*son_obj->get_sidechain_vote_id(st)]); } else { son_weight += get_weight_before_son2_hf(_vote_tally_buffer[*son_obj->get_sidechain_vote_id(st)]); } const share_type pay = ((txs_signed_bitcoin + txs_signed_hive) * son_weight * son_budget.value)/weighted_total_txs_signed; modify( *son_obj, [&]( son_object& _son_obj) { _son_obj.pay_son_fee(pay, *this); }); //Remove the amount paid out to SON from global SON Budget modify( dpo, [&]( dynamic_global_property_object& _dpo ) { _dpo.son_budget -= pay; } ); //Reset the tx counter in each son statistics object modify( s, [&]( son_statistics_object& _s) { if(_s.txs_signed.contains(sidechain_type::bitcoin)) _s.txs_signed.at(sidechain_type::bitcoin) = 0; if(_s.txs_signed.contains(sidechain_type::hive)) _s.txs_signed.at(sidechain_type::hive) = 0; }); } }); //Note the last son pay out time modify( dpo, [&]( dynamic_global_property_object& _dpo ) { _dpo.last_son_payout_time = now; }); } } void database::pay_sons_after_hf_ethereum() { const time_point_sec now = head_block_time(); const dynamic_global_property_object& dpo = get_dynamic_global_properties(); // Current requirement is that we have to pay every 24 hours, so the following check if( dpo.son_budget.value > 0 && ((now - dpo.last_son_payout_time) >= fc::seconds(get_global_properties().parameters.son_pay_time()))) { flat_map bits_to_drop; for(const auto& active_sidechain_type : active_sidechain_types(now)) { assert( _son_count_histogram_buffer.at(active_sidechain_type).size() > 0 ); const share_type stake_target = (_total_voting_stake-_son_count_histogram_buffer.at(active_sidechain_type)[0]) / 2; /// accounts that vote for 0 or 1 son do not get to express an opinion on /// the number of sons to have (they abstain and are non-voting accounts) share_type stake_tally = 0; size_t son_count = 0; if( stake_target > 0 ) { while( (son_count < _son_count_histogram_buffer.at(active_sidechain_type).size() - 1) && (stake_tally <= stake_target) ) { stake_tally += _son_count_histogram_buffer.at(active_sidechain_type)[++son_count]; } } const auto sons = sort_votable_objects(active_sidechain_type, (std::max(son_count*2+1, (size_t)get_chain_properties().immutable_parameters.min_son_count))); // After SON2 HF uint64_t total_votes = 0; for( const son_object& son : sons ) { FC_ASSERT(son.get_sidechain_vote_id(active_sidechain_type).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", active_sidechain_type)("son", son)); total_votes += _vote_tally_buffer[*son.get_sidechain_vote_id(active_sidechain_type)]; } bits_to_drop[active_sidechain_type] = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); } auto get_weight = [&bits_to_drop]( sidechain_type sidechain, uint64_t son_votes ) { const uint16_t weight = std::max((son_votes >> bits_to_drop.at(sidechain)), uint64_t(1) ); return weight; }; // Calculate weighted_total_txs_signed uint64_t weighted_total_txs_signed = 0; get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &get_weight, &now](const object& o) { for(const auto& active_sidechain_type : active_sidechain_types(now)) { const son_statistics_object &s = static_cast(o); const auto &idx = get_index_type().indices().get(); const auto son_obj = idx.find(s.owner); FC_ASSERT(son_obj->get_sidechain_vote_id(active_sidechain_type).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", active_sidechain_type)("son", *son_obj)); const uint16_t son_weight = get_weight(active_sidechain_type, _vote_tally_buffer[*son_obj->get_sidechain_vote_id(active_sidechain_type)]); const uint64_t txs_signed = s.txs_signed.contains(active_sidechain_type) ? s.txs_signed.at(active_sidechain_type) : 0; weighted_total_txs_signed += (txs_signed * son_weight); } }); // Now pay off each SON proportional to the number of transactions signed const share_type son_budget = dpo.son_budget; get_index_type().inspect_all_objects([this, &now, &get_weight, &weighted_total_txs_signed, &dpo, &son_budget](const object& o) { for(const auto& active_sidechain_type : active_sidechain_types(now)) { const son_statistics_object &s = static_cast(o); const uint64_t txs_signed = s.txs_signed.contains(active_sidechain_type) ? s.txs_signed.at(active_sidechain_type) : 0; if (txs_signed > 0) { const auto &idx = get_index_type().indices().get(); auto son_obj = idx.find(s.owner); uint16_t son_weight = 0; FC_ASSERT(son_obj->get_sidechain_vote_id(active_sidechain_type).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", active_sidechain_type)("son", *son_obj)); son_weight += get_weight(active_sidechain_type, _vote_tally_buffer[*son_obj->get_sidechain_vote_id(active_sidechain_type)]); const share_type pay = (txs_signed * son_weight * son_budget.value) / weighted_total_txs_signed; modify(*son_obj, [&](son_object &_son_obj) { _son_obj.pay_son_fee(pay, *this); }); // Remove the amount paid out to SON from global SON Budget modify(dpo, [&](dynamic_global_property_object &_dpo) { _dpo.son_budget -= pay; }); // Reset the tx counter in each son statistics object modify(s, [&](son_statistics_object &_s) { if (_s.txs_signed.contains(active_sidechain_type)) _s.txs_signed.at(active_sidechain_type) = 0; }); } } }); //Note the last son pay out time modify( dpo, [&]( dynamic_global_property_object& _dpo ) { _dpo.last_son_payout_time = now; }); } } void database::update_son_metrics(const flat_map >& curr_active_sons) { for(const auto& curr_active_sidechain_sons : curr_active_sons) { const auto& sidechain = curr_active_sidechain_sons.first; const auto& _curr_active_sidechain_sons = curr_active_sidechain_sons.second; vector current_sons; current_sons.reserve(_curr_active_sidechain_sons.size()); std::transform(_curr_active_sidechain_sons.cbegin(), _curr_active_sidechain_sons.cend(), std::inserter(current_sons, current_sons.end()), [](const son_sidechain_info &swi) { return swi.son_id; }); const auto &son_idx = get_index_type().indices().get(); for (auto &son : son_idx) { auto &stats = son.statistics(*this); bool is_active_son = (std::find(current_sons.begin(), current_sons.end(), son.id) != current_sons.end()); modify(stats, [&](son_statistics_object &_stats) { if (is_active_son) { _stats.total_voted_time[sidechain] = _stats.total_voted_time[sidechain] + get_global_properties().parameters.maintenance_interval; } if(!_stats.current_interval_downtime.contains(sidechain)) _stats.current_interval_downtime[sidechain] = 0; _stats.total_downtime[sidechain] += _stats.current_interval_downtime.at(sidechain); _stats.current_interval_downtime[sidechain] = 0; _stats.sidechain_txs_reported[sidechain] = 0; }); } } } void database::update_son_statuses( const flat_map >& curr_active_sons, const flat_map >& new_active_sons ) { for(const auto& new_active_sidechain_sons : new_active_sons) { const auto& sidechain = new_active_sidechain_sons.first; vector current_sons, new_sons; vector sons_to_remove, sons_to_add; const auto &idx = get_index_type().indices().get(); if(curr_active_sons.contains(sidechain)) { current_sons.reserve(curr_active_sons.at(sidechain).size()); std::transform(curr_active_sons.at(sidechain).cbegin(), curr_active_sons.at(sidechain).cend(), std::inserter(current_sons, current_sons.end()), [](const son_sidechain_info &swi) { return swi.son_id; }); } new_sons.reserve(new_active_sons.at(sidechain).size()); std::transform(new_active_sons.at(sidechain).cbegin(), new_active_sons.at(sidechain).cend(), std::inserter(new_sons, new_sons.end()), [](const son_sidechain_info &swi) { return swi.son_id; }); // find all cur_active_sons members that is not in new_active_sons for_each(current_sons.begin(), current_sons.end(), [&sons_to_remove, &new_sons](const son_id_type &si) { if (std::find(new_sons.begin(), new_sons.end(), si) == new_sons.end()) { sons_to_remove.push_back(si); } }); for (const auto &sid : sons_to_remove) { auto son = idx.find(sid); if (son == idx.end()) // SON is deleted already continue; // keep maintenance status for nodes becoming inactive if (son->statuses.at(sidechain) == son_status::active) { modify(*son, [&](son_object &obj) { obj.statuses.at(sidechain) = son_status::inactive; }); } } // find all new_active_sons members that is not in cur_active_sons for_each(new_sons.begin(), new_sons.end(), [&sons_to_add, ¤t_sons](const son_id_type &si) { if (std::find(current_sons.begin(), current_sons.end(), si) == current_sons.end()) { sons_to_add.push_back(si); } }); for (const auto &sid : sons_to_add) { auto son = idx.find(sid); FC_ASSERT(son != idx.end(), "Invalid SON in active list, id = ${sonid}.", ("sonid", sid)); // keep maintenance status for new nodes if (son->statuses.at(sidechain) == son_status::inactive) { modify(*son, [&](son_object &obj) { obj.statuses.at(sidechain) = son_status::active; }); } } ilog("New SONS for sidechain = ${sidechain}", ("sidechain", sidechain)); for (size_t i = 0; i < new_sons.size(); i++) { auto son = idx.find(new_sons[i]); if (son == idx.end()) // SON is deleted already continue; ilog("${s}, status = ${ss}, total_votes = ${sv}", ("s", new_sons[i])("ss", son->statuses.at(sidechain))("sv", son->total_votes)); } } //! Remove inactive sons (when all sidechain inactive) vector sons_to_remove; const auto &idx = get_index_type().indices().get(); for(const auto& son : idx) { bool inactive_son = true; for(const auto& status : son.statuses) { if (status.second != son_status::inactive) inactive_son = false; } if (inactive_son) sons_to_remove.emplace_back(son.id); } if (sons_to_remove.size() > 0) { remove_inactive_son_proposals(sons_to_remove); } } void database::update_son_wallet(const flat_map >& new_active_sons) { bool should_recreate_pw = true; // Expire for current son_wallet_object wallet, if exists const auto& idx_swi = get_index_type().indices().get(); auto obj = idx_swi.rbegin(); if (obj != idx_swi.rend()) { // Compare current wallet SONs and to-be lists of active sons auto cur_wallet_sons = (*obj).sons; bool wallet_son_sets_equal = (cur_wallet_sons.size() == new_active_sons.size()); if (wallet_son_sets_equal) { for( const auto& cur_wallet_sidechain_sons : cur_wallet_sons ) { const auto& sidechain = cur_wallet_sidechain_sons.first; const auto& _cur_wallet_sidechain_sons = cur_wallet_sidechain_sons.second; wallet_son_sets_equal = wallet_son_sets_equal && (_cur_wallet_sidechain_sons.size() == new_active_sons.at(sidechain).size()); if (wallet_son_sets_equal) { for (size_t i = 0; i < _cur_wallet_sidechain_sons.size(); i++) { wallet_son_sets_equal = wallet_son_sets_equal && (_cur_wallet_sidechain_sons.at(i) == new_active_sons.at(sidechain).at(i)); } } } } should_recreate_pw = !wallet_son_sets_equal; if (should_recreate_pw) { modify(*obj, [&, obj](son_wallet_object &swo) { swo.expires = head_block_time(); }); } } bool should_recreate_pw_sidechain = false; for(const auto& new_active_sidechain_sons : new_active_sons) { if(new_active_sidechain_sons.second.size() >= get_chain_properties().immutable_parameters.min_son_count) should_recreate_pw_sidechain = true; } should_recreate_pw = should_recreate_pw && should_recreate_pw_sidechain; if (should_recreate_pw) { // Create new son_wallet_object, to initiate wallet recreation create( [&]( son_wallet_object& obj ) { obj.valid_from = head_block_time(); obj.expires = time_point_sec::maximum(); for(const auto& new_active_sidechain_sons : new_active_sons){ const auto& sidechain = new_active_sidechain_sons.first; const auto& _new_active_sidechain_sons = new_active_sidechain_sons.second; obj.sons[sidechain].insert(obj.sons[sidechain].end(), _new_active_sidechain_sons.cbegin(), _new_active_sidechain_sons.cend()); } }); } } void database::pay_workers( share_type& budget ) { const auto head_time = head_block_time(); // ilog("Processing payroll! Available budget is ${b}", ("b", budget)); vector> active_workers; // TODO optimization: add by_expiration index to avoid iterating through all objects get_index_type().inspect_all_objects([head_time, &active_workers](const object& o) { const worker_object& w = static_cast(o); if( w.is_active(head_time) && w.approving_stake() > 0 ) active_workers.emplace_back(w); }); // worker with more votes is preferred // if two workers exactly tie for votes, worker with lower ID is preferred std::sort(active_workers.begin(), active_workers.end(), [this](const worker_object& wa, const worker_object& wb) { share_type wa_vote = wa.approving_stake(); share_type wb_vote = wb.approving_stake(); if( wa_vote != wb_vote ) return wa_vote > wb_vote; return wa.id < wb.id; }); const auto last_budget_time = get_dynamic_global_properties().last_budget_time; const auto passed_time_ms = head_time - last_budget_time; const auto passed_time_count = passed_time_ms.count(); const auto day_count = fc::days(1).count(); for( uint32_t i = 0; i < active_workers.size() && budget > 0; ++i ) { const worker_object& active_worker = active_workers[i]; share_type requested_pay = active_worker.daily_pay; // Note: if there is a good chance that passed_time_count == day_count, // for better performance, can avoid the 128 bit calculation by adding a check. // Since it's not the case on BitShares mainnet, we're not using a check here. fc::uint128 pay(requested_pay.value); pay *= passed_time_count; pay /= day_count; requested_pay = pay.to_uint64(); share_type actual_pay = std::min(budget, requested_pay); //ilog(" ==> Paying ${a} to worker ${w}", ("w", active_worker.id)("a", actual_pay)); modify(active_worker, [&](worker_object& w) { w.worker.visit(worker_pay_visitor(actual_pay, *this)); }); budget -= actual_pay; } } void database::update_active_witnesses() { try { assert( _witness_count_histogram_buffer.size() > 0 ); share_type stake_target = (_total_voting_stake-_witness_count_histogram_buffer[0]) / 2; /// accounts that vote for 0 or 1 witness do not get to express an opinion on /// the number of witnesses to have (they abstain and are non-voting accounts) share_type stake_tally = 0; size_t witness_count = 0; if( stake_target > 0 ) { while( (witness_count < _witness_count_histogram_buffer.size() - 1) && (stake_tally <= stake_target) ) { stake_tally += _witness_count_histogram_buffer[++witness_count]; } } const chain_property_object& cpo = get_chain_properties(); auto wits = sort_votable_objects(std::max(witness_count*2+1, (size_t)cpo.immutable_parameters.min_witness_count)); const global_property_object& gpo = get_global_properties(); auto update_witness_total_votes = [this]( const witness_object& wit ) { modify( wit, [this]( witness_object& obj ) { obj.total_votes = _vote_tally_buffer[obj.vote_id]; }); }; if( _track_standby_votes ) { const auto& all_witnesses = get_index_type().indices(); for( const witness_object& wit : all_witnesses ) { update_witness_total_votes( wit ); } } else { for( const witness_object& wit : wits ) { update_witness_total_votes( wit ); } } // Update witness authority modify( get(GRAPHENE_WITNESS_ACCOUNT), [&]( account_object& a ) { if( head_block_time() < HARDFORK_533_TIME ) { uint64_t total_votes = 0; map weights; a.active.weight_threshold = 0; a.active.clear(); for( const witness_object& wit : wits ) { weights.emplace(wit.witness_account, _vote_tally_buffer[wit.vote_id]); total_votes += _vote_tally_buffer[wit.vote_id]; } // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, // then I want to keep the most significant 16 bits of what's left. int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); for( const auto& weight : weights ) { // Ensure that everyone has at least one vote. Zero weights aren't allowed. uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) ); a.active.account_auths[weight.first] += votes; a.active.weight_threshold += votes; } a.active.weight_threshold /= 2; a.active.weight_threshold += 1; } else { vote_counter vc; for( const witness_object& wit : wits ) vc.add( wit.witness_account, std::max(_vote_tally_buffer[wit.vote_id], UINT64_C(1)) ); vc.finish( a.active ); } } ); modify(gpo, [&]( global_property_object& gp ){ gp.active_witnesses.clear(); gp.active_witnesses.reserve(wits.size()); std::transform(wits.begin(), wits.end(), std::inserter(gp.active_witnesses, gp.active_witnesses.end()), [](const witness_object& w) { return w.id; }); }); const witness_schedule_object& wso = witness_schedule_id_type()(*this); modify(wso, [&](witness_schedule_object& _wso) { _wso.scheduler.update(gpo.active_witnesses); }); } FC_CAPTURE_AND_RETHROW() } void database::update_active_committee_members() { try { assert( _committee_count_histogram_buffer.size() > 0 ); share_type stake_target = (_total_voting_stake-_committee_count_histogram_buffer[0]) / 2; /// accounts that vote for 0 or 1 witness do not get to express an opinion on /// the number of witnesses to have (they abstain and are non-voting accounts) uint64_t stake_tally = 0; // _committee_count_histogram_buffer[0]; size_t committee_member_count = 0; if( stake_target > 0 ) while( (committee_member_count < _committee_count_histogram_buffer.size() - 1) && (stake_tally <= stake_target) ) stake_tally += _committee_count_histogram_buffer[++committee_member_count]; const chain_property_object& cpo = get_chain_properties(); auto committee_members = sort_votable_objects(std::max(committee_member_count*2+1, (size_t)cpo.immutable_parameters.min_committee_member_count)); auto update_committee_member_total_votes = [this]( const committee_member_object& cm ) { modify( cm, [this]( committee_member_object& obj ) { obj.total_votes = _vote_tally_buffer[obj.vote_id]; }); }; if( _track_standby_votes ) { const auto& all_committee_members = get_index_type().indices(); for( const committee_member_object& cm : all_committee_members ) { update_committee_member_total_votes( cm ); } } else { for( const committee_member_object& cm : committee_members ) { update_committee_member_total_votes( cm ); } } // Update committee authorities if( !committee_members.empty() ) { modify(get(GRAPHENE_COMMITTEE_ACCOUNT), [&](account_object& a) { if( head_block_time() < HARDFORK_533_TIME ) { uint64_t total_votes = 0; map weights; a.active.weight_threshold = 0; a.active.clear(); for( const committee_member_object& del : committee_members ) { weights.emplace(del.committee_member_account, _vote_tally_buffer[del.vote_id]); total_votes += _vote_tally_buffer[del.vote_id]; } // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, // then I want to keep the most significant 16 bits of what's left. int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); for( const auto& weight : weights ) { // Ensure that everyone has at least one vote. Zero weights aren't allowed. uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) ); a.active.account_auths[weight.first] += votes; a.active.weight_threshold += votes; } a.active.weight_threshold /= 2; a.active.weight_threshold += 1; } else { vote_counter vc; for( const committee_member_object& cm : committee_members ) vc.add( cm.committee_member_account, std::max(_vote_tally_buffer[cm.vote_id], UINT64_C(1)) ); vc.finish( a.active ); } } ); modify(get(GRAPHENE_RELAXED_COMMITTEE_ACCOUNT), [&](account_object& a) { a.active = get(GRAPHENE_COMMITTEE_ACCOUNT).active; }); } modify(get_global_properties(), [&](global_property_object& gp) { gp.active_committee_members.clear(); std::transform(committee_members.begin(), committee_members.end(), std::inserter(gp.active_committee_members, gp.active_committee_members.begin()), [](const committee_member_object& d) { return d.id; }); }); } FC_CAPTURE_AND_RETHROW() } void database::update_active_sons() { try { if (head_block_time() < HARDFORK_SON_TIME) { return; } assert( _son_count_histogram_buffer.size() > 0 ); #ifndef NDEBUG for( const auto& son_count_histogram_buffer : _son_count_histogram_buffer ){ assert( son_count_histogram_buffer.second.size() > 0 ); } #endif const auto supported_active_sidechain_types = active_sidechain_types(head_block_time()); flat_map son_count; for(const auto& active_sidechain_type : supported_active_sidechain_types) { const share_type stake_target = (_total_voting_stake-_son_count_histogram_buffer.at(active_sidechain_type)[0]) / 2; /// accounts that vote for 0 or 1 son do not get to express an opinion on /// the number of sons to have (they abstain and are non-voting accounts) share_type stake_tally = 0; son_count[active_sidechain_type] = 0; if( stake_target > 0 ) { while( (son_count.at(active_sidechain_type) < _son_count_histogram_buffer.at(active_sidechain_type).size() - 1) && (stake_tally <= stake_target) ) { stake_tally += _son_count_histogram_buffer.at(active_sidechain_type)[ ++son_count[active_sidechain_type] ]; } } } const global_property_object& gpo = get_global_properties(); const chain_property_object& cpo = get_chain_properties(); const auto& all_sons = get_index_type().indices(); flat_map > > sons; for(const auto& active_sidechain_type : supported_active_sidechain_types) { if(head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) { sons[active_sidechain_type] = sort_votable_objects(active_sidechain_type, (std::max(son_count.at(active_sidechain_type) * 2 + 1, (size_t)cpo.immutable_parameters.min_son_count))); } else { sons[active_sidechain_type] = sort_votable_objects(sidechain_type::bitcoin, get_global_properties().parameters.maximum_son_count()); } } for( const son_object& son : all_sons ) { for(const auto& status: son.statuses) { const auto& sidechain = status.first; if(status.second == son_status::in_maintenance) { auto &stats = son.statistics(*this); modify(stats, [&](son_statistics_object &_s) { _s.last_down_timestamp[sidechain] = head_block_time(); }); } } modify( son, [this]( son_object& obj ){ for(const auto& sidechain_vote_id : obj.sidechain_vote_ids ){ obj.total_votes[sidechain_vote_id.first] = _vote_tally_buffer.size() > sidechain_vote_id.second ? _vote_tally_buffer[sidechain_vote_id.second] : 0; } for(auto& status: obj.statuses) { if (status.second == son_status::request_maintenance) status.second = son_status::in_maintenance; } }); } // Update SON authority if( gpo.parameters.son_account() != GRAPHENE_NULL_ACCOUNT ) { modify( get(gpo.parameters.son_account()), [&]( account_object& a ) { set account_ids; for(const auto& sidechain_sons : sons) { for( const son_object& son : sidechain_sons.second ) { account_ids.emplace(son.son_account); } } if( head_block_time() < HARDFORK_533_TIME ) { a.active.weight_threshold = 0; a.active.account_auths.clear(); for( const auto& account_id : account_ids ) { // Ensure that everyone has at least one vote. Zero weights aren't allowed. a.active.account_auths[account_id] += 1; a.active.weight_threshold += 1; } a.active.weight_threshold *= 2; a.active.weight_threshold /= 3; a.active.weight_threshold += 1; } else { vote_counter vc; for( const auto& account_id : account_ids ) { vc.add(account_id, UINT64_C(1)); } vc.finish_2_3( a.active ); } } ); } // Compare current and to-be lists of active sons const auto cur_active_sons = gpo.active_sons; flat_map > new_active_sons; const auto &acc = get(gpo.parameters.son_account()); for( const auto& sidechain_sons : sons ){ const auto& sidechain = sidechain_sons.first; const auto& sons_array = sidechain_sons.second; new_active_sons[sidechain].reserve(sons_array.size()); for( const son_object& son : sons_array ) { son_sidechain_info swi; swi.son_id = son.id; swi.weight = acc.active.account_auths.at(son.son_account); swi.signing_key = son.signing_key; if (son.sidechain_public_keys.find(sidechain) != son.sidechain_public_keys.end()) swi.public_key = son.sidechain_public_keys.at(sidechain); new_active_sons[sidechain].push_back(swi); } } bool son_sets_equal = (cur_active_sons.size() == new_active_sons.size()); if (son_sets_equal) { for( const auto& cur_active_sidechain_sons : cur_active_sons ){ const auto& sidechain = cur_active_sidechain_sons.first; const auto& _cur_active_sidechain_sons = cur_active_sidechain_sons.second; son_sets_equal = son_sets_equal && (_cur_active_sidechain_sons.size() == new_active_sons.at(sidechain).size()); if (son_sets_equal) { for (size_t i = 0; i < _cur_active_sidechain_sons.size(); i++) { son_sets_equal = son_sets_equal && (_cur_active_sidechain_sons.at(i) == new_active_sons.at(sidechain).at(i)); } } } } if (!son_sets_equal) { update_son_wallet(new_active_sons); update_son_statuses(cur_active_sons, new_active_sons); } // Update son performance metrics update_son_metrics(cur_active_sons); modify(gpo, [&]( global_property_object& gp ){ gp.active_sons.clear(); gp.active_sons.reserve(new_active_sons.size()); for( const auto& new_active_sidechain_sons : new_active_sons ) { const auto& sidechain = new_active_sidechain_sons.first; const auto& _new_active_sidechain_sons = new_active_sidechain_sons.second; gp.active_sons[sidechain].reserve(_new_active_sidechain_sons.size()); gp.active_sons[sidechain].insert(gp.active_sons[sidechain].end(), _new_active_sidechain_sons.cbegin(), _new_active_sidechain_sons.cend()); } }); for(const auto& active_sidechain_type : supported_active_sidechain_types) { const son_schedule_object& sidechain_sso = son_schedule_id_type(get_son_schedule_id(active_sidechain_type))(*this); modify(sidechain_sso, [&](son_schedule_object& _sso) { flat_set active_sons; active_sons.reserve(gpo.active_sons.at(active_sidechain_type).size()); std::transform(gpo.active_sons.at(active_sidechain_type).cbegin(), gpo.active_sons.at(active_sidechain_type).cend(), std::inserter(active_sons, active_sons.end()), [](const son_sidechain_info& swi) { return swi.son_id; }); _sso.scheduler.update(active_sons); // similar to witness, produce schedule for sons if( ((cur_active_sons.contains(active_sidechain_type) && cur_active_sons.at(active_sidechain_type).size() == 0) || !cur_active_sons.contains(active_sidechain_type)) && new_active_sons.at(active_sidechain_type).size() > 0 ) { witness_scheduler_rng rng(_sso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); for( size_t i=0; i>= GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS; share_type budget; if( budget_u128 < reserve.value ) rec.total_budget = share_type(budget_u128.to_uint64()); else rec.total_budget = reserve; return; } /** * Update the budget for witnesses and workers. */ void database::process_budget() { try { const global_property_object& gpo = get_global_properties(); const dynamic_global_property_object& dpo = get_dynamic_global_properties(); const asset_dynamic_data_object& core = get_core_dynamic_data(); fc::time_point_sec now = head_block_time(); int64_t time_to_maint = (dpo.next_maintenance_time - now).to_seconds(); // // The code that generates the next maintenance time should // only produce a result in the future. If this assert // fails, then the next maintenance time algorithm is buggy. // assert( time_to_maint > 0 ); // // Code for setting chain parameters should validate // block_interval > 0 (as well as the humans proposing / // voting on changes to block interval). // assert( gpo.parameters.block_interval > 0 ); uint64_t blocks_to_maint = (uint64_t(time_to_maint) + gpo.parameters.block_interval - 1) / gpo.parameters.block_interval; // blocks_to_maint > 0 because time_to_maint > 0, // which means numerator is at least equal to block_interval budget_record rec; initialize_budget_record( now, rec ); share_type available_funds = rec.total_budget; share_type witness_budget = gpo.parameters.witness_pay_per_block.value * blocks_to_maint; rec.requested_witness_budget = witness_budget; witness_budget = std::min(witness_budget, available_funds); rec.witness_budget = witness_budget; available_funds -= witness_budget; // We should not factor-in the son budget before SON HARDFORK share_type son_budget = 0; if(now >= HARDFORK_SON_TIME){ rec.leftover_son_funds = dpo.son_budget; available_funds += rec.leftover_son_funds; son_budget = gpo.parameters.son_pay_max(); son_budget = std::min(son_budget, available_funds); rec.son_budget = son_budget; available_funds -= son_budget; } fc::uint128_t worker_budget_u128 = gpo.parameters.worker_budget_per_day.value; worker_budget_u128 *= uint64_t(time_to_maint); worker_budget_u128 /= 60*60*24; share_type worker_budget; if( worker_budget_u128 >= available_funds.value ) worker_budget = available_funds; else worker_budget = worker_budget_u128.to_uint64(); rec.worker_budget = worker_budget; available_funds -= worker_budget; share_type leftover_worker_funds = worker_budget; pay_workers(leftover_worker_funds); rec.leftover_worker_funds = leftover_worker_funds; available_funds += leftover_worker_funds; rec.supply_delta = rec.witness_budget + rec.worker_budget + rec.son_budget - rec.leftover_worker_funds - rec.from_accumulated_fees - rec.from_unused_witness_budget - rec.leftover_son_funds; modify(core, [&]( asset_dynamic_data_object& _core ) { _core.current_supply = (_core.current_supply + rec.supply_delta ); assert( rec.supply_delta == witness_budget + worker_budget + son_budget - leftover_worker_funds - _core.accumulated_fees - dpo.witness_budget - dpo.son_budget ); _core.accumulated_fees = 0; }); modify(dpo, [&]( dynamic_global_property_object& _dpo ) { // Since initial witness_budget was rolled into // available_funds, we replace it with witness_budget // instead of adding it. _dpo.witness_budget = witness_budget; _dpo.son_budget = son_budget; _dpo.last_budget_time = now; }); create< budget_record_object >( [&]( budget_record_object& _rec ) { _rec.time = head_block_time(); _rec.record = rec; }); // available_funds is money we could spend, but don't want to. // we simply let it evaporate back into the reserve. } FC_CAPTURE_AND_RETHROW() } template< typename Visitor > void visit_special_authorities( const database& db, Visitor visit ) { const auto& sa_idx = db.get_index_type< special_authority_index >().indices().get(); for( const special_authority_object& sao : sa_idx ) { const account_object& acct = sao.account(db); if( acct.owner_special_authority.which() != special_authority::tag< no_special_authority >::value ) { visit( acct, true, acct.owner_special_authority ); } if( acct.active_special_authority.which() != special_authority::tag< no_special_authority >::value ) { visit( acct, false, acct.active_special_authority ); } } } void update_top_n_authorities( database& db ) { visit_special_authorities( db, [&]( const account_object& acct, bool is_owner, const special_authority& auth ) { if( auth.which() == special_authority::tag< top_holders_special_authority >::value ) { // use index to grab the top N holders of the asset and vote_counter to obtain the weights const top_holders_special_authority& tha = auth.get< top_holders_special_authority >(); vote_counter vc; const auto& bal_idx = db.get_index_type< account_balance_index >().indices().get< by_asset_balance >(); uint8_t num_needed = tha.num_top_holders; if( num_needed == 0 ) return; // find accounts const auto range = bal_idx.equal_range( boost::make_tuple( tha.asset ) ); for( const account_balance_object& bal : boost::make_iterator_range( range.first, range.second ) ) { assert( bal.asset_type == tha.asset ); if( bal.owner == acct.id ) continue; vc.add( bal.owner, bal.balance.value ); --num_needed; if( num_needed == 0 ) break; } db.modify( acct, [&]( account_object& a ) { vc.finish( is_owner ? a.owner : a.active ); if( !vc.is_empty() ) a.top_n_control_flags |= (is_owner ? account_object::top_n_control_owner : account_object::top_n_control_active); } ); } } ); } void split_fba_balance( database& db, uint64_t fba_id, uint16_t network_pct, uint16_t designated_asset_buyback_pct, uint16_t designated_asset_issuer_pct ) { FC_ASSERT( uint32_t(network_pct) + uint32_t(designated_asset_buyback_pct) + uint32_t(designated_asset_issuer_pct) == GRAPHENE_100_PERCENT ); const fba_accumulator_object& fba = fba_accumulator_id_type( fba_id )(db); if( fba.accumulated_fba_fees == 0 ) return; const asset_dynamic_data_object& core_dd = db.get_core_dynamic_data(); if( !fba.is_configured(db) ) { ilog( "${n} core given to network at block ${b} due to non-configured FBA", ("n", fba.accumulated_fba_fees)("b", db.head_block_time()) ); db.modify( core_dd, [&]( asset_dynamic_data_object& _core_dd ) { _core_dd.current_supply -= fba.accumulated_fba_fees; } ); db.modify( fba, [&]( fba_accumulator_object& _fba ) { _fba.accumulated_fba_fees = 0; } ); return; } fc::uint128_t buyback_amount_128 = fba.accumulated_fba_fees.value; buyback_amount_128 *= designated_asset_buyback_pct; buyback_amount_128 /= GRAPHENE_100_PERCENT; share_type buyback_amount = buyback_amount_128.to_uint64(); fc::uint128_t issuer_amount_128 = fba.accumulated_fba_fees.value; issuer_amount_128 *= designated_asset_issuer_pct; issuer_amount_128 /= GRAPHENE_100_PERCENT; share_type issuer_amount = issuer_amount_128.to_uint64(); // this assert should never fail FC_ASSERT( buyback_amount + issuer_amount <= fba.accumulated_fba_fees ); share_type network_amount = fba.accumulated_fba_fees - (buyback_amount + issuer_amount); const asset_object& designated_asset = (*fba.designated_asset)(db); if( network_amount != 0 ) { db.modify( core_dd, [&]( asset_dynamic_data_object& _core_dd ) { _core_dd.current_supply -= network_amount; } ); } fba_distribute_operation vop; vop.account_id = *designated_asset.buyback_account; vop.fba_id = fba.id; vop.amount = buyback_amount; if( vop.amount != 0 ) { db.adjust_balance( *designated_asset.buyback_account, asset(buyback_amount) ); db.push_applied_operation(vop); } vop.account_id = designated_asset.issuer; vop.fba_id = fba.id; vop.amount = issuer_amount; if( vop.amount != 0 ) { db.adjust_balance( designated_asset.issuer, asset(issuer_amount) ); db.push_applied_operation(vop); } db.modify( fba, [&]( fba_accumulator_object& _fba ) { _fba.accumulated_fba_fees = 0; } ); } void distribute_fba_balances( database& db ) { split_fba_balance( db, fba_accumulator_id_transfer_to_blind , 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT ); split_fba_balance( db, fba_accumulator_id_blind_transfer , 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT ); split_fba_balance( db, fba_accumulator_id_transfer_from_blind, 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT ); } void create_buyback_orders( database& db ) { const auto& bbo_idx = db.get_index_type< buyback_index >().indices().get(); const auto& bal_idx = db.get_index_type< primary_index< account_balance_index > >().get_secondary_index< balances_by_account_index >(); for( const buyback_object& bbo : bbo_idx ) { const asset_object& asset_to_buy = bbo.asset_to_buy(db); assert( asset_to_buy.buyback_account.valid() ); const account_object& buyback_account = (*(asset_to_buy.buyback_account))(db); if( !buyback_account.allowed_assets.valid() ) { wlog( "skipping buyback account ${b} at block ${n} because allowed_assets does not exist", ("b", buyback_account)("n", db.head_block_num()) ); continue; } for( const auto& entry : bal_idx.get_account_balances( buyback_account.id ) ) { const auto* it = entry.second; asset_id_type asset_to_sell = it->asset_type; share_type amount_to_sell = it->balance; if( asset_to_sell == asset_to_buy.id ) continue; if( amount_to_sell == 0 ) continue; if( buyback_account.allowed_assets->find( asset_to_sell ) == buyback_account.allowed_assets->end() ) { wlog( "buyback account ${b} not selling disallowed holdings of asset ${a} at block ${n}", ("b", buyback_account)("a", asset_to_sell)("n", db.head_block_num()) ); continue; } try { transaction_evaluation_state buyback_context(&db); buyback_context.skip_fee_schedule_check = true; limit_order_create_operation create_vop; create_vop.fee = asset( 0, asset_id_type() ); create_vop.seller = buyback_account.id; create_vop.amount_to_sell = asset( amount_to_sell, asset_to_sell ); create_vop.min_to_receive = asset( 1, asset_to_buy.id ); create_vop.expiration = time_point_sec::maximum(); create_vop.fill_or_kill = false; limit_order_id_type order_id = db.apply_operation( buyback_context, create_vop ).get< object_id_type >(); if( db.find( order_id ) != nullptr ) { limit_order_cancel_operation cancel_vop; cancel_vop.fee = asset( 0, asset_id_type() ); cancel_vop.order = order_id; cancel_vop.fee_paying_account = buyback_account.id; db.apply_operation( buyback_context, cancel_vop ); } } catch( const fc::exception& e ) { // we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account wlog( "Skipping buyback processing selling ${as} for ${ab} for buyback account ${b} at block ${n}; exception was ${e}", ("as", asset_to_sell)("ab", asset_to_buy)("b", buyback_account)("n", db.head_block_num())("e", e.to_detail_string()) ); continue; } } } return; } void deprecate_annual_members( database& db ) { const auto& account_idx = db.get_index_type().indices().get(); fc::time_point_sec now = db.head_block_time(); for( const account_object& acct : account_idx ) { try { transaction_evaluation_state upgrade_context(&db); upgrade_context.skip_fee_schedule_check = true; if( acct.is_annual_member( now ) ) { account_upgrade_operation upgrade_vop; upgrade_vop.fee = asset( 0, asset_id_type() ); upgrade_vop.account_to_upgrade = acct.id; upgrade_vop.upgrade_to_lifetime_member = true; db.apply_operation( upgrade_context, upgrade_vop ); } } catch( const fc::exception& e ) { // we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account wlog( "Skipping annual member deprecate processing for account ${a} (${an}) at block ${n}; exception was ${e}", ("a", acct.id)("an", acct.name)("n", db.head_block_num())("e", e.to_detail_string()) ); continue; } } return; } uint32_t database::get_gpos_current_subperiod() { if(this->head_block_time() < HARDFORK_GPOS_TIME) //Can be deleted after GPOS hardfork time return 0; fc::time_point_sec last_date_voted; const auto &gpo = this->get_global_properties(); const auto vesting_period = gpo.parameters.gpos_period(); const auto vesting_subperiod = gpo.parameters.gpos_subperiod(); const auto period_start = fc::time_point_sec(gpo.parameters.gpos_period_start()); // variables needed const auto number_of_subperiods = vesting_period / vesting_subperiod; const auto now = this->head_block_time(); auto seconds_since_period_start = now.sec_since_epoch() - period_start.sec_since_epoch(); // get in what sub period we are uint32_t current_subperiod = 0; std::list period_list(number_of_subperiods); std::iota(period_list.begin(), period_list.end(), 1); std::for_each(period_list.begin(), period_list.end(),[&](uint32_t period) { if(seconds_since_period_start >= vesting_subperiod * (period - 1) && seconds_since_period_start < vesting_subperiod * period) current_subperiod = period; }); return current_subperiod; } double database::calculate_vesting_factor(const account_object& stake_account) { fc::time_point_sec last_date_voted; // get last time voted form account stats // check last_vote_time of proxy voting account if proxy is set if (stake_account.options.voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT) last_date_voted = stake_account.statistics(*this).last_vote_time; else last_date_voted = stake_account.options.voting_account(*this).statistics(*this).last_vote_time; // get global data related to gpos const auto &gpo = this->get_global_properties(); const auto vesting_period = gpo.parameters.gpos_period(); const auto vesting_subperiod = gpo.parameters.gpos_subperiod(); const auto period_start = fc::time_point_sec(gpo.parameters.gpos_period_start()); // variables needed const auto number_of_subperiods = vesting_period / vesting_subperiod; double vesting_factor; // get in what sub period we are uint32_t current_subperiod = get_gpos_current_subperiod(); if(current_subperiod == 0 || current_subperiod > number_of_subperiods) return 0; // On starting new vesting period, all votes become zero until someone votes, To avoid a situation of zero votes, // changes were done to roll in GPOS rules, the vesting factor will be 1 for whoever votes in 6th sub-period of last vesting period // BLOCKBACK-174 fix if(current_subperiod == 1 && this->head_block_time() >= HARDFORK_GPOS_TIME + vesting_period) //Applicable only from 2nd vesting period { if(last_date_voted > period_start - vesting_subperiod) return 1; } if(last_date_voted < period_start) return 0; double numerator = number_of_subperiods; if(current_subperiod > 1) { std::list subperiod_list(current_subperiod - 1); std::iota(subperiod_list.begin(), subperiod_list.end(), 2); subperiod_list.reverse(); for(auto subperiod: subperiod_list) { numerator--; auto last_period_start = period_start + fc::seconds(vesting_subperiod * (subperiod - 1)); auto last_period_end = period_start + fc::seconds(vesting_subperiod * (subperiod)); if (last_date_voted > last_period_start && last_date_voted <= last_period_end) { numerator++; break; } } } vesting_factor = numerator / number_of_subperiods; return vesting_factor; } share_type credit_account(database& db, const account_id_type owner_id, const std::string owner_name, share_type remaining_amount_to_distribute, const share_type shares_to_credit, const asset_id_type payout_asset_type, const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index, const asset_id_type dividend_id) { //wdump((delta_balance.value)(holder_balance)(total_balance_of_dividend_asset)); if (shares_to_credit.value) { remaining_amount_to_distribute -= shares_to_credit; dlog("Crediting account ${account} with ${amount}", ("account", owner_name) ("amount", asset(shares_to_credit, payout_asset_type))); auto pending_payout_iter = pending_payout_balance_index.indices().get().find( boost::make_tuple(dividend_id, payout_asset_type, owner_id)); if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) db.create( [&](pending_dividend_payout_balance_for_holder_object &obj) { obj.owner = owner_id; obj.dividend_holder_asset_type = dividend_id; obj.dividend_payout_asset_type = payout_asset_type; obj.pending_balance = shares_to_credit; }); else db.modify(*pending_payout_iter, [&](pending_dividend_payout_balance_for_holder_object &pending_balance) { pending_balance.pending_balance += shares_to_credit; }); } return remaining_amount_to_distribute; } void rolling_period_start(database& db) { if(db.head_block_time() >= HARDFORK_GPOS_TIME) { const auto gpo = db.get_global_properties(); auto period_start = db.get_global_properties().parameters.gpos_period_start(); const auto vesting_period = db.get_global_properties().parameters.gpos_period(); const auto now = db.head_block_time(); while(now.sec_since_epoch() >= (period_start + vesting_period)) { // roll db.modify(db.get_global_properties(), [period_start, vesting_period](global_property_object& p) { p.parameters.extensions.value.gpos_period_start = period_start + vesting_period; }); period_start = db.get_global_properties().parameters.gpos_period_start(); } } } void clear_expired_custom_account_authorities(database& db) { const auto& cindex = db.get_index_type().indices().get(); while(!cindex.empty() && cindex.begin()->valid_to < db.head_block_time()) { db.remove(*cindex.begin()); } } void clear_expired_account_roles(database& db) { const auto& arindex = db.get_index_type().indices().get(); while(!arindex.empty() && arindex.begin()->valid_to < db.head_block_time()) { db.remove(*arindex.begin()); } } // Schedules payouts from a dividend distribution account to the current holders of the // dividend-paying asset. This takes any deposits made to the dividend distribution account // since the last time it was called, and distributes them to the current owners of the // dividend-paying asset according to the amount they own. void schedule_pending_dividend_balances(database& db, const asset_object& dividend_holder_asset_obj, const asset_dividend_data_object& dividend_data, const fc::time_point_sec& current_head_block_time, const account_balance_index& balance_index, const vesting_balance_index& vesting_index, const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index, const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index) { try { dlog("Processing dividend payments for dividend holder asset type ${holder_asset} at time ${t}", ("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time())); auto balance_by_acc_index = db.get_index_type< primary_index< account_balance_index > >().get_secondary_index< balances_by_account_index >(); auto current_distribution_account_balance_range = //balance_index.indices().get().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account)); balance_by_acc_index.get_account_balances(dividend_data.dividend_distribution_account); auto previous_distribution_account_balance_range = distributed_dividend_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); // the current range is now all current balances for the distribution account, sorted by asset_type // the previous range is now all previous balances for this account, sorted by asset type const auto& gpo = db.get_global_properties(); // get the list of accounts that hold nonzero balances of the dividend asset auto holder_balances_begin = balance_index.indices().get().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id)); auto holder_balances_end = balance_index.indices().get().upper_bound(boost::make_tuple(dividend_holder_asset_obj.id, share_type())); uint64_t distribution_base_fee = gpo.parameters.current_fees->get().distribution_base_fee; uint32_t distribution_fee_per_holder = gpo.parameters.current_fees->get().distribution_fee_per_holder; std::map vesting_amounts; auto balance_type = vesting_balance_type::normal; if(db.head_block_time() >= HARDFORK_GPOS_TIME) balance_type = vesting_balance_type::gpos; uint32_t holder_account_count = 0; // get only once a collection of accounts that hold nonzero vesting balances of the dividend asset auto vesting_balances_begin = vesting_index.indices().get().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id, balance_type)); auto vesting_balances_end = vesting_index.indices().get().upper_bound(boost::make_tuple(dividend_holder_asset_obj.id, balance_type, share_type())); for (const vesting_balance_object& vesting_balance_obj : boost::make_iterator_range(vesting_balances_begin, vesting_balances_end)) { vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount; ++holder_account_count; dlog("Vesting balance for account: ${owner}, amount: ${amount}", ("owner", vesting_balance_obj.owner(db).name) ("amount", vesting_balance_obj.balance.amount)); } auto current_distribution_account_balance_iter = current_distribution_account_balance_range.begin(); if(db.head_block_time() < HARDFORK_GPOS_TIME) holder_account_count = std::distance(holder_balances_begin, holder_balances_end); // the fee, in BTS, for distributing each asset in the account uint64_t total_fee_per_asset_in_core = distribution_base_fee + holder_account_count * (uint64_t)distribution_fee_per_holder; //auto current_distribution_account_balance_iter = current_distribution_account_balance_range.first; auto previous_distribution_account_balance_iter = previous_distribution_account_balance_range.first; dlog("Current balances in distribution account: ${current}, Previous balances: ${previous}", ("current", (int64_t)std::distance(current_distribution_account_balance_range.begin(), current_distribution_account_balance_range.end())) ("previous", (int64_t)std::distance(previous_distribution_account_balance_range.first, previous_distribution_account_balance_range.second))); // when we pay out the dividends to the holders, we need to know the total balance of the dividend asset in all // accounts other than the distribution account (it would be silly to distribute dividends back to // the distribution account) share_type total_balance_of_dividend_asset; if(db.head_block_time() >= HARDFORK_GPOS_TIME && dividend_holder_asset_obj.symbol == GRAPHENE_SYMBOL) { // only core for (const vesting_balance_object &holder_balance_object : boost::make_iterator_range(vesting_balances_begin, vesting_balances_end)) if (holder_balance_object.owner != dividend_data.dividend_distribution_account) { total_balance_of_dividend_asset += holder_balance_object.balance.amount; } } else { for (const account_balance_object &holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end)) if (holder_balance_object.owner != dividend_data.dividend_distribution_account) { total_balance_of_dividend_asset += holder_balance_object.balance; auto itr = vesting_amounts.find(holder_balance_object.owner); if (itr != vesting_amounts.end()) total_balance_of_dividend_asset += itr->second; } } // loop through all of the assets currently or previously held in the distribution account while (current_distribution_account_balance_iter != current_distribution_account_balance_range.end() || previous_distribution_account_balance_iter != previous_distribution_account_balance_range.second) { try { // First, figure out how much the balance on this asset has changed since the last sharing out share_type current_balance; share_type previous_balance; asset_id_type payout_asset_type; if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || current_distribution_account_balance_iter->second->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type) { // there are no more previous balances or there is no previous balance for this particular asset type payout_asset_type = current_distribution_account_balance_iter->second->asset_type; current_balance = current_distribution_account_balance_iter->second->balance; idump((payout_asset_type)(current_balance)); } else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.end() || previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->second->asset_type) { // there are no more current balances or there is no current balance for this particular previous asset type payout_asset_type = previous_distribution_account_balance_iter->dividend_payout_asset_type; previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; idump((payout_asset_type)(previous_balance)); } else { // we have both a previous and a current balance for this asset type payout_asset_type = current_distribution_account_balance_iter->second->asset_type; current_balance = current_distribution_account_balance_iter->second->balance; previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval; idump((payout_asset_type)(current_balance)(previous_balance)); } share_type delta_balance = current_balance - previous_balance; // Next, figure out if we want to share this out -- if the amount added to the distribution // account since last payout is too small, we won't bother. share_type total_fee_per_asset_in_payout_asset; const asset_object* payout_asset_object = nullptr; if (payout_asset_type == asset_id_type()) { payout_asset_object = &db.get_core_asset(); total_fee_per_asset_in_payout_asset = total_fee_per_asset_in_core; dlog("Fee for distributing ${payout_asset_type}: ${fee}", ("payout_asset_type", asset_id_type()(db).symbol) ("fee", asset(total_fee_per_asset_in_core, asset_id_type()))); } else { // figure out what the total fee is in terms of the payout asset const asset_index& asset_object_index = db.get_index_type(); auto payout_asset_object_iter = asset_object_index.indices().find(payout_asset_type); FC_ASSERT(payout_asset_object_iter != asset_object_index.indices().end()); payout_asset_object = &*payout_asset_object_iter; asset total_fee_per_asset = asset(total_fee_per_asset_in_core, asset_id_type()) * payout_asset_object->options.core_exchange_rate; FC_ASSERT(total_fee_per_asset.asset_id == payout_asset_type); total_fee_per_asset_in_payout_asset = total_fee_per_asset.amount; dlog("Fee for distributing ${payout_asset_type}: ${fee}", ("payout_asset_type", payout_asset_type(db).symbol)("fee", total_fee_per_asset_in_payout_asset)); } share_type minimum_shares_to_distribute; if (dividend_data.options.minimum_fee_percentage) { fc::uint128_t minimum_amount_to_distribute = total_fee_per_asset_in_payout_asset.value; minimum_amount_to_distribute *= 100 * GRAPHENE_1_PERCENT; minimum_amount_to_distribute /= dividend_data.options.minimum_fee_percentage; wdump((total_fee_per_asset_in_payout_asset)(dividend_data.options)); minimum_shares_to_distribute = minimum_amount_to_distribute.to_uint64(); } dlog("Processing dividend payments of asset type ${payout_asset_type}, delta balance is ${delta_balance}", ("payout_asset_type", payout_asset_type(db).symbol)("delta_balance", delta_balance)); if (delta_balance > 0) { if (delta_balance >= minimum_shares_to_distribute) { // first, pay the fee for scheduling these dividend payments if (payout_asset_type == asset_id_type()) { // pay fee to network db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) { d.accumulated_fees += total_fee_per_asset_in_core; }); db.adjust_balance(dividend_data.dividend_distribution_account, asset(-total_fee_per_asset_in_core, asset_id_type())); delta_balance -= total_fee_per_asset_in_core; } else { const asset_dynamic_data_object& dynamic_data = payout_asset_object->dynamic_data(db); if (dynamic_data.fee_pool < total_fee_per_asset_in_core) FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} " "because insufficient funds in fee pool (need: ${need}, have: ${have})", ("holder_asset_type", dividend_holder_asset_obj.symbol) ("payout_asset_type", payout_asset_object->symbol) ("need", asset(total_fee_per_asset_in_core, asset_id_type())) ("have", asset(dynamic_data.fee_pool, payout_asset_type))); // deduct the fee from the dividend distribution account db.adjust_balance(dividend_data.dividend_distribution_account, asset(-total_fee_per_asset_in_payout_asset, payout_asset_type)); // convert it to core db.modify(payout_asset_object->dynamic_data(db), [total_fee_per_asset_in_core, total_fee_per_asset_in_payout_asset](asset_dynamic_data_object& d) { d.fee_pool -= total_fee_per_asset_in_core; d.accumulated_fees += total_fee_per_asset_in_payout_asset; }); // and pay it to the network db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) { d.accumulated_fees += total_fee_per_asset_in_core; }); delta_balance -= total_fee_per_asset_in_payout_asset; } dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}", ("count", holder_account_count) ("total", total_balance_of_dividend_asset)); share_type remaining_amount_to_distribute = delta_balance; if(db.head_block_time() >= HARDFORK_GPOS_TIME && dividend_holder_asset_obj.symbol == GRAPHENE_SYMBOL) { // core only // credit each account with their portion, don't send any back to the dividend distribution account for (const vesting_balance_object &holder_balance_object : boost::make_iterator_range( vesting_balances_begin, vesting_balances_end)) { if (holder_balance_object.owner == dividend_data.dividend_distribution_account) continue; auto vesting_factor = db.calculate_vesting_factor(holder_balance_object.owner(db)); auto holder_balance = holder_balance_object.balance; fc::uint128_t amount_to_credit(delta_balance.value); amount_to_credit *= holder_balance.amount.value; amount_to_credit /= total_balance_of_dividend_asset.value; share_type full_shares_to_credit((int64_t) amount_to_credit.to_uint64()); share_type shares_to_credit = (uint64_t) floor(full_shares_to_credit.value * vesting_factor); if (shares_to_credit < full_shares_to_credit) { // Todo: sending results of decay to committee account, need to change to specified account dlog("Crediting committee_account with ${amount}", ("amount", asset(full_shares_to_credit - shares_to_credit, payout_asset_type))); db.adjust_balance(dividend_data.dividend_distribution_account, -asset(full_shares_to_credit - shares_to_credit, payout_asset_type)); db.adjust_balance(account_id_type(0), asset(full_shares_to_credit - shares_to_credit, payout_asset_type)); } remaining_amount_to_distribute = credit_account(db, holder_balance_object.owner, holder_balance_object.owner(db).name, remaining_amount_to_distribute, shares_to_credit, payout_asset_type, pending_payout_balance_index, dividend_holder_asset_obj.id); } } else { // credit each account with their portion, don't send any back to the dividend distribution account for (const account_balance_object &holder_balance_object : boost::make_iterator_range( holder_balances_begin, holder_balances_end)) { if (holder_balance_object.owner == dividend_data.dividend_distribution_account) continue; auto holder_balance = holder_balance_object.balance; auto itr = vesting_amounts.find(holder_balance_object.owner); if (itr != vesting_amounts.end()) holder_balance += itr->second; fc::uint128_t amount_to_credit(delta_balance.value); amount_to_credit *= holder_balance.value; amount_to_credit /= total_balance_of_dividend_asset.value; share_type shares_to_credit((int64_t) amount_to_credit.to_uint64()); remaining_amount_to_distribute = credit_account(db, holder_balance_object.owner, holder_balance_object.owner(db).name, remaining_amount_to_distribute, shares_to_credit, payout_asset_type, pending_payout_balance_index, dividend_holder_asset_obj.id); } } for (const auto& pending_payout : pending_payout_balance_index.indices()) if (pending_payout.pending_balance.value) dlog("Pending payout: ${account_name} -> ${amount}", ("account_name", pending_payout.owner(db).name) ("amount", asset(pending_payout.pending_balance, pending_payout.dividend_payout_asset_type))); dlog("Remaining balance not paid out: ${amount}", ("amount", asset(remaining_amount_to_distribute, payout_asset_type))); share_type distributed_amount = delta_balance - remaining_amount_to_distribute; if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type) db.create( [&]( total_distributed_dividend_balance_object& obj ){ obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; obj.dividend_payout_asset_type = payout_asset_type; obj.balance_at_last_maintenance_interval = distributed_amount; }); else db.modify(*previous_distribution_account_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){ obj.balance_at_last_maintenance_interval += distributed_amount; }); } else FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} " "because amount ${delta_balance} is too small an amount to distribute.", ("holder_asset_type", dividend_holder_asset_obj.symbol) ("payout_asset_type", payout_asset_object->symbol) ("delta_balance", asset(delta_balance, payout_asset_type))); } else if (delta_balance < 0) { // some amount of the asset has been withdrawn from the dividend_distribution_account, // meaning the current pending payout balances will add up to more than our current balance. // This should be extremely rare (caused by an override transfer by the asset owner). // Reduce all pending payouts proportionally share_type total_pending_balances; auto pending_payouts_range = pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); for (const pending_dividend_payout_balance_for_holder_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) total_pending_balances += pending_balance_object.pending_balance; share_type remaining_amount_to_recover = -delta_balance; share_type remaining_pending_balances = total_pending_balances; for (const pending_dividend_payout_balance_for_holder_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) { fc::uint128_t amount_to_debit(remaining_amount_to_recover.value); amount_to_debit *= pending_balance_object.pending_balance.value; amount_to_debit /= remaining_pending_balances.value; share_type shares_to_debit((int64_t)amount_to_debit.to_uint64()); remaining_amount_to_recover -= shares_to_debit; remaining_pending_balances -= pending_balance_object.pending_balance; db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){ pending_balance.pending_balance -= shares_to_debit; }); } // if we're here, we know there must be a previous balance, so just adjust it by the // amount we just reclaimed db.modify(*previous_distribution_account_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){ obj.balance_at_last_maintenance_interval += delta_balance; assert(obj.balance_at_last_maintenance_interval == current_balance); }); } // end if deposit was large enough to distribute } catch (const fc::exception& e) { dlog("${e}", ("e", e)); } // iterate if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second || current_distribution_account_balance_iter->second->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type) ++current_distribution_account_balance_iter; else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.end() || previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->second->asset_type) ++previous_distribution_account_balance_iter; else { ++current_distribution_account_balance_iter; ++previous_distribution_account_balance_iter; } } db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) { dividend_data_obj.last_scheduled_distribution_time = current_head_block_time; dividend_data_obj.last_distribution_time = current_head_block_time; }); } FC_CAPTURE_AND_RETHROW() } void process_dividend_assets(database& db) { try { ilog("In process_dividend_assets time ${time}", ("time", db.head_block_time())); const account_balance_index& balance_index = db.get_index_type(); //const auto& balance_index = db.get_index_type< primary_index< account_balance_index > >().get_secondary_index< balances_by_account_index >(); const vesting_balance_index& vbalance_index = db.get_index_type(); const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index = db.get_index_type(); const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index = db.get_index_type(); // TODO: switch to iterating over only dividend assets (generalize the by_type index) for( const asset_object& dividend_holder_asset_obj : db.get_index_type().indices() ) if (dividend_holder_asset_obj.dividend_data_id) { const asset_dividend_data_object& dividend_data = dividend_holder_asset_obj.dividend_data(db); const account_object& dividend_distribution_account_object = dividend_data.dividend_distribution_account(db); fc::time_point_sec current_head_block_time = db.head_block_time(); schedule_pending_dividend_balances(db, dividend_holder_asset_obj, dividend_data, current_head_block_time, balance_index, vbalance_index, distributed_dividend_balance_index, pending_payout_balance_index); if (dividend_data.options.next_payout_time && db.head_block_time() >= *dividend_data.options.next_payout_time) { try { dlog("Dividend payout time has arrived for asset ${holder_asset}", ("holder_asset", dividend_holder_asset_obj.symbol)); #ifndef NDEBUG // dump balances before the payouts for debugging const auto& balance_index = db.get_index_type< primary_index< account_balance_index > >(); const auto& balances = balance_index.get_secondary_index< balances_by_account_index >().get_account_balances( dividend_data.dividend_distribution_account ); for( const auto balance : balances ) ilog(" Current balance: ${asset}", ("asset", asset(balance.second->balance, balance.second->asset_type))); #endif // when we do the payouts, we first increase the balances in all of the receiving accounts // and use this map to keep track of the total amount of each asset paid out. // Afterwards, we decrease the distribution account's balance by the total amount paid out, // and modify the distributed_balances accordingly std::map amounts_paid_out_by_asset; auto pending_payouts_range = pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); // the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account // we iterate in this order so we can build up a list of payouts for each account to put in the // virtual op vector payouts_for_this_holder; fc::optional last_holder_account_id; // cache the assets the distribution account is approved to send, we will be asking // for these often flat_map approved_assets; // assets that the dividend distribution account is authorized to send/receive auto is_asset_approved_for_distribution_account = [&](const asset_id_type& asset_id) { auto approved_assets_iter = approved_assets.find(asset_id); if (approved_assets_iter != approved_assets.end()) return approved_assets_iter->second; bool is_approved = is_authorized_asset(db, dividend_distribution_account_object, asset_id(db)); approved_assets[asset_id] = is_approved; return is_approved; }; for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; ) { const pending_dividend_payout_balance_for_holder_object& pending_balance_object = *pending_balance_object_iter; if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner && payouts_for_this_holder.size()) { // we've moved on to a new account, generate the dividend payment virtual op for the previous one db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, *last_holder_account_id, payouts_for_this_holder)); dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); payouts_for_this_holder.clear(); last_holder_account_id.reset(); } if (pending_balance_object.pending_balance.value && is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) && is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type)) { dlog("Processing payout of ${asset} to account ${account}", ("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)) ("account", pending_balance_object.owner(db).name)); db.adjust_balance(pending_balance_object.owner, asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)); payouts_for_this_holder.push_back(asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)); last_holder_account_id = pending_balance_object.owner; amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance; db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){ pending_balance.pending_balance = 0; }); } ++pending_balance_object_iter; } // we will always be left with the last holder's data, generate the virtual op for it now. if (last_holder_account_id && payouts_for_this_holder.size()) { // we've moved on to a new account, generate the dividend payment virtual op for the previous one db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, *last_holder_account_id, payouts_for_this_holder)); dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); } // now debit the total amount of dividends paid out from the distribution account // and reduce the distributed_balances accordingly for (const auto& value : amounts_paid_out_by_asset) { const asset_id_type& asset_paid_out = value.first; const share_type& amount_paid_out = value.second; db.adjust_balance(dividend_data.dividend_distribution_account, asset(-amount_paid_out, asset_paid_out)); auto distributed_balance_iter = distributed_dividend_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, asset_paid_out)); assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()); if (distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()) db.modify(*distributed_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){ obj.balance_at_last_maintenance_interval -= amount_paid_out; // now they've been paid out, reset to zero }); } // now schedule the next payout time db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) { dividend_data_obj.last_scheduled_payout_time = dividend_data_obj.options.next_payout_time; dividend_data_obj.last_payout_time = current_head_block_time; fc::optional next_payout_time; if (dividend_data_obj.options.payout_interval) { // if there was a previous payout, make our next payment one interval uint32_t current_time_sec = current_head_block_time.sec_since_epoch(); uint32_t next_possible_time_sec = dividend_data_obj.last_scheduled_payout_time->sec_since_epoch(); do next_possible_time_sec += *dividend_data_obj.options.payout_interval; while (next_possible_time_sec <= current_time_sec); next_payout_time = next_possible_time_sec; } dividend_data_obj.options.next_payout_time = next_payout_time; idump((dividend_data_obj.last_scheduled_payout_time) (dividend_data_obj.last_payout_time) (dividend_data_obj.options.next_payout_time)); }); } FC_RETHROW_EXCEPTIONS(error, "Error while paying out dividends for holder asset ${holder_asset}", ("holder_asset", dividend_holder_asset_obj.symbol)) } } } FC_CAPTURE_AND_RETHROW() } void database::perform_son_tasks() { const global_property_object& gpo = get_global_properties(); if(gpo.parameters.son_account() == GRAPHENE_NULL_ACCOUNT && head_block_time() >= HARDFORK_SON_TIME) { const auto& son_account = create([&](account_object& a) { a.name = "son-account"; a.statistics = create([&a](account_statistics_object& s){ s.owner = a.id; s.name = a.name; }).id; a.owner.weight_threshold = 1; a.active.weight_threshold = 0; a.registrar = a.lifetime_referrer = a.referrer = a.id; a.membership_expiration_date = time_point_sec::maximum(); a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; }); modify( gpo, [&son_account]( global_property_object& gpo ) { gpo.parameters.extensions.value.son_account = son_account.get_id(); if( gpo.pending_parameters ) gpo.pending_parameters->extensions.value.son_account = son_account.get_id(); }); } // create BTC asset here because son_account is the issuer of the BTC if (gpo.parameters.btc_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_TIME) { const asset_dynamic_data_object& dyn_asset = create([](asset_dynamic_data_object& a) { a.current_supply = 0; }); const asset_object& btc_asset = create( [&gpo, &dyn_asset]( asset_object& a ) { a.symbol = "BTC"; a.precision = 8; a.issuer = gpo.parameters.son_account(); a.options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; a.options.market_fee_percent = 500; // 5% a.options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; a.options.flags = asset_issuer_permission_flags::charge_market_fee | asset_issuer_permission_flags::override_authority; a.options.core_exchange_rate.base.amount = 100000; a.options.core_exchange_rate.base.asset_id = asset_id_type(0); a.options.core_exchange_rate.quote.amount = 2500; a.options.core_exchange_rate.quote.asset_id = a.id; a.options.whitelist_authorities.clear(); // accounts allowed to use asset, if not empty a.options.blacklist_authorities.clear(); // accounts who can blacklist other accounts to use asset, if white_list flag is set a.options.whitelist_markets.clear(); // might be traded with a.options.blacklist_markets.clear(); // might not be traded with a.dynamic_asset_data_id = dyn_asset.id; }); modify( gpo, [&btc_asset]( global_property_object& gpo ) { gpo.parameters.extensions.value.btc_asset = btc_asset.get_id(); if( gpo.pending_parameters ) gpo.pending_parameters->extensions.value.btc_asset = btc_asset.get_id(); }); } // create ETH asset here because son_account is the issuer of the ETH if (gpo.parameters.eth_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) { const asset_dynamic_data_object& dyn_asset = create([](asset_dynamic_data_object& a) { a.current_supply = 0; }); const asset_object& eth_asset = create( [&gpo, &dyn_asset]( asset_object& a ) { a.symbol = "ETH"; a.precision = 8; a.issuer = gpo.parameters.son_account(); a.options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; a.options.market_fee_percent = 500; // 5% a.options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; a.options.flags = asset_issuer_permission_flags::charge_market_fee | asset_issuer_permission_flags::override_authority; a.options.core_exchange_rate.base.amount = 100000; a.options.core_exchange_rate.base.asset_id = asset_id_type(0); a.options.core_exchange_rate.quote.amount = 2500; a.options.core_exchange_rate.quote.asset_id = a.id; a.options.whitelist_authorities.clear(); // accounts allowed to use asset, if not empty a.options.blacklist_authorities.clear(); // accounts who can blacklist other accounts to use asset, if white_list flag is set a.options.whitelist_markets.clear(); // might be traded with a.options.blacklist_markets.clear(); // might not be traded with a.dynamic_asset_data_id = dyn_asset.id; }); modify( gpo, [ð_asset]( global_property_object& gpo ) { gpo.parameters.extensions.value.eth_asset = eth_asset.get_id(); if( gpo.pending_parameters ) gpo.pending_parameters->extensions.value.eth_asset = eth_asset.get_id(); }); } // create HBD asset here because son_account is the issuer of the HBD if (gpo.parameters.hbd_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_FOR_HIVE_TIME) { const asset_dynamic_data_object& dyn_asset = create([](asset_dynamic_data_object& a) { a.current_supply = 0; }); const asset_object& hbd_asset = create( [&gpo, &dyn_asset]( asset_object& a ) { a.symbol = "HBD"; a.precision = 3; a.issuer = gpo.parameters.son_account(); a.options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; a.options.market_fee_percent = 500; // 5% a.options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; a.options.flags = asset_issuer_permission_flags::charge_market_fee | asset_issuer_permission_flags::override_authority; a.options.core_exchange_rate.base.amount = 100000; a.options.core_exchange_rate.base.asset_id = asset_id_type(0); a.options.core_exchange_rate.quote.amount = 2500; a.options.core_exchange_rate.quote.asset_id = a.id; a.options.whitelist_authorities.clear(); // accounts allowed to use asset, if not empty a.options.blacklist_authorities.clear(); // accounts who can blacklist other accounts to use asset, if white_list flag is set a.options.whitelist_markets.clear(); // might be traded with a.options.blacklist_markets.clear(); // might not be traded with a.dynamic_asset_data_id = dyn_asset.id; }); modify( gpo, [&hbd_asset]( global_property_object& gpo ) { gpo.parameters.extensions.value.hbd_asset = hbd_asset.get_id(); if( gpo.pending_parameters ) gpo.pending_parameters->extensions.value.hbd_asset = hbd_asset.get_id(); }); } // create HIVE asset here because son_account is the issuer of the HIVE if (gpo.parameters.hive_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_FOR_HIVE_TIME) { const asset_dynamic_data_object& dyn_asset = create([](asset_dynamic_data_object& a) { a.current_supply = 0; }); const asset_object& hive_asset = create( [&gpo, &dyn_asset]( asset_object& a ) { a.symbol = "HIVE"; a.precision = 3; a.issuer = gpo.parameters.son_account(); a.options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; a.options.market_fee_percent = 500; // 5% a.options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; a.options.flags = asset_issuer_permission_flags::charge_market_fee | asset_issuer_permission_flags::override_authority; a.options.core_exchange_rate.base.amount = 100000; a.options.core_exchange_rate.base.asset_id = asset_id_type(0); a.options.core_exchange_rate.quote.amount = 2500; a.options.core_exchange_rate.quote.asset_id = a.id; a.options.whitelist_authorities.clear(); // accounts allowed to use asset, if not empty a.options.blacklist_authorities.clear(); // accounts who can blacklist other accounts to use asset, if white_list flag is set a.options.whitelist_markets.clear(); // might be traded with a.options.blacklist_markets.clear(); // might not be traded with a.dynamic_asset_data_id = dyn_asset.id; }); modify( gpo, [&hive_asset]( global_property_object& gpo ) { gpo.parameters.extensions.value.hive_asset = hive_asset.get_id(); if( gpo.pending_parameters ) gpo.pending_parameters->extensions.value.hive_asset = hive_asset.get_id(); }); } // Pay the SONs if (head_block_time() >= HARDFORK_SON_TIME) { // Before making a budget we should pay out SONs // This function should check if its time to pay sons // and modify the global son funds accordingly, whatever is left is passed on to next budget if(head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME) pay_sons_before_hf_ethereum(); else pay_sons_after_hf_ethereum(); } // Split vote_ids if (head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) { // Get SON 1.33.0 and check if it has HIVE vote_id const son_id_type sid = son_id_type(0); const auto p_son = find(sid); if(p_son != nullptr) { if (p_son->sidechain_vote_ids.find(sidechain_type::hive) == p_son->sidechain_vote_ids.end()) { // Add vote_ids for HIVE and ETHEREUM to all existing SONs const auto &all_sons = get_index_type().indices().get(); for (const son_object &son : all_sons) { const auto existing_vote_id_bitcoin = son.get_bitcoin_vote_id(); vote_id_type new_vote_id_hive; vote_id_type new_vote_id_eth; modify(gpo, [&new_vote_id_hive, &new_vote_id_eth](global_property_object &p) { new_vote_id_hive = get_next_vote_id(p, vote_id_type::son_hive); new_vote_id_eth = get_next_vote_id(p, vote_id_type::son_ethereum); }); modify(son, [new_vote_id_hive, new_vote_id_eth](son_object &obj) { obj.sidechain_vote_ids[sidechain_type::hive] = new_vote_id_hive; obj.sidechain_vote_ids[sidechain_type::ethereum] = new_vote_id_eth; }); // Duplicate all votes from bitcoin to hive const auto &all_accounts = get_index_type().indices().get(); for (const auto &account : all_accounts) { if (existing_vote_id_bitcoin.valid() && account.options.votes.count(*existing_vote_id_bitcoin) != 0) { modify(account, [new_vote_id_hive](account_object &a) { a.options.votes.insert(new_vote_id_hive); }); } } } } } } } void update_son_params(database& db) { if( (db.head_block_time() >= HARDFORK_SON2_TIME) && (db.head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME) ) { const auto& gpo = db.get_global_properties(); db.modify( gpo, []( global_property_object& gpo ) { gpo.parameters.extensions.value.maximum_son_count = 7; }); } if( (db.head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) ) { const auto& gpo = db.get_global_properties(); db.modify( gpo, []( global_property_object& gpo ) { gpo.parameters.extensions.value.maximum_son_count = GRAPHENE_DEFAULT_MAX_SONS; }); } } void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { try { const auto& gpo = get_global_properties(); distribute_fba_balances(*this); create_buyback_orders(*this); process_dividend_assets(*this); rolling_period_start(*this); update_son_params(*this); struct vote_tally_helper { database& d; const global_property_object& props; std::map vesting_amounts; vote_tally_helper(database& d, const global_property_object& gpo) : d(d), props(gpo) { d._vote_tally_buffer.resize(props.next_available_vote_id); d._witness_count_histogram_buffer.resize(props.parameters.maximum_witness_count / 2 + 1); d._committee_count_histogram_buffer.resize(props.parameters.maximum_committee_count / 2 + 1); for( auto& son_count_histogram_buffer : d._son_count_histogram_buffer ){ son_count_histogram_buffer.second.resize(props.parameters.maximum_son_count() / 2 + 1); } d._total_voting_stake = 0; auto balance_type = vesting_balance_type::normal; if(d.head_block_time() >= HARDFORK_GPOS_TIME) balance_type = vesting_balance_type::gpos; const vesting_balance_index& vesting_index = d.get_index_type(); auto vesting_balances_begin = vesting_index.indices().get().lower_bound(boost::make_tuple(asset_id_type(), balance_type)); auto vesting_balances_end = vesting_index.indices().get().upper_bound(boost::make_tuple(asset_id_type(), balance_type, share_type())); for (const vesting_balance_object& vesting_balance_obj : boost::make_iterator_range(vesting_balances_begin, vesting_balances_end)) { vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount; dlog("Vesting balance for account: ${owner}, amount: ${amount}", ("owner", vesting_balance_obj.owner(d).name) ("amount", vesting_balance_obj.balance.amount)); } } void operator()( const account_object& stake_account, const account_statistics_object& stats ) { if( props.parameters.count_non_member_votes || stake_account.is_member(d.head_block_time()) ) { // There may be a difference between the account whose stake is voting and the one specifying opinions. // Usually they're the same, but if the stake account has specified a voting_account, that account is the one // specifying the opinions. const account_object* opinion_account_ptr = (stake_account.options.voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT)? &stake_account : d.find(stake_account.options.voting_account); if( !opinion_account_ptr ) // skip non-exist account return; const account_object& opinion_account = *opinion_account_ptr; const auto& stats = stake_account.statistics(d); uint64_t voting_stake = 0; auto itr = vesting_amounts.find(stake_account.id); if (itr != vesting_amounts.end()) voting_stake += itr->second.value; if(d.head_block_time() >= HARDFORK_GPOS_TIME) { if (itr == vesting_amounts.end() && d.head_block_time() >= (HARDFORK_GPOS_TIME + props.parameters.gpos_subperiod()/2)) return; auto vesting_factor = d.calculate_vesting_factor(stake_account); voting_stake = (uint64_t)floor(voting_stake * vesting_factor); //Include votes(based on stake) for the period of gpos_subperiod()/2 as system has zero votes on GPOS activation if(d.head_block_time() < (HARDFORK_GPOS_TIME + props.parameters.gpos_subperiod()/2)) { voting_stake += stats.total_core_in_orders.value + (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value : 0) + d.get_balance(stake_account.get_id(), asset_id_type()).amount.value; } } else { voting_stake += stats.total_core_in_orders.value + (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value : 0) + d.get_balance(stake_account.get_id(), asset_id_type()).amount.value; } for( vote_id_type id : opinion_account.options.votes ) { uint32_t offset = id.instance(); // if they somehow managed to specify an illegal offset, ignore it. if( offset < d._vote_tally_buffer.size() ) d._vote_tally_buffer[offset] += voting_stake; } if( opinion_account.options.num_witness <= props.parameters.maximum_witness_count ) { uint16_t offset = std::min(size_t(opinion_account.options.num_witness/2), d._witness_count_histogram_buffer.size() - 1); // votes for a number greater than maximum_witness_count // are turned into votes for maximum_witness_count. // // in particular, this takes care of the case where a // member was voting for a high number, then the // parameter was lowered. d._witness_count_histogram_buffer[offset] += voting_stake; } if( opinion_account.options.num_committee <= props.parameters.maximum_committee_count ) { uint16_t offset = std::min(size_t(opinion_account.options.num_committee/2), d._committee_count_histogram_buffer.size() - 1); // votes for a number greater than maximum_committee_count // are turned into votes for maximum_committee_count. // // same rationale as for witnesses d._committee_count_histogram_buffer[offset] += voting_stake; } if ( opinion_account.options.extensions.value.num_son.valid() ) { for(const auto& num_sidechain_son : *opinion_account.options.extensions.value.num_son) { const auto sidechain = num_sidechain_son.first; const auto& num_son = num_sidechain_son.second; if (num_son <= props.parameters.maximum_son_count()) { uint16_t offset = std::min(size_t(num_son / 2), d._son_count_histogram_buffer.at(sidechain).size() - 1); // votes for a number greater than maximum_son_count // are turned into votes for maximum_son_count. // // in particular, this takes care of the case where a // member was voting for a high number, then the // parameter was lowered. d._son_count_histogram_buffer.at(sidechain)[offset] += voting_stake; } } } d._total_voting_stake += voting_stake; } } } tally_helper(*this, gpo); perform_account_maintenance( tally_helper ); struct clear_canary { clear_canary(vector& target): target(target){} ~clear_canary() { target.clear(); } private: vector& target; }; struct clear_canary_map { clear_canary_map(flat_map >& target): target(target){} ~clear_canary_map() { for(auto& sidechain_target : target){ sidechain_target.second.clear(); } } private: flat_map >& target; }; clear_canary a(_witness_count_histogram_buffer), b(_committee_count_histogram_buffer), c(_vote_tally_buffer); clear_canary_map d{_son_count_histogram_buffer}; perform_son_tasks(); update_top_n_authorities(*this); update_active_witnesses(); update_active_committee_members(); update_active_sons(); update_worker_votes(); const dynamic_global_property_object& dgpo = get_dynamic_global_properties(); modify(gpo, [&dgpo](global_property_object& p) { // Remove scaling of account registration fee p.parameters.current_fees->get().basic_fee >>= p.parameters.account_fee_scale_bitshifts * (dgpo.accounts_registered_this_interval / p.parameters.accounts_per_fee_scale); if( p.pending_parameters ) { if( !p.pending_parameters->extensions.value.min_bet_multiplier.valid() ) p.pending_parameters->extensions.value.min_bet_multiplier = p.parameters.extensions.value.min_bet_multiplier; if( !p.pending_parameters->extensions.value.max_bet_multiplier.valid() ) p.pending_parameters->extensions.value.max_bet_multiplier = p.parameters.extensions.value.max_bet_multiplier; if( !p.pending_parameters->extensions.value.betting_rake_fee_percentage.valid() ) p.pending_parameters->extensions.value.betting_rake_fee_percentage = p.parameters.extensions.value.betting_rake_fee_percentage; if( !p.pending_parameters->extensions.value.permitted_betting_odds_increments.valid() ) p.pending_parameters->extensions.value.permitted_betting_odds_increments = p.parameters.extensions.value.permitted_betting_odds_increments; if( !p.pending_parameters->extensions.value.live_betting_delay_time.valid() ) p.pending_parameters->extensions.value.live_betting_delay_time = p.parameters.extensions.value.live_betting_delay_time; if( !p.pending_parameters->extensions.value.gpos_period_start.valid() ) p.pending_parameters->extensions.value.gpos_period_start = p.parameters.extensions.value.gpos_period_start; if( !p.pending_parameters->extensions.value.gpos_period.valid() ) p.pending_parameters->extensions.value.gpos_period = p.parameters.extensions.value.gpos_period; if( !p.pending_parameters->extensions.value.gpos_subperiod.valid() ) p.pending_parameters->extensions.value.gpos_subperiod = p.parameters.extensions.value.gpos_subperiod; if( !p.pending_parameters->extensions.value.gpos_vesting_lockin_period.valid() ) p.pending_parameters->extensions.value.gpos_vesting_lockin_period = p.parameters.extensions.value.gpos_vesting_lockin_period; if( !p.pending_parameters->extensions.value.rbac_max_permissions_per_account.valid() ) p.pending_parameters->extensions.value.rbac_max_permissions_per_account = p.parameters.extensions.value.rbac_max_permissions_per_account; if( !p.pending_parameters->extensions.value.rbac_max_account_authority_lifetime.valid() ) p.pending_parameters->extensions.value.rbac_max_account_authority_lifetime = p.parameters.extensions.value.rbac_max_account_authority_lifetime; if( !p.pending_parameters->extensions.value.rbac_max_authorities_per_permission.valid() ) p.pending_parameters->extensions.value.rbac_max_authorities_per_permission = p.parameters.extensions.value.rbac_max_authorities_per_permission; if( !p.pending_parameters->extensions.value.account_roles_max_per_account.valid() ) p.pending_parameters->extensions.value.account_roles_max_per_account = p.parameters.extensions.value.account_roles_max_per_account; if( !p.pending_parameters->extensions.value.account_roles_max_lifetime.valid() ) p.pending_parameters->extensions.value.account_roles_max_lifetime = p.parameters.extensions.value.account_roles_max_lifetime; if( !p.pending_parameters->extensions.value.son_vesting_amount.valid() ) p.pending_parameters->extensions.value.son_vesting_amount = p.parameters.extensions.value.son_vesting_amount; if( !p.pending_parameters->extensions.value.son_vesting_period.valid() ) p.pending_parameters->extensions.value.son_vesting_period = p.parameters.extensions.value.son_vesting_period; if( !p.pending_parameters->extensions.value.son_pay_max.valid() ) p.pending_parameters->extensions.value.son_pay_max = p.parameters.extensions.value.son_pay_max; if( !p.pending_parameters->extensions.value.son_pay_time.valid() ) p.pending_parameters->extensions.value.son_pay_time = p.parameters.extensions.value.son_pay_time; if( !p.pending_parameters->extensions.value.son_deregister_time.valid() ) p.pending_parameters->extensions.value.son_deregister_time = p.parameters.extensions.value.son_deregister_time; if( !p.pending_parameters->extensions.value.son_heartbeat_frequency.valid() ) p.pending_parameters->extensions.value.son_heartbeat_frequency = p.parameters.extensions.value.son_heartbeat_frequency; if( !p.pending_parameters->extensions.value.son_down_time.valid() ) p.pending_parameters->extensions.value.son_down_time = p.parameters.extensions.value.son_down_time; if( !p.pending_parameters->extensions.value.son_bitcoin_min_tx_confirmations.valid() ) p.pending_parameters->extensions.value.son_bitcoin_min_tx_confirmations = p.parameters.extensions.value.son_bitcoin_min_tx_confirmations; if( !p.pending_parameters->extensions.value.son_account.valid() ) p.pending_parameters->extensions.value.son_account = p.parameters.extensions.value.son_account; if( !p.pending_parameters->extensions.value.btc_asset.valid() ) p.pending_parameters->extensions.value.btc_asset = p.parameters.extensions.value.btc_asset; if( !p.pending_parameters->extensions.value.maximum_son_count.valid() ) p.pending_parameters->extensions.value.maximum_son_count = p.parameters.extensions.value.maximum_son_count; if( !p.pending_parameters->extensions.value.hbd_asset.valid() ) p.pending_parameters->extensions.value.hbd_asset = p.parameters.extensions.value.hbd_asset; if( !p.pending_parameters->extensions.value.hive_asset.valid() ) p.pending_parameters->extensions.value.hive_asset = p.parameters.extensions.value.hive_asset; if( !p.pending_parameters->extensions.value.eth_asset.valid() ) p.pending_parameters->extensions.value.eth_asset = p.parameters.extensions.value.eth_asset; // the following parameters are not allowed to be changed. So take what is in global property p.pending_parameters->extensions.value.gpos_period_start = p.parameters.extensions.value.gpos_period_start; p.pending_parameters->extensions.value.son_account = p.parameters.extensions.value.son_account; p.pending_parameters->extensions.value.btc_asset = p.parameters.extensions.value.btc_asset; p.pending_parameters->extensions.value.maximum_son_count = p.parameters.extensions.value.maximum_son_count; p.pending_parameters->extensions.value.hbd_asset = p.parameters.extensions.value.hbd_asset; p.pending_parameters->extensions.value.hive_asset = p.parameters.extensions.value.hive_asset; p.pending_parameters->extensions.value.eth_asset = p.parameters.extensions.value.eth_asset; p.parameters = std::move(*p.pending_parameters); p.pending_parameters.reset(); } }); auto next_maintenance_time = dgpo.next_maintenance_time; auto maintenance_interval = gpo.parameters.maintenance_interval; if( next_maintenance_time <= next_block.timestamp ) { if( next_block.block_num() == 1 ) next_maintenance_time = time_point_sec() + (((next_block.timestamp.sec_since_epoch() / maintenance_interval) + 1) * maintenance_interval); else { // We want to find the smallest k such that next_maintenance_time + k * maintenance_interval > head_block_time() // This implies k > ( head_block_time() - next_maintenance_time ) / maintenance_interval // // Let y be the right-hand side of this inequality, i.e. // y = ( head_block_time() - next_maintenance_time ) / maintenance_interval // // and let the fractional part f be y-floor(y). Clearly 0 <= f < 1. // We can rewrite f = y-floor(y) as floor(y) = y-f. // // Clearly k = floor(y)+1 has k > y as desired. Now we must // show that this is the least such k, i.e. k-1 <= y. // // But k-1 = floor(y)+1-1 = floor(y) = y-f <= y. // So this k suffices. // auto y = (head_block_time() - next_maintenance_time).to_seconds() / maintenance_interval; next_maintenance_time += (y+1) * maintenance_interval; } } if( (dgpo.next_maintenance_time < HARDFORK_613_TIME) && (next_maintenance_time >= HARDFORK_613_TIME) ) deprecate_annual_members(*this); modify(dgpo, [next_maintenance_time](dynamic_global_property_object& d) { d.next_maintenance_time = next_maintenance_time; d.accounts_registered_this_interval = 0; }); // Reset all BitAsset force settlement volumes to zero //for( const asset_bitasset_data_object* d : get_index_type() ) for( const auto& d : get_index_type().indices() ) modify( d, [](asset_bitasset_data_object& o) { o.force_settled_volume = 0; }); // Ideally we have to do this after every block but that leads to longer block applicaiton/replay times. // So keep it here as it is not critical. valid_to check ensures // these custom account auths and account roles are not usable. clear_expired_custom_account_authorities(*this); clear_expired_account_roles(*this); // process_budget needs to run at the bottom because // it needs to know the next_maintenance_time process_budget(); } FC_CAPTURE_AND_RETHROW() } } }