Substrate Developer Hub

Substrate Developer Hub

  • 教程
  • 知识库
  • 进阶菜谱
  • API 文档
  • Languages icon简体中文
    • English
    • 繁體中文
    • 协助翻译

›Runtime

开始

  • 总览
  • 安装
  • 在 Windows 系统开始
  • 词汇表

学习 Substrate

  • Extrinsics
  • 交易池
  • 账户摘要
  • 会话密钥
  • 交易权重
  • 链下功能

Runtime

  • Runtime 总览
  • Runtime 的基本类型
  • FRAME
  • Pallets
  • Runtime宏
  • Runtime 元数据
  • Runtime 存储
  • Runtime 来源
  • Runtime 执行流程
  • Runtime事件
  • Runtime 错误
  • 交易费用
  • 链下工作机
  • 调试
  • Runtime 测试
  • 链上随机生成
  • Runtime 升级

智能合约

  • 总览
  • ink! 智能合约
  • ink! 概念
  • ink! 开发
  • EVM 模块
  • ink! 常问问题

整合

  • Polkadot-JS
  • 客户端库
  • 链描述
  • Subkey 工具
  • 内存分析

进阶

  • SCALE 编解码器
  • 共识机制
  • 区块导入过程
  • 执行器
  • 密码学
  • 存储
  • SS58 地址格式

贡献

  • 协助翻译
Translate

Runtime 元数据

建立在 Substrate 上的区块链会暴露出元数据,以便能轻松与其交互。 元数据根据不同的 pallets来源被分隔成不同模块, 对于每个模块,元数据都提供该模块对外暴露的 存储项、extrinsic 调用、事件、常量和错误的相关信息。 Substrate 会自动生成这些元数据,并通过 RPC 函数使它可被调用。

由于 Substrate 区块链的 runtime 是区块链状态的不断发展的一部分,因此区块链元数据是按每个区块存储的。 请注意,查询旧区块的元数据(例如使用存档节点) 可能会导致获取与区块链当前状态不兼容的过时元数据。 正如在 Runtime 升级 里说明,开发者可预期 Substrate 链的元数据 只有 在链的 runtime spec_version 改变时才更改。

本文下面所有示例均取自Kusama上的1,768,321区块。 在阅读本文剩余部分之前,您可以查看完整的元数据,并在后续阅读时继续参考它。

如何获取元数据

您可使用特定语言库或者与语言无关的HTTP和WebSocket API这两种渠道,来从Substrate节点中获取元数据。

Rust

获取元数据的最简单方法是查询自动生成的JSON-RPC函数state_getMetadata, 这将返回SCALE编码字节的向量。 可使用frame-metadata 和parity-scale-codec库对此进行解码。

一些有用的库(例如 substrate-subxt)将能获取元数据并为您解码。 然后再使用serde将结构序列化为JSON。 如果您喜欢更直接地使用RPC,则可采用JSONRPC 和jsonrpseeRust库的相应接口。

Javascript

如果您使用的是Javascript,那么polkadot-js/api已经提供了与Substrate区块链进行交互的API,其中包括getMetadata这个函数。

您可以尝试用以下代码片段到Substrate UI页面上获取元数据:

const { magicNumber, metadata } = await api.rpc.state.getMetadata();

console.log("Magic number: " + magicNumber);
console.log("Metadata: " + metadata.raw);

HTTP 和 WebSocket APIs

Substrate节点暴露了a JSON-RPC API ,可通过 HTTP或 WebSocket 请求对其进行访问。 从节点请求元数据的消息格式如下所示:

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "state_getMetadata",
  "params": []
}

您可以将 参数设为空,或者如若想要获取特定区块的元数据,则须提供该块的哈希值作为参数:

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "state_getMetadata",
  "params": ["0xca15c2f1e1540517697b6b5f2cc6bc0c60876a1a1af604269b7215970798bbed"]
}

在上面的示例当中, 0xca15c2f1e1540517697b6b5f2cc6bc0c60876a1a1af604269b7215970798bbed是1,768,321区块的哈希值。

请求的返回结果格式如下:

{
  "jsonrpc": "2.0",
  "result": "0x6d6574610b7c1853797374656d011853797374656d3c1c4163636f756e7401010230543a3a4163636f756e744964944163...",
  "id": 1
}

result 字段包含了用 SCALE编码的十六进制字符串表示的区块链元数据。 上面的示例代表了从区块1,768,321返回的实际值; 您可使用WebSocket客户端去查询节点做个对比。 继续阅读以了解有关此编码Blob的格式及其解码格式的更多信息。

