乐趣区

关于智能合约:智能合约编写高级篇二区块哈希介绍

本文档从区块哈希基本概念登程,具体介绍了中移链的区块哈希交易接口和利用方向。实用于 EOS 区块链智能合约高级开发人员,相熟如何获取以后产生交易所在的区块号和区块哈希前缀,并通过 Tapos 机制验证交易的有效性。

01

概述

(一)哈希算法

哈希算法是能够将任意长度的二进制数据映射为固定长度二进制数据(哈希值)的一种算法。在这个过程中,哈希函数将输出数据通过一系列的简单运算变换成固定长度的输入,这个值等同于存放数据的地址,这个地址外面再将输出的数据进行存储,所以哈希函数能够将互联网上的数据以固定长度字符串的模式来保留。同时,哈希函数能够用于密码学、数据完整性验证、信息指纹等畛域,常见的哈希算法有 MD5、SHA-1、SHA-256 等。

(二)区块哈希

区块哈希是通过哈希算法对区块中的所有数据进行计算得出的固定长度的字符串。具体来说,区块哈希是指在区块链技术中,对于每一个新生产的区块,会给这个区块计算一个固定长度的哈希值,这个哈希值蕴含了这个区块中所有的数据,包含交易记录、上一个区块的哈希值、工夫戳等,并且只有这些数据有任何一点扭转,那么这个区块的哈希值就会发生变化,所以它在保障网络安全性、避免篡改和验证数据完整性方面起着十分重要的作用。

(三)区块哈希的特点

区块哈希的存在保障了区块链的数据安全性,任何篡改数据的行为都会被立刻发现,同时它也具备以下特点:

唯一性:每个区块哈希值都是惟一的,即便是区块链上有极其渺小的一点数据扭转,也会导致哈希值的变动。这保障了数据的不可变性和惟一标识性。

不可逆性:区块哈希函数是一个单向函数,能够将任意长度的数据转换为固定长度的哈希值。对于哈希值无奈进行反向计算推导复原原始数据,这保障了数据的安全性。

不可篡改性:如果输出数据产生了任何扭转,计算失去的哈希值都会发生变化。

02

环境依赖

eosio_2.1.0-1

eosio.cdt v1.8.x

03

区块哈希接口

与区块哈希相干的交易接口别离有 tapos_block_num() 和 tapos_block_prefix(),它们用于生成区块哈希及验证交易执行的前提条件,这有助于确保交易的有效性和安全性,并提供区块链的相干信息以反对智能合约的开发。

(一)tapos_block_num()

它是一个用于获取以后交易所援用的区块号的函数,它返回一个无符号整数值,代表以后执行交易的区块高度。块高度示意以后执行的块在整个区块链上的地位,能够用于构建块摘要和验证交易。在交易处理过程中,每个块都有一个惟一的块高度。

源码形容

   /**
    *  Gets the block number used for TAPOS on the currently executing transaction.
    *
    *  @ingroup transaction
    *  @return block number used for TAPOS on the currently executing transaction
    *  Example:
    *  @code
    *  int tbn = tapos_block_num();
    *  @endcode
    */
   inline int tapos_block_num() {return internal_use_do_not_use::tapos_block_num();
   }

调用形式

#include <eosio/transaction.hpp>
#include <eosio/eosio.hpp>
uint16_t current_block_num = tapos_block_num(); // 获取用于以后执行交易所在的区块号 

(二)tapos_block_prefix()

它是一个用于获取以后交易所在区块哈希前缀的函数,它返回一个无符号整数值,代表以后执行交易的区块哈希前缀。区块哈希前缀是区块哈希的一部分,用于构建 block summary。它通常作为一个随机数,用于减少区块哈希的难度,以放弃加密的安全性。

源码形容

/**
    *  Gets the block prefix used for TAPOS on the currently executing transaction.
    *
    *  @ingroup transaction
    *  @return block prefix used for TAPOS on the currently executing transaction
    *  Example:
    *  @code
    *  int tbp = tapos_block_prefix();
    *  @endcode
    */
   inline int tapos_block_prefix() {return internal_use_do_not_use::tapos_block_prefix();
   }

调用形式

#include <eosio/transaction.hpp>
#include <eosio/eosio.hpp>
uint32_t current_block_prefix = tapos_block_prefix(); // 获取用于以后执行交易所在的区块哈希前缀 

