voting.cpp 13.0 KB
Newer Older
1 2 3 4 5 6 7
/**
 *  @file
 *  @copyright defined in eos/LICENSE.txt
 */
#include "eosio.system.hpp"

#include <eosiolib/eosio.hpp>
D
Daniel Larimer 已提交
8
#include <eosiolib/crypto.h>
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
#include <eosiolib/print.hpp>
#include <eosiolib/datastream.hpp>
#include <eosiolib/serialize.hpp>
#include <eosiolib/multi_index.hpp>
#include <eosiolib/privileged.hpp>
#include <eosiolib/singleton.hpp>
#include <eosiolib/transaction.hpp>
#include <eosio.token/eosio.token.hpp>

#include <algorithm>
#include <cmath>

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
    *
    */
37
   void system_contract::regproducer( const account_name producer, const eosio::public_key& producer_key, const std::string& url, uint16_t location ) {
38
      eosio_assert( url.size() < 512, "url too long" );
39
      eosio_assert( producer_key != eosio::public_key(), "public key should not be the default value" );
40 41
      require_auth( producer );

D
Daniel Larimer 已提交
42
      auto prod = _producers.find( producer );
43

D
Daniel Larimer 已提交
44
      if ( prod != _producers.end() ) {
45
         if( producer_key != prod->producer_key ) {
D
Daniel Larimer 已提交
46
             _producers.modify( prod, producer, [&]( producer_info& info ){
47
                  info.producer_key = producer_key;
48
                  info.is_active    = true;
49 50
                  info.url          = url;
                  info.location     = location;
D
Daniel Larimer 已提交
51
             });
52
         }
53
      } else {
D
Daniel Larimer 已提交
54
         _producers.emplace( producer, [&]( producer_info& info ){
55 56
               info.owner         = producer;
               info.total_votes   = 0;
57
               info.producer_key  = producer_key;
58
               info.is_active     = true;
59 60
               info.url           = url;
               info.location      = location;
D
Daniel Larimer 已提交
61
         });
62 63 64 65 66 67
      }
   }

   void system_contract::unregprod( const account_name producer ) {
      require_auth( producer );

68
      const auto& prod = _producers.get( producer, "producer not found" );
69

D
Daniel Larimer 已提交
70
      _producers.modify( prod, 0, [&]( producer_info& info ){
71
            info.deactivate();
D
Daniel Larimer 已提交
72
      });
73 74
   }

K
Khaled Al-Hassanieh 已提交
75
   void system_contract::update_elected_producers( block_timestamp block_time ) {
D
Daniel Larimer 已提交
76 77
      _gstate.last_producer_schedule_update = block_time;

D
Daniel Larimer 已提交
78
      auto idx = _producers.get_index<N(prototalvote)>();
79

D
Daniel Larimer 已提交
80 81
      std::vector< std::pair<eosio::producer_key,uint16_t> > top_producers;
      top_producers.reserve(21);
K
Khaled Al-Hassanieh 已提交
82

83 84
      std::vector<decltype(idx.cbegin())> inactive_iters;
      for ( auto it = idx.cbegin(); it != idx.cend() && top_producers.size() < 21 && 0 < it->total_votes && it->active(); ++it ) {
85 86 87 88 89 90 91

         /**
            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 ||
92
              block_time.slot > it->time_became_active.slot + 23 * blocks_per_hour ) {
93 94 95
            _producers.modify( *it, 0, [&](auto& p) {
                  p.time_became_active = block_time;
               });
G
Greg Lee 已提交
96
         } else if ( block_time.slot > (2 * 21 * 12 * 100) + it->time_became_active.slot &&
K
Khaled Al-Hassanieh 已提交
97
                     block_time.slot > it->last_produced_block_time.slot + blocks_per_day ) {
98 99
            // save producers that will be deactivated
            inactive_iters.push_back(it);
100 101 102 103 104 105
            continue;
         } else {
            _producers.modify( *it, 0, [&](auto& p) {
                  p.time_became_active = block_time;
               });
         }
106

107
         top_producers.emplace_back( std::pair<eosio::producer_key,uint16_t>({{it->owner, it->producer_key}, it->location}) );
D
Daniel Larimer 已提交
108
      }
109

110 111 112 113
      if ( top_producers.size() < _gstate.last_producer_schedule_size ) {
         return;
      }

114
      for ( const auto& it: inactive_iters ) {
115 116 117 118 119
         _producers.modify( *it, 0, [&](auto& p) {
               p.deactivate();
               p.time_became_active.slot = 0;
            });
      }
K
Khaled Al-Hassanieh 已提交
120

D
Daniel Larimer 已提交
121 122
      /// sort by producer name
      std::sort( top_producers.begin(), top_producers.end() );
K
Khaled Al-Hassanieh 已提交
123

D
Daniel Larimer 已提交
124
      std::vector<eosio::producer_key> producers;
K
Khaled Al-Hassanieh 已提交
125

D
Daniel Larimer 已提交
126 127 128
      producers.reserve(top_producers.size());
      for( const auto& item : top_producers )
         producers.push_back(item.first);
129

D
Daniel Larimer 已提交
130 131 132
      bytes packed_schedule = pack(producers);
      checksum160 new_id;
      sha1( packed_schedule.data(), packed_schedule.size(), &new_id );
133

D
Daniel Larimer 已提交
134 135
      if( new_id != _gstate.last_producer_schedule_id ) {
         _gstate.last_producer_schedule_id = new_id;
136
         set_proposed_producers( packed_schedule.data(),  packed_schedule.size() );
137
         _gstate.last_producer_schedule_size = top_producers.size();
D
Daniel Larimer 已提交
138
      }
139
      _gstate.last_producer_schedule_update = block_time;
140 141
   }

142
   double stake2vote( int64_t staked ) {
143
      /// TODO subtract 2080 brings the large numbers closer to this decade
A
Andrianto Lie 已提交
144
      double weight = int64_t( (now() - (block_timestamp::block_timestamp_epoch / 1000)) / (seconds_per_day * 7) )  / double( 52 );
145 146
      return double(staked) * std::pow( 2, weight );
   }
147
   /**
148
    *  @pre producers must be sorted from lowest to highest and must be registered and active
149
    *  @pre if proxy is set then no producers can be voted for
150
    *  @pre if proxy is set then proxy account must exist and be registered as a proxy
151 152 153
    *  @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
154 155
    *  @pre voter->staked must be up to date
    *
156
    *  @post every producer previously voted for will have vote reduced by previous vote weight
157 158 159 160 161
    *  @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.
162
    */
D
Daniel Larimer 已提交
163
   void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector<account_name>& producers ) {
164 165 166 167
      update_votes( voter_name, proxy, producers, true );
   }

   void system_contract::update_votes( const account_name voter_name, const account_name proxy, const std::vector<account_name>& producers, bool voting ) {
D
Daniel Larimer 已提交
168
      require_auth( voter_name );
169 170 171 172

      //validate input
      if ( proxy ) {
         eosio_assert( producers.size() == 0, "cannot vote for producers and proxy at same time" );
D
Daniel Larimer 已提交
173
         eosio_assert( voter_name != proxy, "cannot proxy to self" );
174 175 176 177 178 179 180 181
         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" );
         }
      }

D
Daniel Larimer 已提交
182 183
      auto voter = _voters.find(voter_name);
      eosio_assert( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object
184
      eosio_assert( !proxy || !voter->is_proxy, "account registered as a proxy is not allowed to use a proxy" );
D
Daniel Larimer 已提交
185

186 187
      /**
       * The first time someone votes we calculate and set last_vote_weight, since they cannot unstake until
K
Khaled Al-Hassanieh 已提交
188
       * after total_activated_stake hits threshold, we can use last_vote_weight to determine that this is
189 190 191
       * their first vote and should consider their stake activated.
       */
      if( voter->last_vote_weight <= 0.0 ) {
K
Khaled Al-Hassanieh 已提交
192
         _gstate.total_activated_stake += voter->staked;
193 194 195
         if( _gstate.total_activated_stake >= min_activated_stake ) {
            _gstate.thresh_activated_stake_time = current_time();
         }
196 197
      }

198
      auto new_vote_weight = stake2vote( voter->staked );
199 200 201
      if( voter->is_proxy ) {
         new_vote_weight += voter->proxied_vote_weight;
      }
D
Daniel Larimer 已提交
202

203
      boost::container::flat_map<account_name, pair<double, bool /*new*/> > producer_deltas;
204
      if ( voter->last_vote_weight > 0 ) {
205 206 207 208 209 210 211 212 213 214 215 216 217
         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;
            }
D
Daniel Larimer 已提交
218
         }
219
      }