元数据格式

本节将简要回顾以十六进制字符串表示的SCALE编码元数据,然后再更详细地查看元数据的解码格式。

编码后的元数据格式

JSON-RPCs的state_getMetadata方法返回的十六进制blob,是以硬编码的魔术数字0x6d657461开头,该数字在纯文本中表示“元”的意思。 下一条数据(在上面的示例中为0x0b) 表示元数据版本; 解码十六进制值0x0b会产生十进制值11,这是Substrate元数据格式版本的编码。 在元数据版本之后,结果字段中编码的下一块信息是表示组成区块链runtime的pallet数量。 在上面的示例中,十六进制值 0x7c代表十进制数字31,通过采用其二进制表示形式(11111或十六进制的 0x1F),将其二进制数值向左移动两位(1111100),并对其进行SCALE编码形成十六进制数值。

剩余的Blob对每个pallet的元数据进行了编码,我们将在下面对这部分内容展开详细说明。extrinsic元数据不在本文的范围中。

解码后的元数据格式

以下为解码后的元数据的精简版本:

{
  "magicNumber": 1635018093,
  "metadata": {
    "V12": {
      "modules": [
        {
          // ...
        },
        {
          // ...
        }
      ],
      "extrinsic": {
        "version": 4,
        "signedExtensions": [
          "CheckSpecVersion",
          "CheckTxVersion",
          "CheckGenesis",
          "CheckMortality",
          "CheckNonce",
          "CheckWeight",
          "ChargeTransactionPayment"
        ]
      }
    } 
  }
}

如上所述,整数 1635018093在纯文本中是代表“元”的意思的“魔法数字”。 数据的剩余内容可分为两部分: modules部分和extrinsic部分。 modules 部分包含有关runtime pallet的信息,而 extrinsic部分则描述了runtime正在使用的extrinsics版本。 不同的extrinsic版本可能具有不同的格式,尤其是考虑到 signed extrinsics的时候。

Modules

以下为modules数组中单个元素的精简示例:

{
  "name": "System",
  "storage": {
    // ..
  },
  "calls": [
    // ..
  ],
  "events": [
    // ..
  ],
  "constants": [
    // ..
  ],
  "errors": [
    // ..
  ],
  "index": 0
}

每个元素都包含了它代表的pallet名称,以及一个 storage对象、calls数组、 event 数组和 errors数组。

注意:如果 calls或 event为空,则它们会被表示为 null。 如果constants 或 errors 为空,则将它们会被表示为空数组。

Storage

以下为 modules数组中单个元素的精简示例,突出显示模块的存储相关元数据:

{
  "name": "System",
  "storage": {
    "prefix": "System",
    "items": [
      {
        "name": "Account",
        "modifier": "Default",
        "type": {
          "Map": {
            "hasher": "Blake2_128Concat",
            "key": "AccountId",
            "value": "AccountInfo",
            "linked": false
          }
        },
        "fallback": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        "documentation": [
          " The full account information for a particular account ID."
        ]
      },
      {
        "name": "ExtrinsicCount",
        // ..
      },
      {
        "name": "AllExtrinsicsLen",
        // ..
      }
    ]
  },
  "calls": [/*...*/],
  "events": [/*...*/],
  "constants": [/*...*/],
  "errors": [/*...*/],
  "index": 0
}

Pallet中定义的每个存储项都会有一个对应的元数据条目。 例如,Account存储项是在 frame-system中由下面这段代码生成的:

decl_storage! {
    trait Store for Module<T: Trait> as System {
        /// The full account information for a particlar account ID.
        pub Account get(fn account):
            map hasher(blake2_128_concat) T::AccountId => AccountInfo<T::Index, T::AccountData>;
    }
}

存储元数据为区块链客户端提供了查询Json-Rpc的存储函数,以获取特定存储项内容。

Calls

模块元数据包含了在decl_module!的宏中定义的runtime可调用函数的信息。 对于每个可调用函数,元数据包括:

  • name: 模块中函数的名称。
  • args: 函数定义里的参数, 包括每个参数的名称和类型。
  • Documentation: 函数的文档说明。

