EOS系統(tǒng)合約 —— 投票

EOS系統(tǒng)合約 —— 投票

In eos network, eosio.system contract enable users to 1) stake tokens, and then vote on producers (or worker proposals), 2) proxy their voting influence to other users, 3) register producers, 4) claim producer rewards, 5) delegate resources (net, cpu & ram) and push other necessary actions to keep blockchain system running.
在EOS網(wǎng)絡(luò)中,區(qū)塊鏈的系統(tǒng)合約允許用戶 1)為區(qū)塊生產(chǎn)者投票(或?yàn)樘岚竿镀保?2)選擇投票代理人; 3)注冊(cè)成為區(qū)塊生產(chǎn)者; 4)領(lǐng)取系統(tǒng)獎(jiǎng)勵(lì); 5)委托計(jì)算、帶寬等資源給其他用戶等各種支持EOS網(wǎng)絡(luò)正常運(yùn)行的系統(tǒng)功能。
In this series, we will go through What is the contract, What are included in the contract and How the contract can be used.
在這個(gè)系列中,我們會(huì)詳細(xì)介紹系統(tǒng)合約的內(nèi)部運(yùn)行機(jī)制,以及如何使用系統(tǒng)合約。
In this article, we will be discussing detailed flows/steps on Producer Registration, Token Staking, Voting on BP , and Changing/Withdrawing Vote successively.
在這片文章中,我們會(huì)深入EOS源碼,詳細(xì)解析注冊(cè)生產(chǎn)者、購(gòu)買股權(quán)、為生產(chǎn)者投票以及收回股權(quán)等功能是如何實(shí)現(xiàn)的。

All codes present are based on commit of 44e7d3e
本文中所有引用的代碼來(lái)源于此次提交44e7d3e
TL;DR:
--

  • Token holders have to stake with their tokens on net and cpu for voting
  • Token的持有者需要首先將使用Token購(gòu)買股權(quán)才能進(jìn)行投票
  • On voting, all staked assets will convert to x amount of weighted votes, which can be used to vote up to 30 producers and each selected producer will get x amount of votes repectively
  • 在投票過(guò)程中,用戶所有已購(gòu)買的股權(quán)會(huì)轉(zhuǎn)變?yōu)橐欢〝?shù)量的選票,然后所有用戶所選擇的生產(chǎn)者(至多30個(gè))都會(huì)增加這個(gè)數(shù)量的票數(shù)。
  • Refunding process takes up to 3 days to reflect the unstaked tokens in available token balance
  • 股權(quán)贖回Tokens的過(guò)程需要等待3天
  • Newer votes possess higher voting weights
  • 新產(chǎn)生的票數(shù)擁有更大的效力
eos_signature.png

Producer Registration
注冊(cè)生產(chǎn)者
--
Accounts should register themselves as producer first before they can be voted. This process is done by pushing a system_contract::regproducer action.
Token持有者只能為已經(jīng)注冊(cè)的生產(chǎn)者進(jìn)行投票,注冊(cè)生產(chǎn)者可以通過(guò)發(fā)送regproducer消息實(shí)現(xiàn)。

  • The core logic code below is to insert or replace producers' configurations (i.e. public key & parameters) into producerinfo table.
  • 這段代碼的核心邏輯是把生產(chǎn)者的配置(公鑰,系統(tǒng)參數(shù)等)寫(xiě)入producerinfo表中,如果生產(chǎn)者曾經(jīng)注冊(cè)過(guò),就更新之前的記錄。
void system_contract::regproducer( const account_name producer, 
                    const eosio::public_key& producer_key, const std::string& url ) { 
                    //, const eosio_parameters& prefs ) {
    ...

    if ( prod != _producers.end() ) {
        if( producer_key != prod->producer_key ) {
             _producers.modify( prod, producer, [&]( producer_info& info ){
                info.producer_key = producer_key;
            });
        }
    } else {
        _producers.emplace( producer, [&]( producer_info& info ){
            info.owner       = producer;
            info.total_votes = 0;
            info.producer_key =  producer_key;
        });
    }
}

