EOS 源码解析 股权证实的交易(TaPos) 的做用

在每一个 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, 像比特币,只要你算力足够,你从第一个块从新建造一条链均可以。 而且他告诉用户当前交易是在哪一个分叉上, 这样用户能够根据交易须要在哪条分叉上成功来指定分叉, 也就是咱们上面举的例子。

原文: https://eos.live/detail/17094

相关文章
相关标签/搜索