voting.cpp 11.5 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
    *
    */
D
Daniel Larimer 已提交
37
   void system_contract::regproducer( const account_name producer, const eosio::public_key& producer_key, const std::string& url ) { //, const eosio_parameters& prefs ) {
38
      eosio_assert( url.size() < 512, "url too long" );
39
      //eosio::print("produce_key: ", producer_key.size(), ", sizeof(public_key): ", sizeof(public_key), "\n");
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;
A
Anton Perkov 已提交
48
                  info.url = url;
D
Daniel Larimer 已提交
49
             });
50
         }
51
      } else {
D
Daniel Larimer 已提交
52
         _producers.emplace( producer, [&]( producer_info& info ){
53 54
               info.owner       = producer;
               info.total_votes = 0;
55
               info.producer_key =  producer_key;
A
Anton Perkov 已提交
56
               info.url         = url;
D
Daniel Larimer 已提交
57
         });
58 59 60 61 62 63
      }
   }

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

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

D
Daniel Larimer 已提交
66
      _producers.modify( prod, 0, [&]( producer_info& info ){
D
Daniel Larimer 已提交
67
         info.producer_key = eosio::public_key();
D
Daniel Larimer 已提交
68
      });
69 70
   }

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

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

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

D
Daniel Larimer 已提交
79 80
      for ( auto it = idx.cbegin(); it != idx.cend() && top_producers.size() < 21 && 0 < it->total_votes; ++it ) {
         if( !it->active() ) continue;
81

82
         if ( it->time_became_active == 0 ) {
83 84 85
            _producers.modify( *it, 0, [&](auto& p) {
                  p.time_became_active = block_time;
               });
86
         } else if ( block_time > 2 * 21 * 12 + it->time_became_active &&
87 88 89 90 91 92 93 94 95 96
                     block_time > it->last_produced_block_time + blocks_per_day ) {
            _producers.modify( *it, 0, [&](auto& p) {
                  p.producer_key = public_key();
                  p.time_became_active = 0;
                  p.last_produced_block_time = 0;
               });

            continue;
         }

D
Daniel Larimer 已提交
97 98
         top_producers.emplace_back( std::pair<eosio::producer_key,uint16_t>({{it->owner, it->producer_key}, it->location}));
      }
99
      
K
Khaled Al-Hassanieh 已提交
100 101


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

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

D
Daniel Larimer 已提交
107 108 109
      producers.reserve(top_producers.size());
      for( const auto& item : top_producers )
         producers.push_back(item.first);
110

D
Daniel Larimer 已提交
111 112 113
      bytes packed_schedule = pack(producers);
      checksum160 new_id;
      sha1( packed_schedule.data(), packed_schedule.size(), &new_id );
114

D
Daniel Larimer 已提交
115 116 117 118
      if( new_id != _gstate.last_producer_schedule_id ) {
         _gstate.last_producer_schedule_id = new_id;
         set_active_producers( packed_schedule.data(),  packed_schedule.size() );
      }
119
      _gstate.last_producer_schedule_update = block_time;
120 121
   }

122 123 124 125
   double stake2vote( int64_t staked ) {
      double weight = int64_t(now() / (seconds_per_day * 7)) / double( 52 );
      return double(staked) * std::pow( 2, weight );
   }
126
   /**
127
    *  @pre producers must be sorted from lowest to highest and must be registered and active
128
    *  @pre if proxy is set then no producers can be voted for
129
    *  @pre if proxy is set then proxy account must exist and be registered as a proxy
130 131 132
    *  @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
133 134 135 136 137 138 139 140
    *  @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.
141
    */