(三)什么是 TaPos 机制?

TaPos 是“交易作为权利证实”(Transaction-as-Proof-of-Stake)的缩写。TaPos 是在 EOS 的交易处理过程中引入的一个概念,它是一种用于实现去中心化共识的机制,目标是确保交易执行的前提条件和验证交易的有效性。在去中心化的区块链网络中,因为网络可能存在提早和分叉,交易的确认和执行程序可能会有所不同,所以引入 TaPos 机制来避免在不蕴含援用区块的分叉上重放交易,从而减少了安全性和可靠性。上面咱们来看一下 EOS 白皮书是如何对 TaPos 进行形容的。

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.
// 避免在不蕴含援用区块的分叉上重放交易。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.

(四)如何通过 TaPos 验证交易

通过 TaPoS 机制,EOS 网络能够确保交易的程序性并避免在不同块之间重放交易,所以每个交易都必须蕴含正确的 TaPoS 字段,即交易作为股权证实的一部分,在交易签名过程中,这些字段会与其余的交易信息一起打包进入交易,以便验证它们的有效性。为了让链更巩固,也让用户交易更平安,当链中每产生一笔交易时,都会验证两个字段,别离是 ref_block_num 和 ref_block_prefix,以下是 eosio.cdt 中对它们的申明。

// eosio.cdt/1.8.1/include/eosiolib/contracts/eosio/transaction.hpp
class transaction_header {
   public:
      /**
       * Construct a new transaction_header with an expiration of now + 60 seconds.
       *
       * @brief Construct a new transaction_header object initialising the transaction header expiration to now + 60 seconds
       */
      transaction_header(time_point_sec exp = time_point_sec(current_time_point()) + 60)
         :expiration(exp)
      {}

      time_point_sec  expiration;
      /// the time at which a transaction expires
      uint16_t        ref_block_num; 
      /// specifies a block num in the last 2^16 blocks
      uint32_t        ref_block_prefix; 
      /// specifies the lower 32 bits of the block id at get_ref_blocknum
      unsigned_int    max_net_usage_words = 0UL; /// number of 8 byte words this transaction can serialize into after compressions
      uint8_t         max_cpu_usage_ms = 0UL; /// number of CPU usage units to bill transaction for
      unsigned_int    delay_sec = 0UL; /// number of seconds to delay transaction, default: 0

      EOSLIB_SERIALIZE(transaction_header, (expiration)(ref_block_num)(ref_block_prefix)(max_net_usage_words)(max_cpu_usage_ms)(delay_sec) )
   };

以下代码是在交易初始化时候验证 ref_block_num 和 ref_block_prefix 两个字段。

// eos/libraries/chain/transaction.cpp
void transaction_header::set_reference_block(const block_id_type& reference_block) {ref_block_num    = fc::endian_reverse_u32(reference_block._hash[0]);
   ref_block_prefix = reference_block._hash[1];
}

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];
}
// eos/libraries/chain/transaction_context.cpp
void transaction_context::init_for_input_trx_common(uint64_t initial_net_usage, bool skip_recording)
   {published = control.pending_block_time();
      is_input = true;
      const transaction& trx = packed_trx.get_transaction();
      if (!control.skip_trx_checks()) {control.validate_expiration(trx);
         control.validate_tapos(trx);
         validate_referenced_accounts(trx, enforce_whiteblacklist && control.is_producing_block() );
      }
      init(initial_net_usage);
      
      if (!skip_recording)
         record_transaction(packed_trx.id(), trx.expiration ); /// checks for dupes
   }
// eos/libraries/chain/controller.cpp
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()}

(五)测试用例

编写智能合约测试用例,通过调用上文中介绍的 tapos_block_num() 和 tapos_block_prefix() 两个函数来获取验证交易有效性的两个字段 ref_block_num 和 ref_block_prefix。

#include <eosio/transaction.hpp>
#include <eosio/eosio.hpp>

using namespace eosio;

class [[eosio::contract("test")]] test: public contract {
public:
  using contract::contract;

  [[eosio::action]]
  void checktapos() {uint32_t current_block_num = tapos_block_num(); // 获取以后交易执行所在的区块号
    uint32_t current_block_prefix = tapos_block_prefix(); // 获取以后交易执行所在的区块哈希前缀
    print("ref_block_num:", current_block_num,"\t\t\t");
    print("ref_block_prefix:", current_block_prefix);
  }
};

