---
title: Libra 技术架构
date: 2019-07-13
draft: false
summary: 围绕账户模型和 Move 语言，分析 Libra 在状态组织和智能合约设计上的关键变化。
slug: libra-architecture
syndication:
- platform: Weibo
  url: https://weibo.com/1648815335/4393605768534550
tags:
- libra
- blockchain
topics:
- move
- blockchain
type: post
---

Libra 开源有一段时间了，关于它的未来的讨论有很多，它的联盟如何组织和治理，它如何突破监管体系，它能否吸引用户，都尚未可知，大家也只能猜测。但它的代码本人一直跟踪 Libra 的源码更新，本文主要从技术角度分析它的架构以及创新点。 

### 概述

Libra 的技术架构方面主要有两个创新点：

1. 账号数据的存储模型。

2. 合约的编程语言 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。

我们看一个例子，可能更容易理解：

```move
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 合约的特点：

1. Asset/Resource 是一种类型，而不仅仅是一个数字。
2. Resource 之间可以嵌套表达更复杂的 Resource。

Move 的另外一个特点是它的 Module 加载机制。它的 Module 可以理解成是一组关于 Resource 操作的静态方法的组合，所以是无状态的。代码中要获取状态，有三个办法：

1. 通过外部参数获取。
2. 通过 borrow_global 向全局借用。
3. 通过 move_from 从用户账号下 move。

因为它的无状态特性，所以比较容易做 Module 的依赖。Move 的代码中可以 import 某个 address 下发布的 Module，从而依赖该 Module 的逻辑，方便代码共享和复用。

**相关链接**

---

2. Vitalik 谈论 smart contract 的推<https://twitter.com/VitalikButerin/status/1051160932699770882>
3. Discreet Log Contracts <https://adiabat.github.io/dlc.pdf>
4. A smart contract and arbitrate oracle service on LightningNetwork
   
   <https://github.com/starcoinorg/thor/>

<!-- WEIBO_MEDIA_START -->
## 原微博中的媒体

[Open video on Weibo](https://weibo.com/1648815335/HDh72zOe2)
<!-- WEIBO_MEDIA_END -->