*This part of code is under rapid development, we will keep updating it if significant changes are found.
*這部分代碼仍處于快速開(kāi)發(fā)的過(guò)程中,如果未來(lái)有大幅改動(dòng),我們會(huì)及時(shí)更新。

Token Staking
購(gòu)置股權(quán)
--
Token holders can only vote after they have staked their tokens on net and cpu. Staking process is done by pushing a system_contract::delegatebw action. Inside delegatebw action, voter's tokens are staked and cannot be transferred until refunded.
**Token持有者需要首先在帶寬和計(jì)算資源上購(gòu)置股權(quán),這個(gè)過(guò)程是通過(guò)發(fā)送delegatebw的實(shí)現(xiàn)的。購(gòu)置股權(quán)的Token不能用來(lái)交易,直到用戶主動(dòng)申請(qǐng)贖回。

  1. If a user has not staked before, insert a record for this account in the table deltable. If a user has staked, add newly amount to the existing amount.
  2. 如果用戶沒(méi)有購(gòu)置過(guò)股權(quán),在 deltable表中插入一條用戶購(gòu)置的股權(quán)信息。如果用戶之前購(gòu)置過(guò)股權(quán),就在它原有的數(shù)額上增加新購(gòu)置的部分。
  3. Set resource limits for stake receiver. Transfer corresponding amount as stake to a public account eosio.
  4. 設(shè)置股權(quán)接受者可以支配的資源限制,然后把購(gòu)置的股權(quán)轉(zhuǎn)移到eosio這個(gè)公共賬戶。
     void system_contract::delegatebw( account_name from, account_name receiver,
                                      asset stake_net_quantity, 
                                      asset stake_cpu_quantity )                  
            {
               require_auth( from );
               ...
    
               set_resource_limits( tot_itr->owner, tot_itr->ram_bytes, 
                                 tot_itr->net_weight.amount, tot_itr->cpu_weight.amount );
    
               if( N(eosio) != from) {
                  INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {from,N(active)}, 
                 { 
                     from, N(eosio), asset(total_stake), std::string("stake bandwidth") 
                 } );
           }
    
  5. Update voter's staked amount.
  6. 更新用戶購(gòu)置股權(quán)的金額。
    • Find the voter from voters table, if not exist, insert a new record of this voter.
    • voters表中找到投票用戶的信息,如果用戶此前從未參與投票,就在表中插入一列新的記錄。
    • Add newly delegated stake into the voter's staked attribute.
    • 在用戶的staked屬性增加新購(gòu)置的股權(quán)額度。
    • Call voteproducer action to update vote results. This means if the push sender has voted before, on new delegatebw action, votes will be updated for last voting producers (or lasting voting proxy).
    • 調(diào)用voteproducer消息,更新投票結(jié)果。也就是說(shuō),如果用戶之前進(jìn)行過(guò)投票,在這一次購(gòu)置股權(quán)的時(shí)候,用戶上次所有投票的生產(chǎn)者的票數(shù)都會(huì)根據(jù)當(dāng)前用戶的全部股權(quán)額度進(jìn)行更新。
    ...
          print( "voters \n" );
          auto from_voter = _voters.find(from);
          if( from_voter == _voters.end() ) {
            print( " create voter \n" );
             from_voter = _voters.emplace( from, [&]( auto& v ) {
                v.owner  = from;
                v.staked = uint64_t(total_stake);
            print( "    vote weight: ", v.last_vote_weight, "\n" );
            });
          } else {
            _voters.modify( from_voter, 0, [&]( auto& v ) {
                v.staked += uint64_t(total_stake);
                print( "    vote weight: ", v.last_vote_weight, "\n" );
             });
          }
    
          print( "voteproducer\n" );
          if( from_voter->producers.size() || from_voter->proxy ) {
            voteproducer( from, from_voter->proxy, from_voter->producers );
          }
       } // delegatebw
    

