# 区块链原生

> 区块链原生应用与你今天构建的应用由同样的组件组成，只是每一层换掉一处。这里列出每一处替换，并用两种方式各实现一遍同一个账本。

区块链原生应用，是结算、托管与规则从构造上就在链上的经济活动，而不是一门在旁边挂上一个代币的传统生意。这听起来像是另一种工程，其实差别没有听上去那么大。

你今天所用技术栈的每个组件都有对应物。对应物做同样的工作，只改变一点：过去靠信任的，现在靠证明。远地虚拟机的存在，就是为了让这一改变便宜到足以成为默认选择。

## 一句话说清这一转变

在传统应用中，服务器就是权威：它持有数据、执行规则、报告结果。在区块链原生应用中，链持有对数据的承诺，规则是一个任何人都能用其摘要指明的程序，而结果只有附带一份“确由该程序产生”的证明才会被接受。

运营方并不会消失。仍然有人运行程序、存储数据、响应请求。消失的，是必须相信他们这件事。

## 逐层对照

| 层 | 传统应用 | 区块链原生，基于远地虚拟机 | 延续下来的 |
| --- | --- | --- | --- |
| 业务逻辑 | 部署在你自己运维的服务器上的服务 | 客户程序（guest）：编译为 RISC-V 的 `no_std` Rust，每次运行都被证明 | 你写的仍然是作用于数据的函数。程序由它的程序身份来指明，即其代码与配置的摘要。 |
| 数据存储 | SQL 表、键值存储 | 数据留在链下；链上存储一个状态根，即概括全部数据某一快照的单个哈希 | 一个能用 32 字节指明、可以拿来核对任何数据的快照。 |
| 读查询 | `SELECT balance FROM accounts WHERE id = ?` | 包含证明，即一条 Merkle 路径，由客户程序对照根进行检查 | 查询仍然返回一行。只是这一行现在附带证据，任何核对不通过的行都会被客户程序拒绝。 |
| 写入 | `UPDATE …; COMMIT;` | 一次状态转换：客户程序计算出新的根并将其发布 | 提交仍然意味着“使其持久化”，只是现在指的是合约把存储的根向前推进。 |
| 请求 | HTTP 请求体 | 公开输入，由证明绑定 | 输入进，输出出。 |
| 大块数据 | 上传的文件、连接查询得到的行、拉取的文档 | 证明者提示（advice）：由证明者提供的字节，客户程序将其与证明所绑定的某样东西核对 | 大数据按引用传递，并核对送达的内容。 |
| 响应 | JSON 响应体 | 公开输出（journal），由证明绑定 | 任何人都能读取响应，并确知它出自该程序。 |
| 身份认证 | 会话、令牌、密码 | 在客户程序内验证签名；secp256k1 公钥恢复运行在委托的域运算与曲线运算之上 | 身份就是一把密钥，授权就是一段你能在源码里读到的检查。 |
| 密码学库 | `sha2`、`ring`、OpenSSL | `guest_sdk::keccak256`、`sha256`、`ec_add`，各自路由到专用电路 | 同样的调用，周期数只是原来的零头。 |
| 发布 | 推送二进制，行为立即改变 | 在验证者合约中登记新的程序身份 | 发布变得显式：新的构建就是新的程序身份，必须由合约接受。 |
| 扩展 | 更多服务器、数据库分片 | 一次执行被切成分片并行证明，再经递归折叠为一份证明 | 吞吐量来自并肩工作的证明者，而链上检验的仍然只是一份证明。 |
| 审计 | 要求别人相信的日志与鉴证 | 证明及其公开输出 | 保障从依赖声誉转向依赖验证。 |

中间一列就是区块链原生应用的构成。其下的机制由远地虚拟机提供：RISC-V 机器、电路、承诺、递归和验证者合约。这些都不会出现在你的程序里。

## 不变的部分