返回后果如下:

executed transaction: b35a59a178af7dedbbad641952146470adbe5e4d48316382afec5941fcdf2372  96 bytes  146 us
#          test <= test::checktapos             ""
>> ref_block_num: 43983             ref_block_prefix: 448306994

以下是对应区块构造 transaction 中的 ref_block_num 和 ref_block_prefix。

root@VM-24-16-ubuntu:/home/ubuntu/biosboot/genesis# cleos get transaction b35a59a178af7dedbbad641952146470adbe5e4d48316382afec5941fcdf2372
{
  "id": "b35a59a178af7dedbbad641952146470adbe5e4d48316382afec5941fcdf2372",
  "trx": {
    "receipt": {
      "status": "executed",
      "cpu_usage_us": 146,
      "net_usage_words": 12,
      "trx": [
        1,{
          "compression": "none",
          "prunable_data": {
            "prunable_data": [
              0,{
                "signatures": ["SIG_K1_Jzmoz3cfi8H7tA3V8zidyihS7XYv29h1bnxdoFSM7ddvRXRo7q4GTJrgPKDsvbbXMFsaFxzCZeAqwwhG2GNVdjHXiL41y1"],
                "packed_context_free_data": ""
              }
            ]
          },
          "packed_trx": "6b6eae64cfab329fb81a0000000001000000000090b1ca0000a6d56488544301000000000090b1ca00000000a8ed32320000"
        }
      ]
    },
    "trx": {
      "expiration": "2023-07-12T09:12:11",
      "ref_block_num": 43983,
      "ref_block_prefix": 448306994,
      "max_net_usage_words": 0,
      "max_cpu_usage_ms": 0,
      "delay_sec": 0,
      "context_free_actions": [],
      "actions": [{
          "account": "test",
          "name": "checktapos",
          "authorization": [{
              "actor": "test",
              "permission": "active"
            }
          ],
          "data": ""
        }
      ],
      "signatures": ["SIG_K1_Jzmoz3cfi8H7tA3V8zidyihS7XYv29h1bnxdoFSM7ddvRXRo7q4GTJrgPKDsvbbXMFsaFxzCZeAqwwhG2GNVdjHXiL41y1"],
      "context_free_data": []}
  },
  "block_time": "2023-07-12T09:11:41.500",
  "block_num": 43985,
  "last_irreversible_block": 44144,
  "traces": [{
      "action_ordinal": 1,
      "creator_action_ordinal": 0,
      "closest_unnotified_ancestor_action_ordinal": 0,
      "receipt": {
        "receiver": "test",
        "act_digest": "f0d16f853ef72e7be5d8c84219cdcaa75d9b13d89b59c9385aebaecc20bc34f7",
        "global_sequence": 43996,
        "recv_sequence": 8,
        "auth_sequence": [[
            "test",
            11
          ]
        ],
        "code_sequence": 2,
        "abi_sequence": 1
      },
      "receiver": "test",
      "act": {
        "account": "test",
        "name": "checktapos",
        "authorization": [{
            "actor": "test",
            "permission": "active"
          }
        ],
        "data": ""},"context_free": false,"elapsed": 39,"console":"ref_block_num: 43983             ref_block_prefix: 448306994","trx_id":"b35a59a178af7dedbbad641952146470adbe5e4d48316382afec5941fcdf2372","block_num": 43985,"block_time":"2023-07-12T09:11:41.500","producer_block_id": null,"account_ram_deltas": [],"account_disk_deltas": [],"except": null,"error_code": null,"return_value_hex_data":""
    }
  ]
}

须要留神,ref_block_num 示意的是交易所援用的区块的区块号码,而 ref_block_prefix 是这个区块的哈希前缀。在交易签名过程中,这些字段会与其余的交易信息一起打包进入交易中,以便验证这个交易是否非法。而在 cleos get transaction 命令所显示的交易信息中,另一个名为 block_num 的字段则示意最终执行该交易的区块的区块号码,此字段与交易提交时所援用的区块号码 ref_block_num 是不同的。因为在提交交易时,可能会产生交易被提早,所以最终执行该交易的区块可能与该交易所援用的区块不同。

END

退出移动版