Introduction to 'Voting Process' for Programmers
eos的投票过程 voting process
https://steemit.com/blockchain/@eosiosg/introduction-to-voting-process-for-programmers
System Contract Part #2 - Voting Process
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网络中,eosio.system合约允许用户:
- 抵押token,为区块生产者或工作提案(worker proposals)投票
2 将投票权代理给其他用户 - 注册为生产者
- 领取出块奖励
- 委托系统资源(网络,cpu和ram)给他人,并将其他必要的action发送到区块链上,保持区块链系统的运行
In this series, we will go through What is the contract, What are included in the contract and How the contract can be used.
在这部分中,我们会看一下,该合约是什么,合约中有什么,以及,如何使用合约。
Our last post discussed the 'Block Producer Rewards', in this article we will talk about voting process.
在之前的一篇帖子中,我们讨论了区块生产者奖励。这篇文章中,我们来谈一下投票的过程。
In this article, we will be discussing detailed flows/steps on Producer Registration, Token Staking, Voting on BP , and Changing/Withdrawing Vote successively.
本文会对如下流程和步骤的细节,依次探讨:区块生产者注册,token抵押,投票,修改/撤销投票等。
All codes present are based on commit of 44e7d3e
所有的代码基于commit
TL;DR:
- Token holders have to stake with their tokens on net and cpu for voting
- 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 getx
amount of votes repectively - Refunding process takes up to 3 days to reflect the unstaked tokens in available token balance
- Newer votes possess higher voting weights
token持有人需要抵押token换取带宽和cpu,才可以投票。
投票时,所有抵押的资产会转换为数量x
的投票权重,用户最多可以为三十个区块生产者投票,就是说,用户所选择的每一个区块生产者,得到的选票数都是 ``x`。
- 撤回抵押时,将取消抵押的token转到可用账户这一过程,最多耗时三天
- 新的投票获得更高的投票权重。
Producer Registration 出块者注册
Accounts should register themselves as producer first before they can be voted. This process is done by pushing a system_contract::regproducer
action.
账户需要先注册成为出块节点,才可能得到投票。调用了system_contract:regproducer 这一action来实现。
- The core logic code below is to insert or replace producers' configurations (i.e. public key & parameters) into
producerinfo
table.
下面代码的核心逻辑是,向producerinfo这个表中插入或替换区块生产者的配置(即,公钥 & 参数)。
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.
这部分代码还处于快速开发中,后续如果有发生明显变化的话,我们会更新。
Token Staking 抵押token
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持有人只有将自己的token抵押获得了net和cpu资源之后,才能够投票。抵押过程是通过 把 system_contract:delegatebw 的action推送到区块链上来实现的。
在delegatebw
这一action中,投票人抵押了token,只有撤回后才能够转账。
- 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.
如果一个用户没有抵押过token,会在数据表deltable
之中插入一条该账号的记录。如果用户已经抵押过token,会在现有的数额上增加新的抵押数量。
- Set resource limits for stake receiver. Transfer corresponding amount as stake to a public account
eosio
.
为stake 接收者设定资源限制。将相应数额的token转到公共账号eosio中作抵押。
```
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")
} );
}
```
这部分代码是说如何抵押token获得带宽等资源。
-
Update voter's staked amount. 更新投票人的抵押token数量
- Find the voter from
voters
table, if not exist, insert a new record of this voter. - Add newly delegated stake into the voter's
staked
attribute. - Call
voteproducer
action to update vote results. This means if the push sender has voted before, on newdelegatebw
action, votes will be updated for last voting producers (or lasting voting proxy).
- Find the voter from
从voters数据表之中查找投票人 voter。如果不存在,则插入一条新的记录
将抵押token的新增数额,加到voter的staked属性中。
调用voteproducer这一action,更新投票结果。这意味着,如果push命令的发送者曾经投过票,则新调用delegatebw动作,投票人最近一次投票的区块生产者的票数,也随之更新(或者,更新最近所选的代理的投票数)。
```
...
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.***
用户也可以将net 和cpu资源委托给其他的账户,可以实现资源转移。在后续的博客之中,我们会谈论用户资源这一部分。
Vote On Producer / Proxy
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.
token的持有者进行了token抵押,为producer投票,(或者代理给其他账户,委托其投票)。所抵押的token会转换为 权重为 X的选票,最多可以为 30 个区块生产者投票,每个的投票数为x。
Vote producer 选举区块生产者
*Leaving proxy
arguments to be empty
将proxy变量留空
-
Validation:
- Producers to be vote must be given in order;
- Producers to be vote must be registered;
- Producers to be vote must be active.
Calculate current vote weight based on the following formula:
验证:
区块生产者必须按顺序排列(从低到高的顺序排列);
必须注册;
必须是活跃的生产者
- 根据如下的公式计算所得投票的权重:
*The weight increasing could be treated as a linear growing with time within a short period.
在短期内,权重的增加可以被看作是随时间而线性增长的。
If the voter is a proxy, proxied_vote_weight
of the voter will also be updated.
如果voter是代理,会更新 voter 的proxied_vote_weight 的属性。
-
Reduce
last_vote_weight
(if ever), and then add current vote weight.
减去last_vote_weight
,增加当前的投票权重。Create a relation between voting producer and vote weight.
Deduct last voting weight from voting producers.
Add each voting producer's vote weight by the new weight.
voting producer 和 vote weight建立关联;
voting producers减去最近一次的投票权重;
将每一位所选的出块节点的投票权重,加上当前的新权重。
void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector
& producers ) { require_auth( voter_name ); ... boost::container::flat_map 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; } } ... } -
Record voting results. 记录投票结果
- Modify
voters
table, update vote weight & voting producers (or proxy) respectively. - Modify
producerinfo
table, update producer's votes.
- Modify
更新voters表,更新投票权重,并相应的更新投票生产者或代理。
更新 producerinfo表,更新区块生产者的票数。
```
...
_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参数留空
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.
如果一个账户标记为代理,就可以使用其他将这个账号选作代理的账号的投票权重来投票。其他账户需要更新voteproducer的action,来更新代理的权重。
- Validation: 验证
Proxy to be vote must have registered to be a proxy by pushing action
system_contract::regproxy
.Proxy and producers cannot be voted at the same time.
想要成为代理,需要推送action `system_contract::regproxy 注册成代理,才能够接收别人的投票。
- Calculate current vote weight, same as above. 跟上面计算投票的方式一样,计算代理当前的投票权重。
- Update proxy's vote weight 更新代理的投票权重
- Deduct last voting weight from the voting proxy.
- Add each voting proxy's vote weight by the new amount.
减去最近一次的投票权重
加上每个投票代理的投票权重
```
...
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 更换/撤销投票
Votes Change
Voters are able to change voted producers (or proxy) by pushing voteproducer
actions again, details have been discussed in the previous section.
可以通过再次推送 voteproducer 的action,投票人可以修改区块生产者(或者代理),在之前的部分已经讨论过了。
Votes Withdraw (Unstake) 撤销投票(取消抵押)
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.
投票人可以通过推送 system_contract::undelegatebw的action,来撤销自己的投票。所撤销的token的数额,必须是不大于所抵押或代理的token的数额。撤销token三天后,可以通过 system_contract::refund 来取回。
- Decrease refunding amount from voter's
staked
column ofvoter
table. - Update
totals_tbl
table and update resource limits for the account. - Create refund request.
- Update
refunds
table with unstaked amount - 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).
- Update
从voter 表中的staked数据列中,减去token的退款数额。
更新 totals_tbl 表,更新账户的资源使用率限制。
创建退款申请。
更新refunds表,增加所撤销抵押的数额
-
如果用户在短时间内多次撤销代理,会记录最后一次撤销操作的记录。(在计算可用退款的时间时,会用到这一时间记录)
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(); }); } ...
- Create (or replace) a deferred
system_contract::refund
transaction & update voting results.- Push a deferred transaction.
-
refund_delay = 3*24*3600
, i.e. 3 days. - Call
voteproducer
to deduct corresponding votes from voted producers.
创建一个延迟事务,system_contract::refund, 更新投票结果。
推送延迟事务到区块链上
refund_delay = 3 * 24 * 3600, 即三天时间。
调用 voteproducer action,减去所选区块生产者的票数。
```
...
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 结论
- Token owner can only vote after they staked their tokens on net & cpu.
- 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.
- Newer votes count more than older votes, the weight grows approximately linearly.
- Users can undelegate their stakes and have to wait up to 3 days before they can re-allocate this amount of tokens.
token持有人只有将代币抵押在带宽和cpu上之后,才可以投票。
投票时,投票人所抵押的代币,会转换为 数量为X 的权重投票,每一位被选的出块人(一个投票人最多可选择三十个出块人)会得到等量的投票权重 x。
新的投票
In the next article, we are going to talk about some detailed implementation about user resources, including delegate cpu & net, buy & sell ram, new account, producer voting and proxy related stuff.
Stay tuned with eosio.sg: Telegram, Medium.