****Note that user can also delegate net & cpu to other accounts, making resource transfer to be possible. We will talk about user resources in depth in the upcoming blog.***
****用戶購(gòu)置的帶寬和計(jì)算股權(quán),可以把資源的使用權(quán)轉(zhuǎn)交給其他用戶,也就是說(shuō)可以調(diào)用delegatebw來(lái)實(shí)現(xiàn)資源的配置。我們會(huì)在下一篇文章里深入講解用戶的資源分配話題。***

Vote On Producer / Proxy
為生產(chǎn)者/代理投票
--
Stake holders (token holders who have their tokens staked) can vote for producers (or proxy, who will vote on behalf of push sender), all stakes will convert to weighted x votes and then add up to 30 producers by x votes.
**股權(quán)的持有者,購(gòu)置過(guò)帶寬和計(jì)算資源股權(quán)的用戶,可以為生產(chǎn)者(或?yàn)榇恚┩镀?。用戶配置的全部股?quán)會(huì)轉(zhuǎn)變?yōu)榧訖?quán)后的票數(shù)x,用戶所票選的生產(chǎn)者(至多30個(gè))都會(huì)增加x票。

Vote producer

為生產(chǎn)者投票

*Leaving proxy arguments to be empty
*proxy參數(shù)置空

  1. Validation:

  2. 條件驗(yàn)證:

    • Producers to be vote must be given in order;
    • 票選的生產(chǎn)者需要按照一定的順序排列(以節(jié)省查找時(shí)間);
    • Producers to be vote must be registered;
    • 票選的生產(chǎn)者必須已經(jīng)注冊(cè)過(guò);
    • Producers to be vote must be active.
    • 票選的生產(chǎn)者必須是活躍狀態(tài)。
  3. Calculate current vote weight based on the following formula:

  4. 根據(jù)下面的公式,計(jì)算當(dāng)前的票數(shù)權(quán)重:

    undelegating.png

*The weight increasing could be treated as a linear growing with time within a short period.
*票數(shù)的權(quán)重增加過(guò)程可以近似看作線性,新票數(shù)權(quán)重相當(dāng)于一年前票數(shù)權(quán)重的2倍。

If the voter is a proxy, proxied_vote_weight of the voter will also be updated.
如果投票的用戶本身是代理,用戶的proxied_vote_weight參數(shù)也會(huì)相應(yīng)進(jìn)行更新。

  1. Reduce last_vote_weight (if ever), and then add current vote weight.

  2. 如果用戶曾經(jīng)有過(guò)投票,先將它上次投出的票數(shù)從相應(yīng)的生產(chǎn)者中減去,然后再增加本次的總票數(shù)。

    • Create a relation between voting producer and vote weight.
    • 創(chuàng)建一個(gè)生產(chǎn)者和票數(shù)的對(duì)應(yīng)關(guān)系。
    • Deduct last voting weight from voting producers.
    • 在生產(chǎn)者的票數(shù)中減去上次投票的加權(quán)票數(shù)。
    • Add each voting producer's vote weight by the new weight.
    • 未本次票選的生產(chǎn)者增加新權(quán)重對(duì)應(yīng)的票數(shù)。
    void system_contract::voteproducer( const account_name voter_name, 
                    const account_name proxy, const std::vector<account_name>& producers ) {
        require_auth( voter_name );
    
        ...
    
        boost::container::flat_map<account_name, double> producer_deltas;
        for( const auto& p : voter->producers ) {
            producer_deltas[p] -= voter->last_vote_weight;
        }
    
        if( new_vote_weight >= 0 ) {
            for( const auto& p : producers ) {
                producer_deltas[p] += new_vote_weight;
            }
        }
        ...
    }    
    
  3. Record voting results.

  4. 記錄投票結(jié)果。

    • Modify voters table, update vote weight & voting producers (or proxy) respectively.
    • 修改voters表。更新用戶的投票權(quán)重和選擇的生產(chǎn)者列表(或代理)。
    • Modify producerinfo table, update producer's votes.
    • 修改producerinfo表。更新所選生產(chǎn)者的票數(shù)。
        ...
        _voters.modify( voter, 0, [&]( auto& av ) {
            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" );
          });
    
        for( const auto& pd : producer_deltas ) {
            auto pitr = _producers.find( pd.first );
            if( pitr != _producers.end() ) {
                _producers.modify( pitr, 0, [&]( auto& p ) {
                p.total_votes += pd.second;
                eosio_assert( p.total_votes >= 0, "something bad happened" );
                eosio_assert( p.active(), "producer is not active" );
                });
            }
        }
    }
    

