Open Tx Protocol Brainstorm: (5) From the Account Operation Perspective

Previous otx brainstorm series:
1) otx in general
2) design consideration
3) scenarios
4) implementation proposal

It is believed that the biggest obstacle to understanding the development on Nervos CKB is the UTXO mode. You have to collect live cells, avoid the duplicated input reference, and generate the accurate output cells. By comparison, transaction on account based blockchain like Ethereum is pretty straight forward, users sign the function invoking parameters, then the miners run the VM to calculate the result. This post will introduce a general method for account level operation on Nervos CKB accomplished by otx.

Typical user scenario

Account model is good at handling shared state update jobs. Imagine a state cell stores some shared information, like the poll statistic data, it would be updated by numerous users simultaneously. A previous solution to this problem is to introduce a two-phase commit process. Users firstly post their intentions on CKB by writing intention data in some specific formed cells, then the aggregator combines all the intention cells with the shared state cell in one later transaction.

It’s natural to eliminate the first phase by using open transaction. Users simply sign open transactions and the aggregator collects them to generate a full transaction with shared cell state transist. The user could get instant response from the aggregator, reduce phase 1 block commitment latency, and ease the throughput burden of layer 1.

Transaction structure consideration

Before exploring the details, we should refer to the existing account based transaction. The typical TX structure on Ethereum is illustrated here:

# Ethereum transaction data structure explaination 
nonce: the anti replay attack variable
gas: the tx fee definition part
to: target receipt / target smart contract
value: native token to utilize
parameters: function invoking variables
signature: authorization proof

The nonce field is also necessary in our design. Although the UTXO model doesn’t need it, we still have to adopt a nonce equivalent variable because of the simulated account model we adopted. The only reason why Ethereum uses nonce field is to prevent replay attack, the side effect is that Ethereum transactions are force processed serialized for single account. We here achieve the same functionality by including unique input cells in transaction, without sacrificing the transaction concurrency.

We use a fee variable to substitute the gas price and gas limit field. It defines the maximal capacity difference for specific account.

The to, value, and inputs fields are function invoking variables refer to the target smart contract function, token to transfer, and call parameters seperately.

The signature part also implies the sender information, which identifies the account ownership. We use lock script or lock hash to identify an account on CKB.

An important difference between Nervos and Ethereum is the native token ckb on Nervos is not “fungible”, because every cell has an optional type script to constrain or defines its functionality and purpose. So we need also to define which cells an otx should involve, which means an explicit field for the input cells with specific type script.

Combined with previous design

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

We add the optional account call simulation data ahead of the witness data, making it look like:

// 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, the interaction script definition field should cover the script hash and script position.

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

Discussion on nonce cells

The nonce cell is introduced to resolve the replay attack problem. But the existance of nonce cell also bring the potential usability and parallelization problems. Let’s investigate and try to figure out a solution.

What if the user does not have an extra cell for nonce?
It is okay if the user has a few cells or even one cell for transaction, the aggregator could provide one cell belongs to itself for nonce purpose. And the nonce cell provided by the aggregator will be unlocked by itself, with an output cell pointed to the aggregator.

What if different otx aggregators try to take the same inputs?
Let’s say a DEX scenario, the user has four live cells, which consist of 200 ckb capacity and 250 DAI sudt in them seperately. The user wants to post two orders, the first order is to buy 10000 CKB with 500 DAI, the second one is to buy 501 USDT with 500 DAI. If the user sent two otx with two different cells as the nonce cell, the aggregators have 2/3 chance to select conflicting cells. So the user could include more cells in the nonce cell field to indicate that which cells must be used in the otx.

	nonce: [cell1, cell2]
	nonce: [cell3, cell4]

p2p network and aggregator

The p2p network should verify the validation of the availability of nonce input, the ckb amount on the sender and maybe a small PoW challenge, before adding an otx to the pool or broadcast it out to prevent DDoS attack. The aggregator should monitor the tx pool and p2p network to capture more otxs to accrue more profits. A valid_until tag may be useful for some scenarios, when the aggregator want to clean up the obsolete transactions.

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

Since the aggregators’ response to this optional data is off-chain and not verifiable, it’s not guaranteed that they will do the PoW check and time invalid check.


Let’s say an AMM DEX which accepts otx orders. The otx order should be like this.

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

The aggregator collects the otx orders and related live cells, then combines them with the AMM pool cell to generate the output cells. The Nervos miners could verify the full transaction and commit it on chain.