- **你写的仍然是普通的 Rust。** 结构体、枚举、trait、迭代器、`Vec`、`BTreeMap`，以及任何不依赖 `std` 就能构建的 crate。没有电路语言要学。
- **你仍然在自己的笔记本电脑上测试。** 常见的布局是把应用逻辑放进一个 `no_std` 库：它在宿主机（host）上通过 `cargo test` 运行，行为与在客户程序中运行时完全一致。参见[编写客户程序](https://apogee.gweb3networks.com/docs/launch/write#host-first)。
- **你思考的仍然是状态、请求与响应。** 形态不变，变的只是它们的保证。
- **确定性的代码依然是确定性的。** 好的后端代码本来就避免隐藏的输入，虚拟机把这一点变成绝对的要求。

## 改变的部分

- **没有外部世界。** 客户程序没有时钟、没有随机数、没有网络，也没有文件。它知道的一切都以公开输入或证明者提示的形式送达，而向宿主程序请求数据，是一种任何证明都不会接纳的调用。
- **每条指令都有代价。** 每条执行过的指令都会成为一行被证明的数据。复制、内存分配和空转循环都要消耗证明时间，于是精打细算周期数的老规矩又回来了。
- **外部提供的数据要检查，而不是信任。** 证明者提示由证明者选择。在发布任何由它推导出的内容之前，客户程序要先把它与证明所绑定的某样东西进行核对。
- **输出小而公开。** 公开输出最多容纳 16,380 字节。较大的结果以摘要形式发布。
- **没有任何东西是隐藏的。** 远地虚拟机 v1.0.0 的证明是简洁的，但不是零知识的。客户程序不得持有秘密。

## 一个账本，两种做法

向一个账户余额存入一笔款项：值得证明的最小状态变化。

### 传统版本

```sql
BEGIN;
SELECT balance FROM accounts WHERE id = $1 FOR UPDATE;    -- read
UPDATE accounts SET balance = balance + $2 WHERE id = $1;  -- write
COMMIT;                                                     -- make it durable
```

用户信任运营方：相信它对真实的表执行的正是这段代码，并且如实报告了结果。

### 区块链原生版本

账户存放在一棵二叉 Merkle 树中，叶子为 `keccak256(account ‖ balance)`。合约存储树根。客户程序以公开输入的形式接收旧根和请求，以证明者提示的形式接收账户余额及其 Merkle 路径，检查路径，然后发布旧根和新根。

```rust title="guests/ledger/src/main.rs"
#![no_std]
#![no_main]

guest_sdk::entry!(main);

const DEPTH: usize = 20; // room for 2^20 accounts

/// A leaf commits to one account's balance.
fn leaf(account: &[u8; 20], balance: u64) -> [u8; 32] {
    let mut bytes = [0u8; 28];
    bytes[..20].copy_from_slice(account);
    bytes[20..].copy_from_slice(&balance.to_le_bytes());
    guest_sdk::keccak256(&bytes)
}

/// Fold a leaf up its Merkle path; bit `level` of `index` says whether the
/// node is a right child at that level.
fn root_of(mut node: [u8; 32], index: u32, path: &[[u8; 32]; DEPTH]) -> [u8; 32] {
    let mut pair = [0u8; 64];
    for (level, sibling) in path.iter().enumerate() {
        let (left, right) = if (index >> level) & 1 == 0 { (&node, sibling) } else { (sibling, &node) };
        pair[..32].copy_from_slice(left);
        pair[32..].copy_from_slice(right);
        node = guest_sdk::keccak256(&pair);
    }
    node
}

/// Public input: old_root (32) ‖ account (20) ‖ amount (8, LE)
/// Advice:       balance (8, LE) ‖ index (4, LE) ‖ path (DEPTH × 32)
/// Journal:      old_root ‖ new_root ‖ account ‖ amount
fn main() {
    let input = guest_sdk::public_input();
    let advice = guest_sdk::advice();
    if input.len() != 60 || advice.len() != 12 + 32 * DEPTH {
        guest_sdk::exit(1);
    }
    let old_root: [u8; 32] = input[..32].try_into().unwrap();
    let account: [u8; 20] = input[32..52].try_into().unwrap();
    let amount = u64::from_le_bytes(input[52..60].try_into().unwrap());

    let balance = u64::from_le_bytes(advice[..8].try_into().unwrap());
    let index = u32::from_le_bytes(advice[8..12].try_into().unwrap());
    let mut path = [[0u8; 32]; DEPTH];
    for (i, sibling) in path.iter_mut().enumerate() {
        sibling.copy_from_slice(&advice[12 + 32 * i..12 + 32 * (i + 1)]);
    }

    // The query: the balance the prover supplied is the one the root commits to.
    if root_of(leaf(&account, balance), index, &path) != old_root {
        guest_sdk::exit(2);
    }
    // The write: the same path with the new leaf gives the new root.
    let Some(new_balance) = balance.checked_add(amount) else { guest_sdk::exit(3) };
    let new_root = root_of(leaf(&account, new_balance), index, &path);

    // The commit: publish the transition for the contract to apply.
    guest_sdk::commit(&old_root);
    guest_sdk::commit(&new_root);
    guest_sdk::commit(&account);
    guest_sdk::commit(&amount.to_le_bytes());
}
```

对照 SQL 来读：`SELECT … FOR UPDATE` 变成了对照根检查的 Merkle 路径；`UPDATE` 变成了同一路径上的新叶子；`COMMIT` 变成了四次 `commit` 调用，它们写入的公开输出将由证明绑定。余额来自证明者，这没有问题：根并未承诺的余额无法通过检查，运行以 2 退出。

持有根的合约只有在收到证明、表明这次转换由该程序产生并以 0 退出时，才会接受它：

```solidity title="Ledger.sol（示意）"
interface IApogeeVerifier {
    function verify(bytes calldata input, bytes calldata output, uint256 exitStatus,
                    uint256[10] calldata proof, uint256[] calldata points) external view returns (bool);
}

contract Ledger {
    IApogeeVerifier public immutable verifier;
    bytes32 public root;

    constructor(IApogeeVerifier v, bytes32 genesis) { verifier = v; root = genesis; }

    function apply(bytes calldata input, bytes calldata journal,
                   uint256[10] calldata proof, uint256[] calldata points) external {
        require(verifier.verify(input, journal, 0, proof, points), "proof");
        require(bytes32(journal[0:32]) == root, "stale root");
        root = bytes32(journal[32:64]);
    }
}
```

> [!NOTE]
> 这只是展示大致形态的草图，不是生产合约。真实部署中，每份证明处理一批请求，由客户程序折叠为一次转换，并且会把验证者固定到正确的程序和公开值长度上。[链上结算](https://apogee.gweb3networks.com/docs/launch/on-chain)介绍已部署的验证者、它的密钥及其仪式。

## 三行概括这个模型

1. 链持有一个根。
2. 客户程序证明这次转换。
3. 合约推进这个根。

其余的一切，从分片、电路到递归与判定器，都由远地虚拟机负责。这就是这层抽象：一个程序、它的输入与输出，以及把它们联系在一起的一份证明。

## 下一步

- [快速上手](https://apogee.gweb3networks.com/docs/launch/quickstart): 从一个空 crate 到一份通过验证的证明。

- [输入、证明者提示与公开输出](https://apogee.gweb3networks.com/docs/launch/io): 每个客户程序都要用到的三个内存区域。

- [客户程序编程指南](https://apogee.gweb3networks.com/docs/launch/guide): 让客户程序保持正确、可证明且低成本的习惯。