Vote proxy

為代理投票

*Leaving producers arguments to be empty
*producers參數(shù)指控

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.
如果一個(gè)用戶被標(biāo)記為代理,那么他可以使用選擇他作代理的用戶票數(shù)權(quán)重。其他用戶必須調(diào)用voteproducer以更新代理的權(quán)重。

  1. Validation:
  2. 條件驗(yàn)證:
    • Proxy to be vote must have registered to be a proxy by pushing action system_contract::regproxy.
    • 票選的代理必須通過(guò)調(diào)用system_contract::regproxy的方式注冊(cè)代理。
    • Proxy and producers cannot be voted at the same time.
    • 不能同時(shí)向生產(chǎn)者和代理投票。
  3. Calculate current vote weight, same as above.
  4. 計(jì)算當(dāng)前的投票權(quán)重,同上。
  5. Update proxy's vote weight
  6. 更新代理權(quán)重
    • Deduct last voting weight from the voting proxy.
    • 從票選的代理人中減去上次投票的權(quán)重。
    • Add each voting proxy's vote weight by the new amount.
    • 為票選的代理人增加新的投票權(quán)重。
        ...
        if( voter->proxy != account_name() ) {
            auto old_proxy = _voters.find( voter->proxy );
            _voters.modify( old_proxy, 0, [&]( auto& vp ) {
                vp.proxied_vote_weight -= voter->last_vote_weight;
                print( "    vote weight: ", vp.last_vote_weight, "\n" );
            });
          }
    
          if( proxy != account_name() && new_vote_weight > 0 ) {
            auto new_proxy = _voters.find( voter->proxy );
             eosio_assert( new_proxy != _voters.end() && new_proxy->is_proxy, "invalid proxy specified" );
             _voters.modify( new_proxy, 0, [&]( auto& vp ) {
                vp.proxied_vote_weight += new_vote_weight;
                print( "    vote weight: ", vp.last_vote_weight, "\n" );
             });
          }
    

Changing/Withdrawing Vote
改變/撤回投票
--

[圖片上傳失敗...(image-161ecf-1527216738832)]

Votes Change

改變投票

Voters are able to change voted producers (or proxy) by pushing voteproducer actions again, details have been discussed in the previous section.
投票者可以通過(guò)再次調(diào)用voteproducer來(lái)實(shí)現(xiàn)改變選票,原有選擇的生產(chǎn)者票數(shù)會(huì)被撤回,新的票數(shù)會(huì)被加在新選擇的生產(chǎn)者上。我們?cè)谏弦粋€(gè)部分已經(jīng)詳細(xì)講解,不再贅述。

Votes Withdraw (Unstake)

撤回投票(贖回股權(quán))