D
Daniel Larimer 已提交
142 143
   void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector<account_name>& producers ) {
      require_auth( voter_name );
144 145 146 147

      //validate input
      if ( proxy ) {
         eosio_assert( producers.size() == 0, "cannot vote for producers and proxy at same time" );
D
Daniel Larimer 已提交
148
         eosio_assert( voter_name != proxy, "cannot proxy to self" );
149 150 151 152 153 154 155 156
         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 已提交
157 158
      auto voter = _voters.find(voter_name);
      eosio_assert( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object
159
      eosio_assert( !proxy || !voter->is_proxy, "account registered as a proxy is not allowed to use a proxy" );
D
Daniel Larimer 已提交
160

161 162
      /**
       * The first time someone votes we calculate and set last_vote_weight, since they cannot unstake until
K
Khaled Al-Hassanieh 已提交
163
       * after total_activated_stake hits threshold, we can use last_vote_weight to determine that this is
164 165 166
       * their first vote and should consider their stake activated.
       */
      if( voter->last_vote_weight <= 0.0 ) {
K
Khaled Al-Hassanieh 已提交
167
         _gstate.total_activated_stake += voter->staked;
168 169
      }

170
      auto new_vote_weight = stake2vote( voter->staked );
171 172 173
      if( voter->is_proxy ) {
         new_vote_weight += voter->proxied_vote_weight;
      }
D
Daniel Larimer 已提交
174

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
      boost::container::flat_map<account_name, pair<double, bool /*new*/> > 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;
            }
D
Daniel Larimer 已提交
190
         }
191
      }
D
Daniel Larimer 已提交
192

193
      if( proxy ) {
194
         auto new_proxy = _voters.find( proxy );
195
         eosio_assert( new_proxy != _voters.end() && new_proxy->is_proxy, "invalid proxy specified" );
196 197 198 199 200 201 202 203 204 205 206 207 208 209
         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;
            }
         }
210
      }
D
Daniel Larimer 已提交
211

212 213 214
      for( const auto& pd : producer_deltas ) {
         auto pitr = _producers.find( pd.first );
         if( pitr != _producers.end() ) {
215
            eosio_assert( pitr->active() || !pd.second.second /* not from new set */, "producer is not currently registered" );
216
            _producers.modify( pitr, 0, [&]( auto& p ) {
217 218 219 220
               print( "orig total_votes: ", p.total_votes, " delta: ", pd.second.first, "\n" );
               p.total_votes += pd.second.first;
               print( "new total_votes: ", p.total_votes, "\n" );
               //eosio_assert( p.total_votes >= 0, "something bad happened" );
221
            });
222 223
         } else {
            eosio_assert( !pd.second.second /* not from new set */, "producer is not registered" );
224 225
         }
      }
226 227 228 229 230 231 232 233 234

      _voters.modify( voter, 0, [&]( auto& av ) {
         print( "last_vote_weight: ", av.last_vote_weight, "\n" );
         print( "new_vote_weight: ", new_vote_weight, "\n" );
         av.last_vote_weight = new_vote_weight;
         av.producers = producers;
         av.proxy     = proxy;
         print( "    vote weight: ", av.last_vote_weight, "\n" );
      });
235 236
   }

237 238 239 240 241 242 243 244 245 246
   /**
    *  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 ) {
247 248
      require_auth( proxy );

249
      auto pitr = _voters.find(proxy);
250
      if ( pitr != _voters.end() ) {
251 252
         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" );
253 254 255 256 257 258 259 260 261 262 263
         _voters.modify( pitr, 0, [&]( auto& p ) {
               p.is_proxy = isproxy;
               print( "    vote weight: ", p.last_vote_weight, "\n" );
            });
         propagate_weight_change( *pitr );
      } else {
         _voters.emplace( proxy, [&]( auto& p ) {
               p.owner  = proxy;
               p.is_proxy = isproxy;
            });
      }
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
   }

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

      if ( new_weight != voter.last_vote_weight ) {
         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 {
            for ( auto acnt : voter.producers ) {
               auto& pitr = _producers.get( acnt, "producer not found" ); //data corruption
               _producers.modify( pitr, 0, [&]( auto& p ) {
                     p.total_votes += new_weight - voter.last_vote_weight;
                  }
               );
            }
         }
      }
      _voters.modify( voter, 0, [&]( auto& v ) {
            v.last_vote_weight = new_weight;
         }
      );
295 296
   }

297
} /// namespace eosiosystem