D
Daniel Larimer 已提交
220

221
      if( proxy ) {
222
         auto new_proxy = _voters.find( proxy );
223 224
         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" );
225 226 227 228 229 230 231 232 233 234 235 236 237 238
         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;
            }
         }
239
      }
D
Daniel Larimer 已提交
240

241 242 243
      for( const auto& pd : producer_deltas ) {
         auto pitr = _producers.find( pd.first );
         if( pitr != _producers.end() ) {
244
            eosio_assert( !voting || pitr->active() || !pd.second.second /* not from new set */, "producer is not currently registered" );
245
            _producers.modify( pitr, 0, [&]( auto& p ) {
246
               p.total_votes += pd.second.first;
247
               if ( p.total_votes < 0 ) { // floating point arithmetics can give small negative numbers
248 249
                  p.total_votes = 0;
               }
D
Daniel Larimer 已提交
250
               _gstate.total_producer_vote_weight += pd.second.first;
251
               //eosio_assert( p.total_votes >= 0, "something bad happened" );
252
            });
253
         } else {
254
            eosio_assert( !pd.second.second /* not from new set */, "producer is not registered" ); //data corruption
255 256
         }
      }
257 258 259 260 261 262

      _voters.modify( voter, 0, [&]( auto& av ) {
         av.last_vote_weight = new_vote_weight;
         av.producers = producers;
         av.proxy     = proxy;
      });
263 264
   }

265 266 267
   /**
    *  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
268
    *  update the proxy's weight.
269 270 271 272 273 274
    *
    *  @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 ) {
275 276
      require_auth( proxy );

277
      auto pitr = _voters.find(proxy);
278
      if ( pitr != _voters.end() ) {
279 280
         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" );
281 282 283 284 285 286 287 288 289 290
         _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;
            });
      }
291 292 293 294 295 296 297 298 299
   }

   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;
      }

300 301
      /// don't propagate small changes (1 ~= epsilon)
      if ( fabs( new_weight - voter.last_vote_weight ) > 1 )  {
302 303 304 305 306 307 308 309
         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 {
D
Daniel Larimer 已提交
310
            auto delta = new_weight - voter.last_vote_weight;
311 312 313
            for ( auto acnt : voter.producers ) {
               auto& pitr = _producers.get( acnt, "producer not found" ); //data corruption
               _producers.modify( pitr, 0, [&]( auto& p ) {
D
Daniel Larimer 已提交
314 315 316
                     p.total_votes += delta;
                     _gstate.total_producer_vote_weight += delta;
               });
317 318 319 320 321 322 323
            }
         }
      }
      _voters.modify( voter, 0, [&]( auto& v ) {
            v.last_vote_weight = new_weight;
         }
      );
324 325
   }

326
} /// namespace eosiosystem