在每一個 trx 交易中,咱們都會看到 ref_block_num 和 ref_block_prefix, 這2個參數有什麼做用呢。git
先講下它的大體做用,再來對代碼進行分析。安全
這是白皮書對這2個參數的做用描述ui
Transaction as Proof of Stake (TaPoS) The EOS.IO software requires every transaction to include part of the hash of a recent block header. This hash serves two purposes: 1. prevents a replay of a transaction on forks that do not include the referenced block; and 2. signals the network that a particular user and their stake are on a specific fork. Over time all users end up directly confirming the blockchain which makes it difficult to forge counterfeit chains as the counterfeit would not be able to migrate transactions from the legitimate chain. 1. 他是用來防止有不包含區塊引用的交易被重放到某個分叉上, 這樣能避免不是該分叉的區塊被添加到該分叉。 2. 告訴用戶該塊是在哪一個分支上面。
這樣作有什麼做用呢?this
假設如今有2個用戶 A 和 B, B 叫 A 說你轉 2 個 EOS 給我, 我就送你 100 個 LIVE,A 說好啊。 而後 A 就轉 2 個 EOS 給 B 了, 這個時候 A 的區塊 a 還不是不可逆狀態, 若是此時 B 轉給 A 100 個 LIVE, 要是 區塊 a 被回滾掉了怎麼辦,那麼 B 就白白給了 A 100 個 LIVE 了。 這時候 ref-block 的做用就體現了,若是區塊 a 被回滾了,那麼 B 轉給 A 100 個 LIVE 的區塊 b 也會被丟棄掉。 因此 當區塊 b ref-block 是 區塊 a 的時候,只有 區塊 a 被成功打包了, 區塊 b 纔會被成功打包。code
因此很顯然, 這兩個參數是爲了讓鏈更穩固,也讓用戶交易更安全。索引
先看下 transaction_header 對這兩個字段的描述。ip
struct transaction_header { // ... // 能夠指定 head_block_num - 0xffff ~ head_block_num 之間的塊。 uint16_t ref_block_num = 0U; ///< specifies a block num in the last 2^16 blocks. // block_id 的按 32 bits分割的第二個部分,也就是 block_id._hash[1]; uint32_t ref_block_prefix = 0UL; ///< specifies the lower 32 bits of the blockid at // ... };
再來看下該參數如何被驗證。ci
// 在 trx 初始化的時候便回去驗證 void transaction_context::init_for_input_trx( uint64_t packed_trx_unprunable_size, uint64_t packed_trx_prunable_size, uint32_t num_signatures, bool skip_recording ) { //... if (!control.skip_trx_checks()) { control.validate_expiration(trx); control.validate_tapos(trx); control.validate_referenced_accounts(trx); } //... } void controller::validate_tapos( const transaction& trx )const { try { const auto& tapos_block_summary = db().get<block_summary_object>((uint16_t)trx.ref_block_num); //Verify TaPoS block summary has correct ID prefix, and that this block's time is not past the expiration EOS_ASSERT(trx.verify_reference_block(tapos_block_summary.block_id), invalid_ref_block_exception, "Transaction's reference block did not match. Is this transaction from a different fork?", ("tapos_summary", tapos_block_summary)); } FC_CAPTURE_AND_RETHROW() } bool transaction_header::verify_reference_block( const block_id_type& reference_block )const { return ref_block_num == (decltype(ref_block_num))fc::endian_reverse_u32(reference_block._hash[0]) && ref_block_prefix == (decltype(ref_block_prefix))reference_block._hash[1]; }
從 block_summary_object 獲取的 block 數據拿來跟 ref-block 的, 很奇怪爲何不直接用 get_block 那種方式取 block 的信息呢? 這樣不用維護多一個多索引容器,並且還能獲取所有的 block 。 來看看 block_summary_object 是如何建立和維護的。get
// libraries/chain/include/eosio/chain/block_summary_object.hpp class block_summary_object : public chainbase::object<block_summary_object_type, block_summary_object> { OBJECT_CTOR(block_summary_object) id_type id; block_id_type block_id; }; struct by_block_id; using block_summary_multi_index = chainbase::shared_multi_index_container< block_summary_object, indexed_by< ordered_unique<tag<by_id>, BOOST_MULTI_INDEX_MEMBER(block_summary_object, block_summary_object::id_type, id)> // ordered_unique<tag<by_block_id>, BOOST_MULTI_INDEX_MEMBER(block_summary_object, block_id_type, block_id)> > >; // 建立了 id 從 0 ~ 65535 的數據 void contoller_impl::initialize_database() { // Initialize block summary index for (int i = 0; i < 0x10000; i++) db.create<block_summary_object>([&](block_summary_object&) {}); // ... } // 每次添加新的區塊的時候都回去更新 block_summary_object 的 索引表 void contoller_impl::finalize_block() { // ... auto p = pending->_pending_block_state; p->id = p->header.id(); create_block_summary(p->id); } FC_CAPTURE_AND_RETHROW() } void create_block_summary(const block_id_type& id) { auto block_num = block_header::num_from_id(id); // 從這裏能夠看出 block_summary_object 的 id 永遠都是 0 ~ 65535。也就是說它只維護 head_block_num - 0xffff ~ head_block_num 的塊, 你 ref-block 只能是這個區間的塊, 若是 ref 更早的 block 就會驗證出錯。 auto sid = block_num & 0xffff; db.modify( db.get<block_summary_object,by_id>(sid), [&](block_summary_object& bso ) { bso.block_id = id; }); }
cleos 在 push transaction 的時候默認的 ref-block 是取 last_irreversible_block ,當head_block_num 跟 lib_num 相差超出 0xffff 個塊的時候就會出現該錯誤:input
Error 3040007: Invalid Reference Block
Ensure that the reference block exist in the blockchain!
Error Details:
Transaction's reference block did not match. Is this transaction from a different fork?
若是你的私鏈出現問題,檢查你鏈上有沒 2/3 個 BP 在出塊,若是沒有則是由於沒確認塊,致使 head_block 和 lib 之間超過了 0xffff 個塊而致使該錯誤。
結論: ref-block 的主要做用從白皮書能夠看出,它是爲了創建一條難以造假的鏈, 由於其餘鏈違法從 合法鏈鏈直接遷移交易,只能添加交易。每一個 block 都會 ref-block 前面的數據, 你也沒法直接 ref-block 的早期的塊,由於只能 ref-block 只能是從 head_block_num - 0xffff ~ head_block_num, 像比特幣,只要你算力足夠,你從第一個塊從新建造一條鏈均可以。 而且他告訴用戶當前交易是在哪一個分叉上, 這樣用戶能夠根據交易須要在哪條分叉上成功來指定分叉, 也就是咱們上面舉的例子。