Voters can withdraw their votes by pushing by pushing system_contract::undelegatebw actions with any amount that is no bigger than the net & cpu been staked & delegated. Undelegated stakes will be available for system_contract::refund after 3 days.
**用戶可以通過(guò)調(diào)用sundelegatebw方法來(lái)撤回股權(quán),計(jì)算和帶寬資源撤回的額度不能超過(guò)之前購(gòu)置的額度。贖回的股權(quán)可以在3天后申請(qǐng)回收到Token余額。

  1. Decrease refunding amount from voter's staked column of voter table.
  2. voter表中的staked列中減去用戶申請(qǐng)贖回的股權(quán)額度。
  3. Update totals_tbl table and update resource limits for the account.
  4. 更新totals_tbl表,并且設(shè)置用戶可以支配的資源限度。
  5. Create refund request.
  6. 創(chuàng)建退回請(qǐng)求。
    • Update refunds table with unstaked amount
    • 更新refunds表,設(shè)置贖回額度。
    • If user undelegate many times within a short period of time, the last undelegating time will be recorded (this time will be used for calculating the available refunding time).
    • 如果用戶在短時(shí)間內(nèi)進(jìn)行多次贖回股權(quán)的操作,最后一次贖回的時(shí)間被記錄(這個(gè)時(shí)間是用來(lái)計(jì)算用戶何時(shí)可以申請(qǐng)回收Token到余額)。
       void system_contract::undelegatebw( account_name from, account_name receiver,
                                       asset unstake_net_quantity, asset unstake_cpu_quantity )
    {
        ...
        auto req = refunds_tbl.find( from );
          if ( req != refunds_tbl.end() ) {
            refunds_tbl.modify( req, 0, [&]( refund_request& r ) {
                r.amount += unstake_net_quantity + unstake_cpu_quantity;
                r.request_time = now();
            });
          } else {
            refunds_tbl.emplace( from, [&]( refund_request& r ) {
                r.owner = from;
                   r.amount = unstake_net_quantity + unstake_cpu_quantity;
                   r.request_time = now();
               });
         }
        ...
    
  7. Create (or replace) a deferred system_contract::refund transaction & update voting results.
  8. 創(chuàng)建(或替換)一個(gè)system_contract::refund延遲交易,并且更新投票結(jié)果。
    • Push a deferred transaction.
    • 發(fā)起一個(gè)延遲交易。
    • refund_delay = 3*24*3600, i.e. 3 days.
    • refund_delay = 3*24*3600,延遲3天后執(zhí)行。
    • Call voteproducer to deduct corresponding votes from voted producers.
    • 調(diào)用voteproducer, 從用戶投票的生產(chǎn)者上減去相應(yīng)的票數(shù)。
        ...
        eosio::transaction out;
          out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from );
          out.delay_sec = refund_delay;
          out.send( from, receiver );
    
          const auto& fromv = _voters.get( from );
    
          if( fromv.producers.size() || fromv.proxy ) {
            voteproducer( from, fromv.proxy, fromv.producers );
          }
       } // undelegatebw
    

Conclusion
結(jié)論
--

  1. Token owner can only vote after they staked their tokens on net & cpu.
  2. Token的持有者需要在帶寬和計(jì)算資源上購(gòu)置股權(quán)才可以投票。
  3. During voting action, all stakes of the voter will convert into x weighted votes, and every voted producer (up to 30) is going to get equivalent x weighted votes.
  4. 在投票過(guò)程中,用戶的所有股權(quán)會(huì)變成加權(quán)的x票,每個(gè)用戶所選擇的生產(chǎn)者(至多30個(gè))會(huì)分別增加x票。
  5. Newer votes count more than older votes, the weight grows approximately linearly.
  6. 新的票數(shù)具有更大的權(quán)重,票數(shù)權(quán)重的增加是一個(gè)接近線性的過(guò)程。
  7. Users can undelegate their stakes and have to wait up to 3 days before they can re-allocate this amount of tokens.
  8. 用戶可以贖回股權(quán),但是如果需要等待3天才能重新交易或使用這部分金額來(lái)購(gòu)置其他資源。

In the following article, we are going to talk about some detailed implementation about user resources, including delegate cpu & net, buy & sell ram, new account related stuff.
在接下來(lái)的文章里,我們會(huì)討論有關(guān)用戶資源*的具體實(shí)現(xiàn),包括用戶如何購(gòu)置和支配帶寬和計(jì)算資源,如何買賣內(nèi)存資源,以及如何收費(fèi)等問(wèn)題。
Stay tuned with eosio.sg: Telegram, Medium.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容