bitshares研究系列【见证人选择机制】

in #bitshares6 years ago (edited)

前面文章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 的帖子,期待您能留言交流!

Sort:  

恭喜你!您的这篇文章入选 @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 西瓜集志社。如果不想再收到我的留言,请回复“取消”。

Coin Marketplace

STEEM 0.19
TRX 0.14
JST 0.030
BTC 64535.04
ETH 3462.08
USDT 1.00
SBD 2.49