voting.cpp 10.9 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 81 82
      for ( auto it = idx.cbegin(); it != idx.cend() && top_producers.size() < 21 && 0 < it->total_votes; ++it ) {
         if( !it->active() ) continue;
         top_producers.emplace_back( std::pair<eosio::producer_key,uint16_t>({{it->owner, it->producer_key}, it->location}));
      }
K
Khaled Al-Hassanieh 已提交
83 84


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

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

D
Daniel Larimer 已提交
90 91 92
      producers.reserve(top_producers.size());
      for( const auto& item : top_producers )
         producers.push_back(item.first);
93

D
Daniel Larimer 已提交
94 95 96
      bytes packed_schedule = pack(producers);
      checksum160 new_id;
      sha1( packed_schedule.data(), packed_schedule.size(), &new_id );
97

D
Daniel Larimer 已提交
98 99 100 101
      if( new_id != _gstate.last_producer_schedule_id ) {
         _gstate.last_producer_schedule_id = new_id;
         set_active_producers( packed_schedule.data(),  packed_schedule.size() );
      }
102
      _gstate.last_producer_schedule_update = block_time;
103 104
   }

105 106 107 108
   double stake2vote( int64_t staked ) {
      double weight = int64_t(now() / (seconds_per_day * 7)) / double( 52 );
      return double(staked) * std::pow( 2, weight );
   }
109
   /**
110
    *  @pre producers must be sorted from lowest to highest and must be registered and active
111
    *  @pre if proxy is set then no producers can be voted for
112
    *  @pre if proxy is set then proxy account must exist and be registered as a proxy
113 114 115
    *  @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
116 117 118 119 120 121 122 123
    *  @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.
124
    */
D
Daniel Larimer 已提交
125 126
   void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector<account_name>& producers ) {
      require_auth( voter_name );
127 128 129 130

      //validate input
      if ( proxy ) {
         eosio_assert( producers.size() == 0, "cannot vote for producers and proxy at same time" );
D
Daniel Larimer 已提交
131
         eosio_assert( voter_name != proxy, "cannot proxy to self" );
132 133 134 135 136 137 138 139
         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 已提交
140 141 142
      auto voter = _voters.find(voter_name);
      eosio_assert( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object

143 144
      /**
       * The first time someone votes we calculate and set last_vote_weight, since they cannot unstake until
K
Khaled Al-Hassanieh 已提交
145
       * after total_activated_stake hits threshold, we can use last_vote_weight to determine that this is
146 147 148
       * their first vote and should consider their stake activated.
       */
      if( voter->last_vote_weight <= 0.0 ) {
K
Khaled Al-Hassanieh 已提交
149
         _gstate.total_activated_stake += voter->staked;
150 151
      }

152
      auto new_vote_weight = stake2vote( voter->staked );
153 154 155
      if( voter->is_proxy ) {
         new_vote_weight += voter->proxied_vote_weight;
      }
D
Daniel Larimer 已提交
156

157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
      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 已提交
172
         }
173
      }
D
Daniel Larimer 已提交
174

175
      if( proxy ) {
176
         auto new_proxy = _voters.find( proxy );
177
         eosio_assert( new_proxy != _voters.end() && new_proxy->is_proxy, "invalid proxy specified" );
178 179 180 181 182 183 184 185 186 187 188 189 190 191
         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;
            }
         }
192
      }
D
Daniel Larimer 已提交
193

194 195 196
      for( const auto& pd : producer_deltas ) {
         auto pitr = _producers.find( pd.first );
         if( pitr != _producers.end() ) {
197
            eosio_assert( pitr->active() || !pd.second.second /* not from new set */, "producer is not currently registered" );
198
            _producers.modify( pitr, 0, [&]( auto& p ) {
199 200 201 202
               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" );
203
            });
204 205
         } else {
            eosio_assert( !pd.second.second /* not from new set */, "producer is not registered" );
206 207
         }
      }
208 209 210 211 212 213 214 215 216

      _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" );
      });
217 218
   }

219 220 221 222 223 224 225 226 227 228
   /**
    *  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 ) {
229 230
      require_auth( proxy );

231
      auto pitr = _voters.find(proxy);
232
      //eosio_assert( pitr != _voters.end(), "proxy must have some stake first" );
233
      //eosio_assert( !pitr->is_proxy, "account is already a proxy" );
234
      eosio_assert( pitr->is_proxy != isproxy, "action has no effect" );
235

236 237 238 239 240 241 242 243 244 245 246 247
      if ( pitr != _voters.end() ) {
         _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;
            });
      }
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
   }

   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;
         }
      );
279 280
   }

281
} /// namespace eosiosystem