午夜咖啡午夜咖啡

jolestar 的文章与笔记。

Post

xuperchain 试图用 UTXO 追踪合约状态

2019-05-28 18:06:36Post

xuperchain 把合约状态拆成按 key 追踪的版本变化,用预执行和冲突检测来绕开传统 global state trie 的一部分成本。

百度开源了一个区块链项目 xuperchain。我当时下了源码看了一下,觉得它里面有几个设计挺有意思。

Ethereum 为了支持链上合约执行,选择了 Account 模式,而不是 UTXO。原因也比较直接:合约执行时,需要先加载合约当前状态,执行后再更新状态。用户在构造交易的时候,并不能准确知道交易执行后的最终状态是什么,所以很难像 UTXO 那样提前把输入输出关系表达清楚。于是 Ethereum 用的是 Account + Global State Trie 这一套状态追踪方式。

xuperchain 的思路不太一样。它尝试用 UTXO 的方式来追踪链上合约状态。它的交易里有两类 input

  • 一类追踪资产
  • 一类追踪合约里 key-value 的变化

比如一个 counter 合约第一次调用时,对应的 input.refTxid 可以为空;第二次调用时,counter 这个 key 的 input 就会引用上一次交易。也就是说,它不是把合约状态看成一个整体,而是把状态拆成很多可以单独追踪版本变化的 key。

这套设计自然会带来一个问题:客户端怎么知道这次交易到底会改哪些 key,应该如何构造交易?

xuperchain 的做法是提供一层预执行。客户端先请求节点预执行合约,拿到输入输出,再按这个结果构造正式交易并提交上链。

另一个问题是冲突控制。比如两个交易同时调用 increase counter,如果它们都引用了同一个旧状态,就会发生冲突。所以它不是按“整个合约状态”做冲突追踪,而是按 key 粒度追踪。这样可以显著降低冲突概率,这个思路其实很像数据库里的乐观锁。

这套机制的一个直接好处是:链的状态可以被拆成一组 key-value 的版本变化追踪,从而省掉 Global State Trie 的一部分构建和维护成本,理论上对性能是有帮助的。

它还会反过来影响智能合约虚拟机的设计。虚拟机上下文后面接的是一个多版本 key-value 数据库,合约通过 GetObject / PutObject 这类接口来声明自己读写了哪些 key,以及这些 key 的最新值。这样一来,合约更像是在验证交易,而不是每次都要从头重新执行一遍才能恢复最终状态。如果信任区块中的交易数据,最终状态理论上是可以通过交易直接构造出来的。

它的合约支持 Wasm,当时有 C++Golang 两种语言。Golang Wasm 那时候还不支持直接 export 方法给外部调用,所以它用了一个 driver 模式,在 main 里通过反射分发具体方法。代价也很明显:Golang Wasm 生成的文件还是太大,一个简单的 counter 合约都能编译出 5MB 以上。

所以我对它的印象是:它不是沿着 Ethereum 的默认路径往前走,而是在尝试回答另一个问题,能不能继续保留 UTXO 式的追踪优势,同时把合约状态表达出来。这条路约束更多,但也很值得看。

原微博中的媒体