bitshares基本概念详解【见证人】

见证人是什么?

见证人是为区块链打包生成新的区块的实体。每一个见证人由股东批准,打包经验证的交易,生成并签署区块。每一条进入网络的交易最终将被所有见证人验证。

由谁在什么时间来打包生成区块是由被称为Delegated Proof of Stak (DPOS)的共识算法决定的。算法的本质是 BitShares 的股东(BTS的持有者)能通过投票来决定他们期望的块打包者。由获得最多票数的所谓"见证人"来打包生成区块。

见证人是比特股系统中运行服务器的雇员:打包交易出块、为智能货币输入喂价、提供强劲内存满足 100000+ TPS 的交易撮合服务等。

目前比特股这个 DAC 的理事会投票通过的参数是:见证人可以获得系统储备资金池中提供的 witness_pay_per_block指定的奖励(根据 DAC的发展和运营需要可以修改这个参数,网络收取的用户支付的每笔手续费的 20% 则回流到储备资金池;未来如果系统交易量非常大,见证人则需要运行性能更强大的服务器,到时候可以根据需要取消这个奖励或者减少这个奖励,而把网络收取的 20%

交易手续费全部或部分奖励给见证人,总之这些参数都是可以在不硬分叉的情况下进行修改的)。

get_object 2.0.0可以取到这些参数,如下:

      "network_percent_of_fee": 2000,
      "witness_pay_per_block": 100000,

现在每产生一个块可获得1BTS。(参考bitshares研究系列【喂价和资产抵押率】中的精度说明)


怎么成为见证人?

成为见证人好像需要用cli_wallet,而不能直接网页钱包操作,以下用cli_wallet示例。

建立用户

unlocked >>> suggest_brain_key   
suggest_brain_key
{
  "brain_priv_key": "OVERCUT CLINK BROOCH POWDRY CORNCOB RESTYLE THIRDLY DOSSAL ANTOECI ADJECT AMIL HEALER ATAXIA PETROL AMPALEA INVEIN",
  "wif_priv_key": "5JfXK7Ld4ok4RXBkS52Y3QAwxt6UkjBj6xUL9FNcne1V75VQ9rF",
  "pub_key": "BTS7Arpuipwe4Tik2bQubPjWFgWtqoiVuZkosbgfSosP8Hog6W58n"
}
unlocked >>> create_account_with_brain_key "OVERCUT CLINK BROOCH POWDRY CORNCOB RESTYLE THIRDLY DOSSAL ANTOECI ADJECT AMIL HEALER ATAXIA PETROL AMPALEA INVEIN" barnard007 nathan nathan true
create_account_with_brain_key "OVERCUT CLINK BROOCH POWDRY CORNCOB RESTYLE THIRDLY DOSSAL ANTOECI ADJECT AMIL HEALER ATAXIA PETROL AMPALEA INVEIN" barnard007 nathan nathan true
884616ms th_a       wallet.cpp:779                save_wallet_file     ] saving wallet to file wallet.json

wallet.cpp / account_evaluator.cpp

wallet实际调用函数 create_account_with_private_key,从脑钥生成owner key,再由owner key获得active key和memo key,发送 account_create_operation 到节点。

创建帐号是不会传私钥到节点的,只会把公钥发送给节点,私钥总是在自己的钱包中。

节点主要创建帐号代码如下:

   const auto& new_acnt_object = db().create<account_object>( [&]( account_object& obj ){
         obj.registrar = o.registrar;
         obj.referrer = o.referrer;
         obj.lifetime_referrer = o.referrer(db()).lifetime_referrer;

         auto& params = db().get_global_properties().parameters;
         obj.network_fee_percentage = params.network_percent_of_fee;
         obj.lifetime_referrer_fee_percentage = params.lifetime_referrer_percent_of_fee;
         obj.referrer_rewards_percentage = referrer_percent;

         obj.name             = o.name;
         obj.owner            = o.owner;
         obj.active           = o.active;
         obj.options          = o.options;
         obj.statistics = db().create<account_statistics_object>([&](account_statistics_object& s){s.owner = obj.id;}).id;

         if( o.extensions.value.owner_special_authority.valid() )
            obj.owner_special_authority = *(o.extensions.value.owner_special_authority);
         if( o.extensions.value.active_special_authority.valid() )
            obj.active_special_authority = *(o.extensions.value.active_special_authority);
         if( o.extensions.value.buyback_options.valid() )
         {
            obj.allowed_assets = o.extensions.value.buyback_options->markets;
            obj.allowed_assets->emplace( o.extensions.value.buyback_options->asset_to_buy );
         }
   });

升为终身会员

