Post
Libra 技术架构
围绕账户模型和 Move 语言,分析 Libra 在状态组织和智能合约设计上的关键变化。
Libra 开源有一段时间了,关于它的未来的讨论有很多,它的联盟如何组织和治理,它如何突破监管体系,它能否吸引用户,都尚未可知,大家也只能猜测。但它的代码本人一直跟踪 Libra 的源码更新,本文主要从技术角度分析它的架构以及创新点。
概述
Libra 的技术架构方面主要有两个创新点:
-
账号数据的存储模型。
-
合约的编程语言 Move 以及数据模型。
上面两点是息息相关的,要理解它的存储模型,就得理解它的合约编程语言,要理解 Move 也需要理解数据的存储模型。本文主要从以上两个角度分析。
账户模型和合约状态
Libra 虽然和 Ethereum 类似,都是账号模式,但二者的账号模型有很大区别。
为了照顾到不熟悉以太坊的朋友,我们先回顾下以太坊的账户和数据模型。
Ethereum 把账号分为外部账号和合约账号,外部账号就是通过私钥来控制的账号,合约账号是通过合约控制的账号。 而在 Libra 中,只有一种用户账号,即通过私钥来控制的账号,没有了合约账号。这个改变带来了两个变化。
一. 合约以及状态存储的变化
Libra 中的合约以及状态都存储到用户账号之下, 用户的数据并不存在合约中,而是在各自的账号之下,合约本身是无状态的。比如通过合约发行 Token, Ethereum 的 Token 都存在合约中,通过一个 map 映射用户地址和 Token 数额,而在 Libra 中,Token 本身是一种资源对象,会存储到用户自己的账号下。
这样存储一方面可以明确资源的所有权。 Ethereum 的合约账号实际上是一种公共资源,也面临公地悲剧的问题,大家都创建合约,往合约中写数据,但不承担合约状态的存储成本,最后会导致状态爆炸问题。如果要解决这个问题,就必须先将状态拆分开,否则合约状态的费用由谁来支付这个是没法明确的。
另外一个好处是钱包开发比较容易,只需要监听用户自己的目录即可获取到该用户所有的状态,无需去监听全局的合约变化。
二. 可编程能力的变化
在 Ethereum 上,外部账号如果想具有可编程能力,必须先把 Ether 转到合约账号中,然后才能通过合约操作,合约无法直接操作外部账号中的 Ether。而在 Libra 中,任何一个账号都具有了可编程能力。账号可以自己部署合约,或者调用合约来操作用户自己的状态,整体上可编程能力是增强的。当然这个也带来了权限问题,后面的 Move 相关章节会说到这个问题。
Move 合约
关于 Libra 的 Move 文章已经不少了,都提到了它的一个亮点 First class Resource/Assert,就是资产是一等公民。这个怎么理解呢?资产是什么的一等公民?在区块链领域可以理解成链上的一等公民。Bitcoin 中,BTC 是一等公民,因为链对这种资产是有感知的,但定义在 op_return 中的资产就不属于。Ethereum 中的 Ether 也是一等公民,但通过合约定义的 Token 则不是,Ethereum 的 Token 和合约状态混在一起,链只知道合约状态发生了变化,不知道是否产生了 Token 转移,也无法追踪。
而 Move 中,通过引入 Resource 关键词,在编程语言层面区分了 Resource 和 Value,Value 只代表程序中的变量,可以 move,也可以 copy,但 Resource 只能 move 不能 copy,可以通过线性逻辑(liner logic)追踪,保证只能被使用一次,并且一定被使用一次。
如果熟悉 Rust 的读者可能容易理解这一点,Rust 中没有垃圾回收,也不需要显示的释放内存,它通过所有权以及生命周期机制追踪内存的使用,Move 应该是借鉴 Rust 的思想,用来追踪 Resource。
我们看一个例子,可能更容易理解:
module EToken {
import 0x0.LibraAccount;
resource T {
value: u64,
}
resource Order {
token: R#Self.T,
price: u64,
}
public sell_token(token_amount: u64, price: u64) {
let token: R#Self.T;
token = Self.withdraw(move(token_amount);
move_to_sender<Order>(Order { token: move(token), price: move(price)});
return;
}
public buy_token(order_address: address) {
let sender: address;
let order: R#Self.Order;
let token: R#Self.T;
let price: u64;
sender = get_txn_sender();
order = move_from<Order>(copy(order_address));
Order { token:token, price:price } = move(order);
LibraAccount.pay_from_sender(move(order_address), move(price));
Self.deposit(move(sender), move(token);
return;
}
}
这个例子是一个 Token 的例子,这里简化掉了权限相关的逻辑。resource T 表示的就是 Token,Order 表示一个订单,订单中的 token 字段的类型也是 resource T。sell_token 方法中,合约把 token 从交易发送方(txn_sender)账号中提出来,然后 move 到 order 中,最后把 order 存到发送方账号下(move_to_sender)。这时候,发送方账号下的 token 已经减少了,token 存储到 order 中去了。buy_token 方法中,通过调用 LibraAccount 方法,让发送方付给订单方 LibraCoin,然后将 order 中的 token 转给发送方,完成购买。
这个例子虽然简单,但可以体现出 Move 合约的特点:
- Asset/Resource 是一种类型,而不仅仅是一个数字。
- Resource 之间可以嵌套表达更复杂的 Resource。
Move 的另外一个特点是它的 Module 加载机制。它的 Module 可以理解成是一组关于 Resource 操作的静态方法的组合,所以是无状态的。代码中要获取状态,有三个办法:
- 通过外部参数获取。
- 通过 borrow_global 向全局借用。
- 通过 move_from 从用户账号下 move。
因为它的无状态特性,所以比较容易做 Module 的依赖。Move 的代码中可以 import 某个 address 下发布的 Module,从而依赖该 Module 的逻辑,方便代码共享和复用。
相关链接
-
Vitalik 谈论 smart contract 的推https://twitter.com/VitalikButerin/status/1051160932699770882
-
Discreet Log Contracts https://adiabat.github.io/dlc.pdf
-
A smart contract and arbitrate oracle service on LightningNetwork