Linera 协议共享基础层 linera-base:贯穿 Native 与 Wasm 应用的类型与密码学基座
【免费下载链接】linera-protocolMain repository for the Linera protocol项目地址: https://gitcode.com/GitHub_Trending/li/linera-protocol
linera-base 是 Linera 协议(一个以"多链并行执行"为核心理念的分层链协议)中的基础 crate,为协议核心(编译为原生代码运行)与链上应用(编译为 Wasm 运行)提供一套通用的类型定义与库函数。阅读本文后,你将掌握 linera-base 的模块划分、核心标识符与密码学原语的实现原理、双序列化格式设计,以及它是如何通过条件编译与 WIT 桥接同时服务于原生节点和 Wasm 智能合约的。
linera-base 在 Linera 生态中的定位
linera-base/README.md 开宗明义地定义了该模块的职责:
This module provides a common set of types and library functions that are shared between the Linera protocol (compiled from Rust to native code) and Linera applications (compiled from Rust to Wasm).
这句话点明了 linera-base 的核心价值:一份代码、两个运行环境。Linera 协议节点(validator、客户端、存储层)以原生二进制运行,而链上应用(如 examples/ 目录下的 counter、fungible、amm 等示例)则被编译成 Wasm 字节码部署到链上。二者必须对"链 ID、账户、余额、块高、时间戳、签名、哈希"等概念持有完全一致的语义与字节表示,否则跨环境的数据交换(如签名验证、消息投递、GraphQL 查询)就会失败。
因此,linera-base 承担的角色是"协议内共享内核",它被 Linera 生态中几乎所有其他 crate 依赖,包括:
- linera-chain:链数据结构(块、收件箱、出件箱),大量使用
ChainId、BlockHeight、CryptoHash; - linera-core:节点与客户端核心逻辑,依赖
AccountOwner、签名验证等; - linera-sdk:应用开发 SDK,应用端看到的所有基础类型(如
ApplicationId、Amount、ChainId)都来自 linera-base; - linera-execution:执行引擎,使用
Resources、VmRuntime等执行原语。
该 crate 的 Cargo 描述与 README 一致:linera-base/Cargo.toml 中写着 "Base definitions, including cryptography, used by the Linera protocol."
模块全景:从模块列表看 linera-base 的能力边界
linera-base/src/lib.rs 是 crate 的入口,#![deny(missing_docs)]强制所有公开项必须带文档注释,保证了该库的可维护性。通过模块声明可以完整看到它的能力版图:
| 模块 | 职责 |
|---|---|
crypto | 密码学原语:Keccak256 哈希、Ed25519 / secp256k1 / EVM 签名、密钥对、BCS 签名基础设施 |
identifiers | 核心标识符:ChainId、AccountOwner、Account、ApplicationId、ModuleId、BlobId、StreamId |
data_types | 业务数据类型:Amount、BlockHeight、Round、Timestamp、TimeDelta、Resources、Cursor、SendMessageRequest等 |
hashed | Hashed<T>包装类型:缓存"值 + 哈希",避免重复计算 |
abi | 应用 ABI(接口)描述相关类型 |
ownership | 链所有权模型(ChainOwnership) |
http/command/port/panic_hook | 节点运行辅助:HTTP 客户端、CLI 命令解析、端口选择、panic 钩子、信号处理(listen_for_shutdown_signals)等 |
task/task_processor | 任务调度(Wasm 目标下不可用,见条件编译) |
vm | 虚拟机运行时枚举(VmRuntime,区分 Wasm / EVM) |
time/util/limited_writer | 时间抽象、工具函数、带限制的写入器 |
其中prometheus_util仅在启用metricsfeature 时编译。值得注意的是 lib.rs 顶部有一处强制约束:
const _: () = assert!( usize::BITS >= u32::BITS, "linera-base requires a target with usize of at least 32 bits", );这是编译期断言:协议代码中大量存在u32与usize之间的隐式转换(长度、计数等),因此从编译期就排除了 16 位目标平台。
密码学基座:哈希、密钥与签名的三种形态
crypto 是 linera-base 最重要的子模块之一,包含hash.rs、ed25519.rs、secp256k1/、signer.rs等文件。
CryptoHash:Keccak256 内容寻址
crypto/hash.rs 定义了全协议最核心的哈希类型CryptoHash,它包装alloy-primitives::B256(32 字节),底层是Keccak256(以太坊同款哈希,便于与 EVM 生态互通):
pub struct CryptoHash(B256); impl CryptoHash { /// Computes a hash. pub fn new<'de, T: BcsHashable<'de>>(value: &T) -> Self { let mut hasher = Keccak256Ext(Keccak256::new()); value.write(&mut hasher); CryptoHash(hasher.0.finalize()) } }关键的实现细节在于哈希的输入:value.write(&mut hasher)写入的不仅是 BCS 序列化字节,还以类型名(serde_name提取的 seed)作为前缀(见 crypto/mod.rs):
impl<'de, T, Hasher> Hashable<Hasher> for T { fn write(&self, hasher: &mut Hasher) { let name = <Self as HasTypeName>::type_name(); write!(hasher, "{name}::").expect("Hasher should not fail"); bcs::serialize_into(hasher, &self).expect("Message serialization should not fail"); } }也就是说,哈希值 =Keccak256(类型名 + "::" + BCS 字节)。这带来一个重要性质:相同字节内容但不同类型的数据不会碰撞出相同的哈希,从而避免了类型混淆攻击(type confusion)。CryptoHash还提供了make_evm_compatible(),将后 12 字节清零,用于 EVM 兼容场景。
三种密钥体系
crypto/mod.rs 通过枚举统一了三种密钥/签名方案:
- Ed25519:适用于账户所有者(chain owner),对应
AccountSecretKey::Ed25519; - secp256k1:也适用于账户所有者,对应
AccountSecretKey::Secp256k1; - EVM secp256k1:兼容以太坊地址体系,对应
AccountSecretKey::EvmSecp256k1,签名中直接携带 20 字节的 EVM 地址。
同时定义了协议的别名体系:验证者(validator)固定使用 secp256k1(ValidatorPublicKey等类型别名),而链所有者则可以在上述三种方案中选择。AccountSecretKey::sign()与AccountSignature::verify()提供了统一的签名/验签入口,AccountSignature::owner()还能直接从签名恢复出对应的AccountOwner。签名与公钥都提供to_bytes()/from_slice(),统一走 BCS 编码,且具备 Display(十六进制字符串)与 FromStr 往返能力,单元测试roundtrip_account_pk_bytes_repr与roundtrip_signature_bytes_repr(crypto/mod.rs)验证了这几种方案的字节往返一致性。
标识符体系:账户、链、应用与 Blob
identifiers.rs 是 1500 余行的标识符大本营,定义了协议中一切"地址"类概念。
AccountOwner:三种所有者形态
AccountOwner枚举定义了账户所有者的三种形态(identifiers.rs):
pub enum AccountOwner { /// Short addresses reserved for the protocol. Reserved(u8), /// 32-byte account address. Address32(CryptoHash), /// 20-byte account EVM-compatible address. Address20([u8; 20]), }Reserved(0)是AccountOwner::CHAIN,表示"链共享账户"(链上余额的共同所有者);Address32由CryptoHash(如公钥的哈希)派生;Address20直接承载 EVM 地址,是 Linera 与 EVM 世界互操作的桥梁。
AccountOwner有很实用的From<[u8; 32]>转换:若前 12 字节全零,则取后 20 字节作为Address20,否则作为Address32(identifiers.rs)。Account结构则把chain_id与owner组合起来,其Display格式为owner@chain-id,FromStr也支持只写链 ID 表示链共享账户(identifiers.rs)。
ChainId:链的唯一标识
ChainId(pub CryptoHash)是链的唯一标识,文档注释明确说明:它当前由ChainDescription的哈希计算得到。由于链可以动态创建(微链模式是 Linera 的核心设计),ChainId天然是内容寻址的。
ApplicationId 与 ModuleId:应用与模块的标识
ApplicationId<A>包装application_description_hash: CryptoHash(identifiers.rs),即应用描述(ApplicationDescription)的哈希;ModuleId<Abi, Parameters, InstantiationArgument>则包含四个字段:合约字节码 blob 哈希、服务字节码 blob 哈希、虚拟机运行时(VmRuntime),以及可选的Formats描述 blob 哈希(identifiers.rs)——后者用于承载应用的 BCS 编码格式描述,与合约/服务 blob 一起发布;GenericApplicationId区分系统应用(System)与用户应用(User(ApplicationId))。
ApplicationId还能转换为AccountOwner:EVM 应用取描述哈希的前 20 字节作为Address20,非 EVM 应用直接作为Address32(identifiers.rs),这支撑了"应用本身也可以成为账户"的模型。
BlobType 与 BlobId:内容寻址的字节块
Blob 是 Linera 内容寻址存储的基本单元。BlobType枚举(identifiers.rs)区分了九种 blob 类型:
| 变体 | 含义 |
|---|---|
Data | 通用数据 blob |
ContractBytecode/ServiceBytecode | 压缩的合约 / 服务 Wasm 字节码 |
EvmBytecode | 压缩的 EVM 字节码 |
ApplicationDescription | 应用描述 |
Committee | 验证者委员会 |
ChainDescription | 链描述 |
ApplicationFormats | 应用 BCS 格式描述 |
CheckpointExecutionState | 检查点执行状态转储的分块(用于节点引导,免于重放链历史) |
BlobId { blob_type, hash }是 blob 的完整标识,其字符串形式为BlobType:hash(如Data:0x...),同样支持 Display / FromStr 往返;在人类可读的序列化格式中它输出为字符串,在二进制 BCS 格式中输出为结构体(identifiers.rs)。
数据原语:余额、块高、轮次、时间与资源
data_types.rs 定义了链上业务逻辑必须依赖的数值与时间类型,它们都被设计为newtype 包装,在类型层面杜绝"余额当块高用"这类错误。
Amount:定点数余额
Amount(u128)是"非负代币数量",内部以定点小数表示,DECIMAL_PLACES指定小数点后的位数,Amount::ONE是一个完整代币。它的序列化是"双格式"的:人类可读格式(JSON/GraphQL)输出十进制字符串,二进制格式(BCS)输出裸u128(data_types.rs)。Amount支持与U256(alloy 类型)互相转换——U256 -> Amount可能失败(超过 128 位),因此使用TryFrom。
Amount、U128、BlockHeight、TimeDelta通过宏impl_wrapped_number!统一获得一整套checked / saturating 算术:try_add、try_sub、saturating_mul、midpoint、abs_diff等,溢出时返回ArithmeticError(Overflow/Underflow),而不是 panic(data_types.rs)。
BlockHeight、Round 与 Cursor:链上的定位系统
BlockHeight(u64)标识链中块的高度;Round枚举标识共识协议中"第几轮尝试",包含四种形态:Fast(初始快速轮)、MultiLeader(u32)(多领导者轮)、SingleLeader(u32)(单领导者轮)、Validator(u32)(验证者轮流当领导者的轮次)(data_types.rs),反映了 Linera 分层共识的多阶段设计;Cursor { height, index }定位链出站消息流中的逻辑位置:产出消息的块高 + 块内交易索引(data_types.rs)。
Timestamp 与 TimeDelta:微秒精度的时间
Timestamp(u64)以自 Unix 纪元起的微秒数表示,提供now()、delta_since、saturating_add/saturating_sub等方法;其Display输出可读的YYYY-MM-DD HH:MM:SS(UTC)格式,FromStr支持%Y-%m-%dT%H:%M:%S与%Y-%m-%d %H:%M:%S两种解析格式(data_types.rs)。TimeDelta(u64)是微秒计数的时长,提供from_micros/from_millis/from_secs构造。
Resources:应用的执行预算
Resources结构是交易/应用调用执行期间可消耗的资源清单(data_types.rs),字段覆盖两个执行后端:
- 燃料类:
wasm_fuel(Wasm 执行燃料)、evm_fuel(EVM 执行燃料); - I/O 类:
read_operations/write_operations、bytes_to_read/bytes_to_write、blobs_to_read/blobs_to_publish、blob_bytes_to_read/blob_bytes_to_publish; - 通信类:
messages(消息条数)、message_size、service_as_oracle_queries(服务作为预言机的查询次数)、http_requests(HTTP 请求次数)。
SendMessageRequest<Message>则包装一次跨链消息投递请求:目的地链、是否认证、是否被跟踪、转发的资源授权(grant)与消息体(data_types.rs)。代码注释中还留下了 TODO(#1531 相关),说明消息大小统计粒度等仍待精细化。
此外,data_types.rs 还定义了NonCanonicalBTreeMap/CanonicalBTreeSet/CanonicalBTreeMap/NonCanonicalBTreeSet一组针对BCS 规范序编码的优化容器:BCS 对 map 的规范编码会在每次序列化时按序列化后的键字节重新排序(O(n log n)),而 BTreeMap 本身已经有序,因此NonCanonicalBTreeMap在值位置(如RegisterView<Value>、MapView<_, Value>)按普通序列对编码以省去重复排序;而键位置(MapView<Key, _>)必须保持规范序,所以使用CanonicalBTreeSet(以T -> ()的 map 形式触发 BCS 规范排序)。
哈希缓存:Hashed<T>
hashed.rs 定义了Hashed<T>包装类型,将值与预先计算的哈希打包在一起:
pub struct Hashed<T> { value: T, hash: CryptoHash, }Hashed::new(value):计算并缓存哈希;Hashed::with_hash(value, hash):使用预先给定的哈希(调用方必须保证该哈希是 value 的规范哈希),避免重复计算,这在区块/证书的传播与验证场景中能显著减少哈希计算开销;PartialEq只比较hash(),即两个Hashed<T>只要哈希相同就相等。
其单元测试with_hash_stores_provided_hash验证了with_hash会原样存储给定的哈希(hashed.rs)。Hashed同时实现了 async-graphql 的输出类型,可无缝暴露到 GraphQL 查询中。
双序列化设计:JSON/GraphQL 可读、BCS 紧凑
linera-base 中几乎所有核心类型(CryptoHash、BlobId、Amount、U128、StreamId、Account、ChainId等)都实现了"双格式序列化":
- 人类可读格式(JSON、GraphQL、CLI 输出):序列化为十六进制 / 十进制字符串。例如
CryptoHash序列化为 64 位十六进制字符串,反序列化时校验长度(错误为IncorrectHashSize,见 crypto/hash.rs); - 二进制格式(BCS):序列化为紧凑的固定长度字节。
CryptoHash在 BCS 下是裸 32 字节,Amount是裸u128,BlobId是结构体。
实现模式统一为serializer.is_human_readable()分支判断(例如 identifiers.rs 中BlobId的实现)。这种设计让节点之间走紧凑高效的 BCS 线格式,同时让 CLI、GraphQL API 与日志对人友好——linera-base/src/graphql.rs 中的doc_scalar!宏即为这些标量提供 GraphQL Schema 文档。
一库双端:Wasm 与原生环境的条件编译
回到 README 的主题,linera-base 是如何做到"一份代码服务于原生协议与 Wasm 应用"的?答案是系统的条件编译(lib.rs):
#[cfg(not(target_arch = "wasm32"))]:command(CLI 解析)、panic_hook、port(端口选择)以及listen_for_shutdown_signals(Unix 下监听 SIGINT/SIGTERM/SIGHUP,Windows 下监听 Ctrl+C,见 lib.rs)仅在原生环境编译;#[cfg(not(chain))]:task/task_processor任务调度模块在"链内(Wasm 应用)"目标下被排除;#[cfg(with_metrics)]:prometheus_util与init_metrics()(提前注册所有指标,避免冷路径导致面板空白)仅在启用 metrics feature 时编译。
同时,Cargo.toml 中[target.'cfg(target_arch = "wasm32")'.dependencies]与[target.'cfg(not(target_arch = "wasm32"))'.dependencies]分别引入不同的依赖:Wasm 目标使用web-time、tracing-web、ruzstd等轻量替代;原生目标则引入tokio(含signal、fs、process)、prometheus、zstd等。webfeature 还引入了wasm-bindgen、getrandom/js等,使类型可以通过tsify直接导出为 TypeScript 类型(如export type ApplicationId = string;,见 identifiers.rs),支撑 web/ 目录下的浏览器端开发。
跨 Wasm 边界的类型桥接:WIT 接口
让同一组类型在原生协议与 Wasm 应用间传递的另一个关键机制是WIT(WebAssembly Interface Types)桥接:linera-base 中的核心类型(CryptoHash、ChainId、AccountOwner、Amount、BlockHeight、Timestamp、ApplicationId等)都实现了 linera-witty 提供的WitLoad/WitStore/WitTypetrait。
以CryptoHash为例(crypto/hash.rs):它在 WIT 世界中表示为record crypto-hash { part1: u64, part2: u64, part3: u64, part4: u64 },即把 32 字节哈希拆成四个u64作为线性内存布局,并提供load/lift_from/store/lower四个方向的转换。这样,应用 Wasm 与节点原生代码之间传递哈希、账户、余额等数据时,可以复用同一套类型定义,且编译期即可验证内存布局的一致。
在应用中的落地:从示例看 linera-base 的日常使用
linera-base 的类型在 examples/ 中的应用中随处可见。以 examples/counter/(经典计数器应用)为例,其应用代码引入的Application、ApplicationCall、Service等 SDK 抽象底层,以及CounterAbi等 ABI 类型,最终都依赖 linera-base 提供的Amount、ChainId、ApplicationId、ModuleId等基础类型。再如 examples/fungible/(同质化代币)直接在链上逻辑中使用AccountOwner与Amount管理账户余额,examples/fungible/src/lib.rs 中的转账逻辑正是基于 linera-base 定义的账户模型。
读者若想深入 linera-base 的细节,可以:
- 阅读 linera-base/README.md(模块定位概述);
- 浏览 linera-base/src/lib.rs(模块组织与条件编译全貌);
- 研读 linera-base/src/crypto/mod.rs、linera-base/src/crypto/hash.rs(密码学实现);
- 查阅 linera-base/src/identifiers.rs 与 linera-base/src/data_types.rs(标识符与数据原语);
- 在 linera-base/tests/command_tests.rs 与 linera-base/src/unit_tests.rs 中查看测试示例。
如需参与贡献,请参照 CONTRIBUTING 指南;该 crate 以 Apache 2.0 license 发布。
【免费下载链接】linera-protocolMain repository for the Linera protocol项目地址: https://gitcode.com/GitHub_Trending/li/linera-protocol
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考