前面文章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().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(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 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(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& target): target(target){}
~clear_canary() { target.clear(); }
private:
vector& 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,这些数据每次都会重新统计。
这一段代码比较有意思就是活动见证人个数的决定,如果一个人票数足够多,对活动见证人个数就能起到比较大的作用,他可以少投一些见证人就能让见证人个数减少,而多投一些见证人可能让见证人个数增加。
感谢您阅读 @chaimyu 的帖子,期待您能留言交流!
https://steemit.com/bitshares/@chaimyu/imzcm-bitshares