unlocked >>> transfer nathan barnard007 1000000000 BTS "How are you?" true

unlocked >>> upgrade_account barnard007 true

wallet.cpp / account_evaluator.cpp

发送一个 account_upgrade_operation 到节点,节点更新帐号数据。节点主要代码如下:

void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator::operation_type& o)
{ try {
   database& d = db();

   d.modify(*account, [&](account_object& a) {
      if( o.upgrade_to_lifetime_member )
      {
         // Upgrade to lifetime member. I don't care what the account was before.
         a.statistics(d).process_fees(a, d);
         a.membership_expiration_date = time_point_sec::maximum();
         a.referrer = a.registrar = a.lifetime_referrer = a.get_id();
         a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - a.network_fee_percentage;
      }
      ...

创建见证人

先要创建一个可以投票的见证对象

unlocked >>> create_witness barnard007 "url.barnard007" true
create_witness barnard007 "url.barnard007" true
{
  "ref_block_num": 46138,
  "ref_block_prefix": 523909841,
  "expiration": "2018-05-12T10:55:20",
  "operations": [[
      20,{
        "fee": {
          "amount": 500000000,
          "asset_id": "1.3.0"
        },
        "witness_account": "1.2.31",
        "url": "url.barnard007",
        "block_signing_key": "BTS5oS4VKJFEHo5wMXhSd3JuxatNw1Zw6qBaY4qa8ru3ZfPHbdQgZ"
      }
    ]
  ],
  "extensions": [],
  "signatures": [
    "2035263900a78b2e1bf58715def3714ce4bbd393ebdf8a9ffb4a9ff5ed1b59239054230fbab38a28e7b7ed5866e9b9fa815e99a5a91102065f4ee21018343d4563"
  ]
}

wallet.cpp

wallet发送 witness_create_operation 到节点,节点只做了是否终身会员的判断,就执行创建工作:

object_id_type witness_create_evaluator::do_apply( const witness_create_operation& op )
{ try {
   vote_id_type vote_id;
   db().modify(db().get_global_properties(), [&vote_id](global_property_object& p) {
      vote_id = get_next_vote_id(p, vote_id_type::witness);
   });

   const auto& new_witness_object = db().create<witness_object>( [&]( witness_object& obj ){
         obj.witness_account  = op.witness_account;
         obj.signing_key      = op.block_signing_key;
         obj.vote_id          = vote_id;
         obj.url              = op.url;
   });
   return new_witness_object.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }

投票只能按维护时间每天记录一次,下一次维护时间可以通过"get_object 2.1.0"或者 get_dynamic_global_properties 获得,把"2.1.0"对象数据列出参考:

get_object 2.1.0
[{
    "id": "2.1.0",
    "head_block_number": 46548,
    "head_block_id": "0000b5d4117b93c582547053c8d5be79f252bc16",
    "time": "2018-05-12T11:29:00",
    "current_witness": "1.6.8",
    "next_maintenance_time": "2018-05-13T00:00:00",
    "last_budget_time": "2018-05-12T00:04:40",
    "witness_budget": 0,
    "accounts_registered_this_interval": 1,
    "recently_missed_count": 655624,
    "current_aslot": 244934,
    "recent_slots_filled": "340282366920938463463374607431768211455",
    "dynamic_flags": 0,
    "last_irreversible_block_num": 46540
  }
]

db_main.cpp

维护函数,内部调用更新活动见证人等

void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props)
{
   ...
   update_top_n_authorities(*this);
   update_active_witnesses();
   update_active_committee_members();
   update_worker_votes();
   ...    
}

db_block.cpp

每次产生块都会进行判断,是否到了维护时间,如果到了执行维护函数

void database::_apply_block( const signed_block& next_block )
{ try {
   ...
   bool maint_needed = (dynamic_global_props.next_maintenance_time <= next_block.timestamp);
   ...
   if( maint_needed )
      perform_chain_maintenance(next_block, global_props);
}

这样见证人注册到系统,但不能打包,还需要有人投票。

unlocked >>> vote_for_witness nathan barnard007 true true

要到下一个维护周期结束,才能用 get_global_properties 或者 "get_object 2.0.0"看到见证人信息。


如何给见证人投票?

bitshares wallet

在右上角菜单的"Voting"项,可以对见证人投票:

cli_wallet

给见证人投票可以cli_wallet执行vote_for_witness,以下为给"fox"(id为1.2.167)投票:

vote_for_witness barnard18 1.2.167 true true
{
  "ref_block_num": 19578,
  "ref_block_prefix": 11051924,
  "expiration": "2018-05-09T09:36:03",
  "operations": [[
      6,{
        "fee": {
          "amount": 815,
          "asset_id": "1.3.0"
        },
        "account": "1.2.861586",
        "new_options": {
          "memo_key": "BTS5snEyiDkP6dReWafV4jqoHZYqcg2D8L4n2yUmBipyysLmZsRna",
          "voting_account": "1.2.5",
          "num_witness": 0,
          "num_committee": 0,
          "votes": [
            "1:26"
          ],
          "extensions": []
        },
        "extensions": {}
      }
    ]
  ],
  "extensions": [],
  "signatures": [
    "1f576e66c899c549ff19623047646469c3f9944e2c01f7ab4a5e3fcfcdad7cba1f3a9f0627e9af1c2700e393d6340be786365df5f49bf392f2373f70c514b5e949"
  ]
}

注意参数都是帐户名或者id

投票完成后,可以在自己的信息中看到,如下:

get_account barnard18
{
  "id": "1.2.861586",
  ...
  "options": {
    "memo_key": "BTS5snEyiDkP6dReWafV4jqoHZYqcg2D8L4n2yUmBipyysLmZsRna",
    "voting_account": "1.2.5",
    "num_witness": 0,
    "num_committee": 0,
    "votes": [
      "1:26"
    ],
    "extensions": []
  }
  ...
}

votes字段中指定了见证人对象中的vote_id,格式为:vote_type:instance,代码中定义如下:

// vote.hpp
struct vote_id_type
{
   /// Lower 8 bits are type; upper 24 bits are instance
   uint32_t content;

   friend size_t hash_value( vote_id_type v ) { return std::hash<uint32_t>()(v.content); }
   enum vote_type
   {
      committee,
      witness,
      worker,   
      VOTE_TYPE_COUNT
   };
   ...
}

vote_type是个枚举(从0开始计数,committee=0...),所以"1:26"就表示投了witness(见证人)的票,对应见证人对象的vote_id是1:26。

wallet.cpp / account_evaluator.cpp

wallet发送 account_update_operation 给节点,节点调用 verify_account_votes() 做了比较多判断,如见证人是否存在等。

整个代码判断比较复杂,但此处投票更新操作只有更新options,就一行代码。

void_result account_update_evaluator::do_apply( const account_update_operation& o )
{
     ...
     if( o.new_options ) a.options = *o.new_options;
     ...
}

见证人怎么打包

注册为见证人后,就可以通过 get_witness 取得见证对象数据:

unlocked >>> get_witness barnard007
get_witness barnard007
{
  "id": "1.6.13",
  "witness_account": "1.2.31",
  "last_aslot": 0,
  "signing_key": "BTS5oS4VKJFEHo5wMXhSd3JuxatNw1Zw6qBaY4qa8ru3ZfPHbdQgZ",
  "vote_id": "1:24",
  "total_votes": 0,
  "url": "url.barnard007",
  "total_missed": 0,
  "last_confirmed_block_num": 0
}

取得见证人对象id和signing_key,我们需要得到私钥:

unlocked >>> dump_private_keys      
dump_private_keys
[[
    "BTS5oS4VKJFEHo5wMXhSd3JuxatNw1Zw6qBaY4qa8ru3ZfPHbdQgZ",
    "5KFH7QukTZyrBQg4hAjeYgmLpmKx8sXF2BL2zn6apDzjz2KxzGd"
  ]
  ...
]

重新启动节点,指定见证人信息:

./witness_node --data-dir=witness_node_data_dir --enable-stale-production --seed-nodes "[]" --witness-id '"1.6.13"' --private-key '["BTS5oS4VKJFEHo5wMXhSd3JuxatNw1Zw6qBaY4qa8ru3ZfPHbdQgZ", "5KFH7QukTZyrBQg4hAjeYgmLpmKx8sXF2BL2zn6apDzjz2KxzGd"]'

或者在 witness_node_data_dir 目录下的config.ini中指定见证人信息。

正常情况下见证人就能产生块了,“见证人打包”说起来很复杂,其实也就是运行节点自动做了包的签名而已,并不用手工去做什么太多事情。当然见证人需要有良好的硬件条件保证打包的稳定,否则也会失去见证人打包资格。


见证人有无到底有多大影响?

Bitshares采用DPOS共识机制,不像BTC、ETH的POW机制,见证人节点是非常重要的一环,如果没有见证人整个链就无法正常运行了。

见证人的切换主要在db_witness_schedule.cpp中处理,等有时间再单独分析。


感谢您阅读 @chaimyu 的帖子,期待您能留言交流!

H2
H3
H4
Upload from PC
Video gallery
3 columns
2 columns
1 column
2 Comments