/** * @file * @copyright defined in eos/LICENSE.txt */ #include "eosio.system.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace eosiosystem { using eosio::indexed_by; using eosio::const_mem_fun; using eosio::bytes; using eosio::print; using eosio::singleton; using eosio::transaction; /** * This method will create a producer_config and producer_info object for 'producer' * * @pre producer is not already registered * @pre producer to register is an account * @pre authority of producer to register * */ void system_contract::regproducer( const account_name producer, const eosio::public_key& producer_key, const std::string& url, uint16_t location ) { eosio_assert( url.size() < 512, "url too long" ); eosio_assert( producer_key != eosio::public_key(), "public key should not be the default value" ); require_auth( producer ); auto prod = _producers.find( producer ); if ( prod != _producers.end() ) { if( producer_key != prod->producer_key ) { _producers.modify( prod, producer, [&]( producer_info& info ){ info.producer_key = producer_key; info.is_active = true; info.url = url; info.location = location; }); } } else { _producers.emplace( producer, [&]( producer_info& info ){ info.owner = producer; info.total_votes = 0; info.producer_key = producer_key; info.is_active = true; info.url = url; info.location = location; }); } } void system_contract::unregprod( const account_name producer ) { require_auth( producer ); const auto& prod = _producers.get( producer, "producer not found" ); _producers.modify( prod, 0, [&]( producer_info& info ){ info.deactivate(); }); } void system_contract::update_elected_producers( block_timestamp block_time ) { _gstate.last_producer_schedule_update = block_time; auto idx = _producers.get_index(); std::vector< std::pair > top_producers; top_producers.reserve(21); std::vector inactive_iters; for ( auto it = idx.cbegin(); it != idx.cend() && top_producers.size() < 21 && 0 < it->total_votes && it->active(); ++it ) { /** If it's the first time or it's been over a day since a producer was last voted in, update his info. Otherwise, a producer gets a grace period of 7 hours after which he gets deactivated if he hasn't produced in 24 hours. */ if ( it->time_became_active.slot == 0 || block_time.slot > it->time_became_active.slot + 23 * blocks_per_hour ) { _producers.modify( *it, 0, [&](auto& p) { p.time_became_active = block_time; }); } else if ( block_time.slot > (2 * 21 * 12 * 100) + it->time_became_active.slot && block_time.slot > it->last_produced_block_time.slot + blocks_per_day ) { // save producers that will be deactivated inactive_iters.push_back(it); continue; } else { _producers.modify( *it, 0, [&](auto& p) { p.time_became_active = block_time; }); } top_producers.emplace_back( std::pair({{it->owner, it->producer_key}, it->location}) ); } if ( top_producers.size() < _gstate.last_producer_schedule_size ) { return; } for ( const auto& it: inactive_iters ) { _producers.modify( *it, 0, [&](auto& p) { p.deactivate(); p.time_became_active.slot = 0; }); } /// sort by producer name std::sort( top_producers.begin(), top_producers.end() ); std::vector producers; producers.reserve(top_producers.size()); for( const auto& item : top_producers ) producers.push_back(item.first); bytes packed_schedule = pack(producers); checksum160 new_id; sha1( packed_schedule.data(), packed_schedule.size(), &new_id ); if( new_id != _gstate.last_producer_schedule_id ) { _gstate.last_producer_schedule_id = new_id; set_proposed_producers( packed_schedule.data(), packed_schedule.size() ); _gstate.last_producer_schedule_size = top_producers.size(); } _gstate.last_producer_schedule_update = block_time; } double stake2vote( int64_t staked ) { /// TODO subtract 2080 brings the large numbers closer to this decade double weight = int64_t( (now() - (block_timestamp::block_timestamp_epoch / 1000)) / (seconds_per_day * 7) ) / double( 52 ); return double(staked) * std::pow( 2, weight ); } /** * @pre producers must be sorted from lowest to highest and must be registered and active * @pre if proxy is set then no producers can be voted for * @pre if proxy is set then proxy account must exist and be registered as a proxy * @pre every listed producer or proxy must have been previously registered * @pre voter must authorize this action * @pre voter must have previously staked some EOS for voting * @pre voter->staked must be up to date * * @post every producer previously voted for will have vote reduced by previous vote weight * @post every producer newly voted for will have vote increased by new vote amount * @post prior proxy will proxied_vote_weight decremented by previous vote weight * @post new proxy will proxied_vote_weight incremented by new vote weight * * If voting for a proxy, the producer votes will not change until the proxy updates their own vote. */ void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector& producers ) { update_votes( voter_name, proxy, producers, true ); } void system_contract::update_votes( const account_name voter_name, const account_name proxy, const std::vector& producers, bool voting ) { require_auth( voter_name ); //validate input if ( proxy ) { eosio_assert( producers.size() == 0, "cannot vote for producers and proxy at same time" ); eosio_assert( voter_name != proxy, "cannot proxy to self" ); require_recipient( proxy ); } else { eosio_assert( producers.size() <= 30, "attempt to vote for too many producers" ); for( size_t i = 1; i < producers.size(); ++i ) { eosio_assert( producers[i-1] < producers[i], "producer votes must be unique and sorted" ); } } auto voter = _voters.find(voter_name); eosio_assert( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object eosio_assert( !proxy || !voter->is_proxy, "account registered as a proxy is not allowed to use a proxy" ); /** * The first time someone votes we calculate and set last_vote_weight, since they cannot unstake until * after total_activated_stake hits threshold, we can use last_vote_weight to determine that this is * their first vote and should consider their stake activated. */ if( voter->last_vote_weight <= 0.0 ) { _gstate.total_activated_stake += voter->staked; if( _gstate.total_activated_stake >= min_activated_stake ) { _gstate.thresh_activated_stake_time = current_time(); } } auto new_vote_weight = stake2vote( voter->staked ); if( voter->is_proxy ) { new_vote_weight += voter->proxied_vote_weight; } boost::container::flat_map > producer_deltas; if ( voter->last_vote_weight > 0 ) { if( voter->proxy ) { auto old_proxy = _voters.find( voter->proxy ); eosio_assert( old_proxy != _voters.end(), "old proxy not found" ); //data corruption _voters.modify( old_proxy, 0, [&]( auto& vp ) { vp.proxied_vote_weight -= voter->last_vote_weight; }); propagate_weight_change( *old_proxy ); } else { for( const auto& p : voter->producers ) { auto& d = producer_deltas[p]; d.first -= voter->last_vote_weight; d.second = false; } } } if( proxy ) { auto new_proxy = _voters.find( proxy ); eosio_assert( new_proxy != _voters.end(), "invalid proxy specified" ); //if ( !voting ) { data corruption } else { wrong vote } eosio_assert( !voting || new_proxy->is_proxy, "proxy not found" ); if ( new_vote_weight >= 0 ) { _voters.modify( new_proxy, 0, [&]( auto& vp ) { vp.proxied_vote_weight += new_vote_weight; }); propagate_weight_change( *new_proxy ); } } else { if( new_vote_weight >= 0 ) { for( const auto& p : producers ) { auto& d = producer_deltas[p]; d.first += new_vote_weight; d.second = true; } } } for( const auto& pd : producer_deltas ) { auto pitr = _producers.find( pd.first ); if( pitr != _producers.end() ) { eosio_assert( !voting || pitr->active() || !pd.second.second /* not from new set */, "producer is not currently registered" ); _producers.modify( pitr, 0, [&]( auto& p ) { p.total_votes += pd.second.first; if ( p.total_votes < 0 ) { // floating point arithmetics can give small negative numbers p.total_votes = 0; } _gstate.total_producer_vote_weight += pd.second.first; //eosio_assert( p.total_votes >= 0, "something bad happened" ); }); } else { eosio_assert( !pd.second.second /* not from new set */, "producer is not registered" ); //data corruption } } _voters.modify( voter, 0, [&]( auto& av ) { av.last_vote_weight = new_vote_weight; av.producers = producers; av.proxy = proxy; }); } /** * An account marked as a proxy can vote with the weight of other accounts which * have selected it as a proxy. Other accounts must refresh their voteproducer to * update the proxy's weight. * * @param isproxy - true if proxy wishes to vote on behalf of others, false otherwise * @pre proxy must have something staked (existing row in voters table) * @pre new state must be different than current state */ void system_contract::regproxy( const account_name proxy, bool isproxy ) { require_auth( proxy ); auto pitr = _voters.find(proxy); if ( pitr != _voters.end() ) { eosio_assert( isproxy != pitr->is_proxy, "action has no effect" ); eosio_assert( !isproxy || !pitr->proxy, "account that uses a proxy is not allowed to become a proxy" ); _voters.modify( pitr, 0, [&]( auto& p ) { p.is_proxy = isproxy; }); propagate_weight_change( *pitr ); } else { _voters.emplace( proxy, [&]( auto& p ) { p.owner = proxy; p.is_proxy = isproxy; }); } } void system_contract::propagate_weight_change( const voter_info& voter ) { eosio_assert( voter.proxy == 0 || !voter.is_proxy, "account registered as a proxy is not allowed to use a proxy" ); double new_weight = stake2vote( voter.staked ); if ( voter.is_proxy ) { new_weight += voter.proxied_vote_weight; } /// don't propagate small changes (1 ~= epsilon) if ( fabs( new_weight - voter.last_vote_weight ) > 1 ) { if ( voter.proxy ) { auto& proxy = _voters.get( voter.proxy, "proxy not found" ); //data corruption _voters.modify( proxy, 0, [&]( auto& p ) { p.proxied_vote_weight += new_weight - voter.last_vote_weight; } ); propagate_weight_change( proxy ); } else { auto delta = new_weight - voter.last_vote_weight; for ( auto acnt : voter.producers ) { auto& pitr = _producers.get( acnt, "producer not found" ); //data corruption _producers.modify( pitr, 0, [&]( auto& p ) { p.total_votes += delta; _gstate.total_producer_vote_weight += delta; }); } } } _voters.modify( voter, 0, [&]( auto& v ) { v.last_vote_weight = new_weight; } ); } } /// namespace eosiosystem