见证人是什么?
见证人是为区块链打包生成新的区块的实体。每一个见证人由股东批准,打包经验证的交易,生成并签署区块。每一条进入网络的交易最终将被所有见证人验证。
由谁在什么时间来打包生成区块是由被称为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 的帖子,期待您能留言交流!