举个例子,以下来自于Timestamp pallet:

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        // ... snip

        /// Set the current time.
        ///
        /// This call should be invoked exactly once per block. It will panic at the finalization
        /// phase, if this call hasn't been invoked by that time.
        ///
        /// The timestamp should be greater than the previous one by the amount specified by
        /// `MinimumPeriod`.
        ///
        /// The dispatch origin for this call must be `Inherent`.
        #[weight = (
            T::DbWeight::get().reads_writes(2, 1) + 9_000_000,
            DispatchClass::Mandatory
        )]
        fn set(origin, #[compact] now: T::Moment) {
            // ... snip
        }
    }
}

在元数据中表示如下:

"calls": [
  {
    "name": "set",
    "args": [
      {
        "name": "now",
        "ty": "Compact<T::Moment>"
      }
    ],
    "documentation": [
      " Set the current time.",
      "",
      " This call should be invoked exactly once per block. It will panic at the finalization",
      " phase, if this call hasn't been invoked by that time.",
      "",
      " The timestamp should be greater than the previous one by the amount specified by",
      " `MinimumPeriod`.",
      "",
      " The dispatch origin for this call must be `Inherent`."
    ]
  }
],
事件 (Events)

下面要讲述的元数据代码片段是从frame-system中的以下列代码生成的:

decl_event!(
    /// Event for the System module
    pub enum Event<T> where AccountId = <T as Trait>::AccountId {
        /// An extrinsic completed successfully.
        ExtrinsicSuccess(DispatchInfo),
        /// An extrinsic failed.
        ExtrinsicFailed(DispatchError, DispatchInfo),
        // ... snip
    }
)

Substrate的元数据将会把这些事件描述如下:

"event": [
  {
    "name": "ExtrinsicSuccess",
    "arguments": [
      "DispatchInfo"
    ],
    "documentation": [
      " An extrinsic completed successfully."
    ]
  },
  {
    "name": "ExtrinsicFailed",
    "arguments": [
      "DispatchError",
      "DispatchInfo"
    ],
    "documentation": [
      " An extrinsic failed."
    ]
  },
],
Constants

元数据包含任何的模块常量。 下述代码是从 pallet-babe中获取:

decl_module! {
    /// The BABE Pallet
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        /// The number of **slots** that an epoch takes. We couple sessions to
        /// epochs, i.e. we start a new session once the new epoch begins.
        const EpochDuration: u64 = T::EpochDuration::get();

        // ... snip
    }
}

该常量的元数据如下所示:

"constants": [
  {
    "name": "EpochDuration",
    "type": "u64",
    "value": "0x6009000000000000",
    "documentation": [
      " The number of **slots** that an epoch takes. We couple sessions to",
      " epochs, i.e. we start a new session once the new epoch begins."
    ]
  },
  // ...
]

元数据还包括在runtime的lib.rs文件中定义的常量。 举个例子,以下代码从Kusama获取:

parameter_types! {
    pub const EpochDuration: u64 = EPOCH_DURATION_IN_BLOCKS as u64;
}

其中 EPOCH_DURATION_IN_BLOCKS 是在runtime/src/constants.rs中定义的常数。

Errors

元数据会从 decl_error!宏中获取所有可能的runtime错误。 举个例子,以下代码从frame-system中获取:

decl_error! {
    /// Error for the system module
    pub enum Error for Module<T: Trait> {
        /// The name of specification does not match between the current runtime
        /// and the new runtime.
        InvalidSpecName,
        // ... snip
    }
}

这将会对外暴露以下元数据:

"errors": [
  {
    "name": "InvalidSpecName",
    "documentation": [
      " The name of specification does not match between the current runtime",
      " and the new runtime."
    ]
  },
  // ...
]

这些都是在 extrinsic 提交或执行过程中可能发生的错误。 在这个例子中, Frame System 模块声明了它可能会引发 InvalidSpecName 的错误。

后续步骤

进一步学习

  • Storage
  • SCALE编码
  • 宏
  • 事件 (Events)
  • Extrinsics

参考文档

  • 元数据
← Runtime宏Runtime 存储 →
  • 如何获取元数据
    • Rust
    • Javascript
    • HTTP 和 WebSocket APIs
  • 元数据格式
    • 编码后的元数据格式
  • 解码后的元数据格式
  • 后续步骤
    • 进一步学习
    • Examples
    • 参考文档
Substrate Developer Hub
开发者中心
教程知识库进阶菜谱API 文档
社区
社区主页通讯Substrate 技术聊天室Substrate 研讨会Stack Overflow推特事件 (Events)
更多
Substrate Builders 计划BlogSubstrate GitHub开发者中心 GitHub隐私政策使用条款Cookie 设置
Copyright © 2021 Parity Technologies