所有代币的15%进行投票后eosio会停止出块,主网会自动启动。在这里我们的代币数据数量总共是10亿,那么我们的主网启动需要1.5亿质押的代币进行投票后才能启动,使用如下命令查看我们三个用来投票的普通用户:
质押的代币计算方式:staked = cpu + net,所以这里usera有1亿的质押,使用usera投票后只有1/10的投票率,不够主网启动,我们再用userb投票之后,发现主网就开始启动了,出块是由lwz和hml这两个节点用户进行了,当然,最后用userc给lx投票的时候,节点名为lx的用户也会加入到出块者中间。
最后有producers标志,说明此用户投票给了名称为lwz的节点。
//查看用户的资金
cleos --wallet-url [http://127.0.0.1:8900](http://127.0.0.1:8900) -u [http://10.186.11.211:8888](http://10.186.11.211:8888) get currency balance eosio.token eosio
//获取用户信息
cleos --wallet-url [http://127.0.0.1:8900](http://127.0.0.1:8900) -u [http://10.186.11.211:8888](http://10.186.11.211:8888) get account usera
//列举候选节点
cleos --wallet-url [http://127.0.0.1:8900](http://127.0.0.1:8900) --url [http://10.186.11.211:8888](http://10.186.11.211:8888) system listproducers
//解锁钱包
cleos --wallet-url [http://127.0.0.1:8900](http://127.0.0.1:8900) --url [http://10.186.11.211:8888](http://10.186.11.211:8888) wallet unlock
//查看投票数
cleos --wallet-url [http://127.0.0.1:8900](http://127.0.0.1:8900) --url [http://10.186.11.211:8888](http://10.186.11.211:8888) get table eosio eosio producers
系统合同 - 投票过程
在eos网络中,eosio.system合同使用户能够1)持有代币,然后对生产者(或工人提案)进行投票,2)将其投票影响力代理给其他用户,3)注册生产者,4)要求生产者奖励,5)委托资源(net,cpu和ram)并推动其他必要的操作以保持区块链系统的运行。
EOS网络中,eosio.system合约允许用户:
抵押令牌,为区块生产者或工作提案(工人提案)投票
2将投票权代理给其他用户
注册为生产者
领取出块奖励
委托系统资源(网络,CPU和RAM)给他人,并将其他必要的动作发送到区块链上,保持区块链系统的运行
区块生产者注册,令牌抵押,投票,修改/撤销投票等。
所有的代码基于承诺
令牌持有者必须在网络和cpu上使用他们的令牌进行投票
在投票时,所有赌注资产将转换为x加权投票数量,可用于投票最多30个生产者,每个选定的生产者将分别获得x投票数量
退款流程最多需要3天才能反映可用令牌余额中未标记的令牌
较新的投票具有较高的投票权重
令牌持有人需要抵押令牌换取带宽和CPU,才可以投票。
投票时,抵押所有的资产会转换为数量x的投票权重,用户最多可以为三十个区块生产者投票,
撤回抵押时,将取消抵押的令牌转到可用账户这一过程,最多耗时三天
新的投票获得更高的投票权重。
制片人注册出块者注册
在投票之前,账户应首先注册为生产者。这个过程是通过推动一个system_contract::regproducer动作来完成的。
账户需要先注册成为出块节点,才可能得到投票。调用了system_contract:regproducer这一动作来实现。
- 下面的核心逻辑代码是将生产者的配置(即公钥和参数)插入或替换到producerinfo表中。
下面代码的核心逻辑是,向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;
});
}
}
Token Staking抵押令牌
令牌持有者只能在网络和cpu上放置令牌后才能投票。通过推动system_contract::delegatebw动作来完成放样过程。在内部delegatebw行动中,选民的代币是赌注,在退款之前不能转让。
token持有人只有将自己的令牌抵押获得了net和cpu资源之后,才能够投票。抵押过程是通过把system_contract:delegatebw的动作推送到区块链上来实现的。
在delegatebw这一行动中,投票人抵押了的道理,只有撤回后才能够转账。
如果用户之前没有赌注,请在表格中插入此帐户的记录deltable。如果用户已赌注,请将新金额添加到现有金额。
如果一个用户没有抵押过令牌,在会表数据deltable之中插入一条该账号的记录。如果用户已经抵押过令牌,会在现有的数额上增加新的抵押数量。
设置桩号接收器的资源限制。将相应金额作为赌注转入公共账户eosio。
为桩接收者设定资源限制。将相应数额的令牌转到公共账号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")
} );
}
这部分代码是说如何抵押令牌获得带宽等资源。
更新选民的赌注金额。更新投票人的抵押令牌数量
从voters表中找到选民,如果不存在,请插入此选民的新记录。
将新委托的赌注添加到选民的staked属性中。
调用voteproducer操作以更新投票结果。这意味着如果推送发件人之前已投票,则在新delegatebw操作上,将更新最后投票制作人(或持久投票代理人)的投票。
从选民数据表之中查找投票人选民。如果不存在,则插入一条新的记录
将抵押令牌的新增数额,加到选民的赌注属性中。
调用voteproducer这一动作,更新投票结果。这意味着,如果推命令的发送者曾经投过票,则新调用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
请注意,用户还可以将net和cpu委托给其他帐户,从而可以进行资源转移。我们将在即将发布的博客中深入讨论用户资源。
用户也可以将net和cpu资源委托给其他的账户,可以实现资源转移。在后续的博客之中,我们会谈论用户资源这一部分。
在制片人/代理上投票
持股人(持有代币的代币持有人)可以投票给生产者(或代理人,代表推送发件人投票),所有股权将转换为加权x票,然后通过x票加起来最多30个生产者。
token的持有者进行了token抵押,为生产者投票,(或者代理给其他账户,委托其投票)。所抵押的令牌会转换为权重为X的选票,最多可以为30个区块生产者投票,每个的投票数为X。
投票制作人选举区块生产者
- 离开proxy参数为空
将代理变量留空
验证:
必须按顺序给予投票的制作人;
投票的制作人必须登记;
投票的制作人必须积极参与。
根据以下公式计算当前投票权重:
验证:
区块生产者必须按顺序排列(从低到高的顺序排列);
必须注册;
必须是活跃的生产者
根据如下的公式计算所得投票的权重:
- 重量增加可以被视为在短时间内随时间线性增长。
在短期内,权重的增加可以被看作是随时间而线性增长的。
如果选民是代理人,proxied_vote_weight选民也将更新。
如果选民是代理,会更新选民的proxied_vote_weight的属性。
减少last_vote_weight(如果有的话),然后加上当前的投票权重。
减去last_vote_weight,增加当前的投票权重。
在投票制作人和投票权重之间建立关系。
从投票制作人那里扣除最后一次投票权重。
按新权重添加每个投票制作人的投票权重。
投票生产者和投票权重建立关联;
投票生产者减去最近一次的投票权重;
将每一位所选的出块节点的投票权重,加上当前的新权重。
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;
}
}
...
}
记录投票结果。记录投票结果
修改voters表,分别更新投票权重和投票生产者(或代理人)。
修改producerinfo表,更新生产者的投票。
更新选表,更新投票权重,并相应的更新投票生产者或代理。
更新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" );
});
}
}
}
投票代理投票代理
- 留下producers参数为空的
生产者参数留空
标记为代理的帐户可以使用已选择其作为代理的其他帐户的权重进行投票。其他帐户必须刷新他们的voteproducer以更新代理的权重。
如果一个账户标记为代理,就可以使用其他将这个账号选作代理的账号的投票权重来投票。其他账户需要更新voteproducer的行动,来更新代理的权重。
验证:验证
要投票的代理必须通过推送操作注册为代理system_contract::regproxy。
代理人和制作人不能同时投票。
想要成为代理,需要推送动作`system_contract :: regproxy注册成代理,才能够接收别人的投票。
计算当前投票权重,与上述相同。跟上面计算投票的方式一样,计算代理当前的投票权重。
更新代理的投票权重更新代理的投票权重
从投票代理中扣除最后一次投票权重。
按新金额添加每个投票代理的投票权重。
队最近减去一次的投票权重
加上每个投票代理的投票权重
...
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" );
});
}
更改/撤销投票更换/撤销投票
投票改变
选民可以通过再次推动voteproducer行动来改变投票的生产者(或代理人),详情已在上一节中讨论过。
可以通过再次推送voteproducer的动作,投票人可以修改区块生产者(或者代理),在之前的部分已经讨论过了。
Votes Withdraw(Unstake)撤销投票(取消抵押)
选民可以通过推动system_contract::undelegatebw行动来撤回他们的投票,其数量不超过网络和cpu被赌注和委派的数量。system_contract::refund3天后将提供未授权的赌注。
投票人可以通过推送system_contract :: undelegatebw的动作,来撤销自己的投票。所撤销的令牌的数额,必须是不大于所抵押或代理的令牌的数额。撤销令牌三天后,可以通过system_contract :: refund来取回。
从选民staked列表中减少退款金额voter。
更新totals_tbl表并更新帐户的资源限制。
创建退款请求。
更新未refunds固定金额的表格
如果用户在短时间内多次取消呼叫,将记录最后一次未释放时间(此时间将用于计算可用的退款时间)。
从投票表中的赌注数据列中,减去令牌的退款数额。
更新totals_tbl表,更新账户的资源使用率限制。
创建退款申请。
更新退款表,增加所撤销抵押的数额
如果用户在短时间内多次撤销代理,会记录最后一次撤销操作的记录。(在计算可用退款的时间时,会用到这一时间记录)
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();
});
}
创建(或替换)延期system_contract::refund交易并更新投票结果。
推送延期交易。
refund_delay = 3243600,即3天。
要求voteproducer从投票的制片人中扣除相应的选票。
创建一个延迟事务,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
结论
令牌所有者只能在网络和cpu上放置令牌后才能投票。
在投票行动期间,选民的所有赌注将转换为x加权投票,并且每个投票的生产者(最多30个)将获得相等的x加权投票。
较新的选票比旧票数多,重量大致线性增长。
用户可以取消他们的赌注并且必须等待最多3天才能重新分配这一数量的令牌。
token持有人只有将代币抵押在带宽和cpu上之后,才可以投票。
投票时,投票人所抵押的代币,会转换为数量为X的权重投票,每一位被选的出块人(一个投票人最多可选择三十个出块人)会得到等量的投票权重x。
新的投票