bitshares研究系列【见证人选择机制】
前面文章bitshares基本概念详解【见证人】聊过见证人的基本概念,当时想着另写文章来聊见证人的切换等,终于找到时间来研究下这方面的内容。
见证人打包顺序
db_witness_schedule.cpp
void database::update_witness_schedule()
{
const witness_schedule_object& wso = witness_schedule_id_type()(*this);
const global_property_object& gpo = get_global_properties();
if( head_block_num() % gpo.active_witnesses.size() == 0 )
{
modify( wso, [&]( witness_schedule_object& _wso )
{
_wso.current_shuffled_witnesses.clear();
_wso.current_shuffled_witnesses.reserve( gpo.active_witnesses.size() );
for( const witness_id_type& w : gpo.active_witnesses )
_wso.current_shuffled_witnesses.push_back( w );
auto now_hi = uint64_t(head_block_time().sec_since_epoch()) << 32;
for( uint32_t i = 0; i < _wso.current_shuffled_witnesses.size(); ++i )
{
/// High performance random generator
/// http://xorshift.di.unimi.it/
uint64_t k = now_hi + uint64_t(i)*2685821657736338717ULL;
k ^= (k >> 12);
k ^= (k << 25);
k ^= (k >> 27);
k *= 2685821657736338717ULL;
uint32_t jmax = _wso.current_shuffled_witnesses.size() - i;
uint32_t j = i + k%jmax;
std::swap( _wso.current_shuffled_witnesses[i],
_wso.current_shuffled_witnesses[j] );
}
});
}
}
这个函数做了活动见证人的随机排序,从代码看是用了一个高性能的随机数生成器,然后根据活动见证人个数在for循环中每次执行一个随机换位。
代码中有 head_block_num() % gpo.active_witnesses.size() == 0 的判断,也就是说每个活动见证人都打包过一次后重新排序。
update_witness_schedule这个函数会在
void database::_apply_block( const signed_block& next_block )
中调用,也就是每个块调用一次。
见证人选择
那么上面的 active_witnesses 又是怎么确定的呢?
初始指定见证人
首先在init_genesis中会对增加指定的初始见证人,并按序加到current_shuffled_witnesses数组中,如下:
db_init.cpp
// Set active witnesses
modify(get_global_properties(), [&](global_property_object& p) {
for( uint32_t i = 1; i <= genesis_state.initial_active_witnesses; ++i )
{
p.active_witnesses.insert(witness_id_type(i));
}
});
活动见证人个数选择
同样的在 _apply_block 中会做维护时间是否到达的判断,如果到达链维护时间则执行维护函数perform_chain_maintenance
bool maint_needed = (dynamic_global_props.next_maintenance_time <= next_block.timestamp);
...
// Are we at the maintenance interval?
if( maint_needed )
perform_chain_maintenance(next_block, global_props);
在这个函数中会调用 update_active_witnesses()函数。
db_maint.cpp
void database::update_active_witnesses()
{ try {
assert( _witness_count_histogram_buffer.size() > 0 );
share_type stake_target = (_total_voting_stake-_witness_count_histogram_buffer[0]) / 2;
/// accounts that vote for 0 or 1 witness do not get to express an opinion on
/// the number of witnesses to have (they abstain and are non-voting accounts)
share_type stake_tally = 0;
size_t witness_count = 0;
if( stake_target > 0 )
{
while( (witness_count < _witness_count_histogram_buffer.size() - 1)
&& (stake_tally <= stake_target) )
{
stake_tally += _witness_count_histogram_buffer[++witness_count];
}
}
const auto& all_witnesses = this->get_index_type<witness_index>().indices();
for (const witness_object& witness : all_witnesses)
database_check_witness(*this,witness);
const chain_property_object& cpo = get_chain_properties();
auto wits = sort_votable_objects<witness_index>(std::max(witness_count*2+1, (size_t)cpo.immutable_parameters.min_witness_count));
const global_property_object& gpo = get_global_properties();
for( const witness_object& wit : all_witnesses )
{
modify( wit, [&]( witness_object& obj ){
obj.total_votes = _vote_tally_buffer[wit.vote_id];
});
}
// Update witness authority
modify( get(GRAPHENE_WITNESS_ACCOUNT), [&]( account_object& a )
{
if( head_block_time() < HARDFORK_533_TIME )
{
uint64_t total_votes = 0;
map<account_id_type, uint64_t> weights;
a.active.weight_threshold = 0;
a.active.clear();
for( const witness_object& wit : wits )
{
weights.emplace(wit.witness_account, _vote_tally_buffer[wit.vote_id]);
total_votes += _vote_tally_buffer[wit.vote_id];
}
// total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits,
// then I want to keep the most significant 16 bits of what's left.
int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0);
for( const auto& weight : weights )
{
// Ensure that everyone has at least one vote. Zero weights aren't allowed.
uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) );
a.active.account_auths[weight.first] += votes;
a.active.weight_threshold += votes;
}
a.active.weight_threshold /= 2;
a.active.weight_threshold += 1;
}
else
{
vote_counter vc;
for( const witness_object& wit : wits )
{
vc.add( wit.witness_account, _vote_tally_buffer[wit.vote_id] );
idump((wit));
}
vc.finish( a.active );
}
} );
modify(gpo, [&]( global_property_object& gp ){
gp.active_witnesses.clear();
gp.active_witnesses.reserve(wits.size());
std::transform(wits.begin(), wits.end(),
std::inserter(gp.active_witnesses, gp.active_witnesses.end()),
[](const witness_object& w) {
return w.id;
});
});
} FC_CAPTURE_AND_RETHROW() }
这个函数比较有意思,代码全放上来吧。
最前面有注释说明了,如果一个帐户不投票见证人或者只投票给一个见证人,则不能对见证人数量有任何影响,因为不投票见证人或者只投票给一个见证人在见证人个数直方图(_witness_count_histogram_buffer)上投票数量都计在第一个数组元素_witness_count_histogram_buffer[0]中,更详细看后面 vote_tally_helper 那部分代码说明。
stake_target是来决定有多少个见证人的数值,等于投两个见证人以上的帐户的投票数总和除以二,如下:
share_type stake_target = (_total_voting_stake-_witness_count_histogram_buffer[0]) / 2;
从_witness_count_histogram_buffer[1]开始累加投票数,只到累加投票数大于stake_target,累加个数即为活动见证人个数,如下:
while( (witness_count < _witness_count_histogram_buffer.size() - 1)
&& (stake_tally <= stake_target) )
{
stake_tally += _witness_count_histogram_buffer[++witness_count];
}
假设没有一个帐户投见证人个数大致2人,则会按最小见证人处理。
database_check_witness()会对每个见证人允许状态、丢包数、创世见证人、资产等做了判断。
会根据见证人得到投票数进行排序,至少会有链参数 min_witness_count 个见证人,当前值是11,如下:
auto wits = sort_votable_objects<witness_index>(std::max(witness_count*2+1, (size_t)cpo.immutable_parameters.min_witness_count));
根据 _vote_tally_buffer 的计数,把投票数设到每个见证人对象上,如下:
obj.total_votes = _vote_tally_buffer[wit.vote_id];
db_maint.cpp -> vote_tally_helper
struct vote_tally_helper {
database& d;
const global_property_object& props;
vote_tally_helper(database& d, const global_property_object& gpo)
: d(d), props(gpo)
{
d._vote_tally_buffer.resize(props.next_available_vote_id);
d._witness_count_histogram_buffer.resize(props.parameters.maximum_witness_count / 2 + 1);
d._committee_count_histogram_buffer.resize(props.parameters.maximum_committee_count / 2 + 1);
d._total_voting_stake = 0;
}
void operator()(const account_object& stake_account) {
if( props.parameters.count_non_member_votes || stake_account.is_member(d.head_block_time()) )
{
// There may be a difference between the account whose stake is voting and the one specifying opinions.
// Usually they're the same, but if the stake account has specified a voting_account, that account is the one
// specifying the opinions.
const account_object& opinion_account =
(stake_account.options.voting_account ==
GRAPHENE_PROXY_TO_SELF_ACCOUNT)? stake_account
: d.get(stake_account.options.voting_account);
const auto& stats = stake_account.statistics(d);
uint64_t voting_stake = stats.total_core_in_orders.value
+ (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value: 0)
+ d.get_balance(stake_account.get_id(), asset_id_type()).amount.value;
for( vote_id_type id : opinion_account.options.votes )
{
uint32_t offset = id.instance();
// if they somehow managed to specify an illegal offset, ignore it.
if( offset < d._vote_tally_buffer.size() )
d._vote_tally_buffer[offset] += voting_stake;
}
if( opinion_account.options.num_witness <= props.parameters.maximum_witness_count )
{
uint16_t offset = std::min(size_t(opinion_account.options.num_witness/2),
d._witness_count_histogram_buffer.size() - 1);
// votes for a number greater than maximum_witness_count
// are turned into votes for maximum_witness_count.
//
// in particular, this takes care of the case where a
// member was voting for a high number, then the
// parameter was lowered.
d._witness_count_histogram_buffer[offset] += voting_stake;
}
if( opinion_account.options.num_committee <= props.parameters.maximum_committee_count )
{
uint16_t offset = std::min(size_t(opinion_account.options.num_committee/2),
d._committee_count_histogram_buffer.size() - 1);
// votes for a number greater than maximum_committee_count
// are turned into votes for maximum_committee_count.
//
// same rationale as for witnesses
d._committee_count_histogram_buffer[offset] += voting_stake;
}
d._total_voting_stake += voting_stake;
}
}
} tally_helper(*this, gpo);
struct process_fees_helper {
database& d;
const global_property_object& props;
process_fees_helper(database& d, const global_property_object& gpo)
: d(d), props(gpo) {}
void operator()(const account_object& a) {
a.statistics(d).process_fees(a, d);
}
} fee_helper(*this, gpo);
perform_account_maintenance(std::tie(
tally_helper,
fee_helper
));
在 perform_chain_maintenance()中定义了一个vote_tally_helper类,用来处理投票计数。
构造函数中会设置_witness_count_histogram_buffer大小,按当前数据就是 1001/2+1=501,如下:
d._witness_count_histogram_buffer.resize(props.parameters.maximum_witness_count / 2 + 1);
重载了操作符()参数是account_object&,在这个函数中可以看到投票权重与三个数值有关,如下:
const auto& stats = stake_account.statistics(d);
uint64_t voting_stake = stats.total_core_in_orders.value
+ (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value: 0)
+ d.get_balance(stake_account.get_id(), asset_id_type()).amount.value;
根据每个帐号投票的见证人列表,会在 _vote_tally_buffer 中把见证人的票数进行统计,如下:
for( vote_id_type id : opinion_account.options.votes )
{
uint32_t offset = id.instance();
// if they somehow managed to specify an illegal offset, ignore it.
if( offset < d._vote_tally_buffer.size() )
d._vote_tally_buffer[offset] += voting_stake;
}
同时会根据每个帐号投票的见证人个数不同,在 _witness_count_histogram_buffer 中做统计,如下:
if( opinion_account.options.num_witness <= props.parameters.maximum_witness_count )
{
uint16_t offset = std::min(size_t(opinion_account.options.num_witness/2),
d._witness_count_histogram_buffer.size() - 1);
// votes for a number greater than maximum_witness_count
// are turned into votes for maximum_witness_count.
//
// in particular, this takes care of the case where a
// member was voting for a high number, then the
// parameter was lowered.
d._witness_count_histogram_buffer[offset] += voting_stake;
}
从上面可以看出,如果不投票给见证人或者只投票一个见证人,offset为0,所有的票数都会加在_witness_count_histogram_buffer[0]上。
注意后面还有一个vector清除操作,如下:
struct clear_canary {
clear_canary(vector<uint64_t>& target): target(target){}
~clear_canary() { target.clear(); }
private:
vector<uint64_t>& target;
};
clear_canary a(_witness_count_histogram_buffer),
b(_committee_count_histogram_buffer),
c(_vote_tally_buffer);
这些vector变量是在析构中clear()的,也就是在perform_chain_maintenance()函数执行完时才会清除,还有 _total_voting_stake 也是在进入时设为0,这些数据每次都会重新统计。
这一段代码比较有意思就是活动见证人个数的决定,如果一个人票数足够多,对活动见证人个数就能起到比较大的作用,他可以少投一些见证人就能让见证人个数减少,而多投一些见证人可能让见证人个数增加。
大神的特权
在cli_wallet中调用 vote_for_witness 时发现 num_witness 并没有跟着votes数量改变,查看代码也的确没有修改 num_witness 的地方,但在bitshares-ui中却可以改变,原来在bitshares-ui中有这样一段代码调用:
AccountVoting.jsx
new_options.voting_account = new_proxy_id ? new_proxy_id : "1.2.5";
new_options.num_witness = this.state.witnesses.size;
new_options.num_committee = this.state.committee.size;
updateObject.new_options = new_options;
而cli_wallet还有一个接口叫做 set_desired_witness_and_committee_member_count 可以设置期望的见证人个数和理事会成员个数,那这个是干嘛的呢?
实际上用这个并不能随意设置 num_witness 值,因为在account.cpp中又有判断逻辑,如下:
void account_options::validate() const
{
auto needed_witnesses = num_witness;
auto needed_committee = num_committee;
for( vote_id_type id : votes )
if( id.type() == vote_id_type::witness && needed_witnesses )
--needed_witnesses;
else if ( id.type() == vote_id_type::committee && needed_committee )
--needed_committee;
FC_ASSERT( needed_witnesses == 0 && needed_committee == 0 ,
"May not specify fewer witnesses or committee members than the number voted for.");
}
看起来通过这个接口可以起到某种控制,例如票数够多的话可以尽量控制见证人数量,也就是控制投票都在 _committee_count_histogram_buffer 这个直方图数组的低位,同时还可以通过cli_wallet给需要的见证人投票,而不会增加活动见证人个数。
感谢您阅读 @chaimyu 的帖子,期待您能留言交流!
恭喜你!您的这篇文章入选 @justyy 今日 (2018-06-21) 榜单 【优秀被错过的文章】, 回复本条评论24小时内领赏,点赞本评论将支持 @dailychina 并增加将来您的奖赏。
Congratulations! This post has been selected by @justyy as today's (2018-06-21) 【Good Posts You May Miss】, Steem On! Reply to this message in 24 hours to get rewards. Upvote this comment to support the @dailychina and increase your future rewards! ^_^
可爱的justyy啊
写得很好,受益匪浅!
有用就好,你们玩得比较熟
你今天过的开心吗?你的帖子要不要被推广?去@aellly 西瓜集志社。如果不想再收到我的留言,请回复“取消”。