Open Tx 协议头脑风暴[5]: 从账户操作视角来看

Cipher Wang

Cipher Wang

Nervos Core Team

前期系列(英 & 中):

1、otx in general & OTX 概述 2、design consideration & 设计考量 3、scenarios & 场景分析 4、implementation proposal & 实现提案

或许上手 Nervos CKB 开发最大的难点在于 UTXO 模式。你必须收集可用 cells(live cells),避免输入的重复引用,并且准确生成输出 cells。相比之下,以太坊这类基于账户的链上交易就非常直接,用户签名触发函数调用,然后矿工通过运行 VM 来计算结果。

本文将介绍一种通过 OTX 实现的,在 Nervos CKB 上进行账户级操作的通用方法。

典型用户场景

账户模型有利于处理共享状态的更新。例如,有一个状态 cell 存储了一些共享信息,如投票统计数据,这个状态 cell 会被许多用户同时更新。以前解决这个问题的方案是引入两阶段的提交流程。用户首先将他们的数据更新(intention)写入一些特定格式的 cells 中后发送到 CKB 上,然后聚合器会在随后的一次交易中将所有的 intention cells 跟共享状态 cell 合并。

imageimage2442×686 36.8 KB

使用 Open Tx 可以省去第一阶段,用户对 Open Tx 签名后,聚合器会收集所有 OTX 交易并结合共享状态 cell 生成一笔完整的交易。这样做,不仅用户能够获得聚合器的即时响应,同时也能够降低第一阶段区块提交的延迟以及减轻 layer 1 的吞吐负担。

imageimage1390×630 19.9 KB

交易结构考量

在深入探讨之前,我们先了解一下基于账户模型的以太坊上典型的交易结构:

imageimage1472×114 15.7 KB

# 以太坊交易数据结构解释
nonce: 用于抗重放攻击的变量
gas: 定义交易手续费
to: 接收账户(普通/合约账户)
value: 原生代币的数量
parameters: 函数调用参数
signature: 授权证明

我们的 OTX 设计中也需要 nonce 字段。虽然 UTXO 模型不需要这个字段,但是我们的模拟账户模型仍需采用一个类似 nonce 的变量。以太坊使用 nonce 字段的原因是为了防止重放攻击,带来的副作用是每个账户的以太坊交易会被强制序列化。在 CKB 上,交易的输入 cells 都是唯一的,所以不存在重放问题,也不会牺牲交易的并发性。

我们使用 fee 字段取代 gas price 以及 gas limit 字段。fee 值等于所有输入 cell 的容量总和与所有输出 cell 的容量总和之间的差值。

tovalueinputs 分别对应接收账户,转账数量以及调用参数。

signature 象征着账户的所有权。我们使用锁脚本(lock script)或者锁哈希(lock hash)来识别 CKB 上的账户。

以太坊和 Nervos 之间的一个重大区别是,Nervos 的原生代币 ckb 是“非同质化的”,因为每个 cell 都有一个可选的类型脚本(type script)来约束或者定义它的功能用途。所以我们也需要定义 OTX 交易需要包含哪些 cells,即输入 cell 需要一个字段来指定具体的类型脚本(type script)。

Combined with previous design

In the series article #4 1, we proposed a general otx implementation protocol with the following witness data structure.

系列 4 中,我们提议了一个通用的 OTX 实现协议,其见证(witness)数据结构如下:

imageimage858×116 3.83 KB

我们在见证数据前面添加可选的账户调用模拟数据,如下:

imageimage1486×140 7.51 KB

// account call data
// with hidden sender: lock script / lock script hash
account_call: {
nonce: outpoint array, // explicit input cells for anti replay attack
fee: uint32, // decimal 4, otx fee in ckb, optional zero
typescript: hash, // byte32, means it allows specific input cells with this typescript
to: struct, // specific script hash in transction, see below
value: uint64, // decimal 8, maximal ckb transfered from sender
inputs: list, // call variables
}

to 是用来定义交互脚本的字段,需要包含脚本哈希(script hash)和脚本定位(script position)

to:
# 0x01 - input type script
# 0x02 - input lock script
# 0x04 - output type script
position: uint8,
script: bytes20_script_hash

nonce cells

引入 nonce cells 是为了解决重放攻击问题,但是也会带来潜在的可用性和并行问题,那该怎么解决呢?

如果用户没有多余的 cell 来作为 nonce cell,怎么办? 如果用户只有少量 cells 甚至只有一个 cell 可用于交易,也没事。聚合器可以提供一个 cell 用于 nonce 用途,由聚合器提供的 nonce cell 也将由聚合器解锁,即交易输出中会有一个 cell 指向聚合器。

如果不同的 OTX 聚合器试图使用相同的输入,怎么办?

我们以 DEX 场景为例:用户有 4 个可用 cells(live cells),每个 cell 中分别都有 200 ckb 容量和 250 DAI。用户想要提交两笔订单,第一笔订单是用 500 DAI 购买 10000 CKB;第二笔订单是用 500 DAI 购买 501 USDT。如果用户使用不同的 cell 作为 nonce cell 来发送两笔 OTX 交易,那么聚合器有 2/3 的机会会选择到冲突的 cells。所以用户应该在 nonce cell 字段中包含多点 cells,以表示具体的 OTX 交易必须使用哪些 cells。

otx1:
nonce: [cell1, cell2]
...
otx2:
nonce: [cell3, cell4]
...

p2p 网络和聚合器

p2p 网络在把一笔 OTX 交易添加到交易内存池或者向外广播以防止 DDoS 攻击前,应该验证 nonce 输入的可用性、发送方持有的 ckb 数量,以及可能还有一个小的 PoW 挑战。聚合器应该监控交易内存池和 p2p 网络,以捕获更多的 OTX 交易,赚取更多收益。当聚合器想要清理过时交易时,valid_until 标签可能在某些场景下很有用。

# optional fields for propagation
optional:
pow: nonce hash
valid_until: time limit

因为聚合器对这些可选数据的响应是链下的,无法验证,所以不能保证他们会做 PoW 检查和时间无效检查。

例子

我们举个例子:一个能接受 OTX 订单的 AMM DEX。OTX 订单结构如下:

# sell DAI for USDC
input:
nonce cells
witness:
nonce: outpoint array of the cells to cost
fee: 0.01 ckb
typescript: DAI sUDT typehash
to:
position: 0x01
script: AMM pool's typescript hash
value: 0
inputs:
price: 1.01
pay: 1000
slippage: 0.005
fee: 0.003
signhash_coverage_arrage: null
signature: signature

聚合器收集 OTX 订单以及相关的可用 cells(live cells),然后跟 AMM 池的 cell 合并生成输出 cells。Nervos 矿工能够验证完整交易并且提交上链。

image