Off-chain determinism

How to obtain the current block number

I have heard developers repeatedly complain about why they can’t read the current block number in their CKB scripts(smart contract)?

As every blockchain developer knows, reading the current block number from a smart contract is a fundamental feature of Ethereum and other blockchains that use EVM. So if CKB, like it says, is the next-generation blockchain, why can’t it accomplish things that the previous generation blockchain could?

Well, I guess few people can answer it, the core team hasn’t explained the unique design of CKB to the community frequently, which I think the core team should do better.

So I decided to write this article to explain the trade-off of this design: why can’t we read the current block number in the script.

The right way

Let’s start by examining the right way to read the block number in the CKB script.

By my observation, most developers ask for the block number to measure the block time between two actions, in abstract terms: a user deposits at block height X then withdraws at block height Y. After the withdrawal, the smart contract has to calculate the blocks during X to Y, and send a reward to the user according to the user’s deposit time or do other things similar.

Ethereum can read the block number of the current block directly and calculate the reward. In CKB, however, instead of reading the block number, we must employ alternative approaches:

  1. We can constrain the withdrawal transactions by using the transaction valid since feature to ensure that no withdrawal transaction takes place until the deposit has been completed at least after Z blocks. However, the user can delay the tx for a longer time than Z, which may not be appropriate in some situations.
  2. We can use a two-step withdrawal to locate the block height. Step one, we read the deposition block height X by using CKB syscalls, then record the X in the new prepare-withdrawal cell; step two, we use syscall to read the height of the prepare-withdrawal cell Y and read the X from the cell’s data. Thus, we can calculate the block time Y - X.

The off-chain determinism

So what’s the purpose of doing this? Obviously, developers prefer the simpler and more intuitive method, so why does the CKB use such a difficult method to do a simple job?

It is all about the off-chain determinism. Let’s start by explaining two questions: What is the input of the Ethereum contract? What is the input of the CKB script?

If we consider the contract(or script) as a function f, then we can represent the smart contract’s calculation in the following form:

In Ethereum:

output = f(tx, state tree, current blockchain)

In CKB:

output = f(tx, deterministic blockchain)

In Ethereum, a contract can retrieve information from three inputs: tx, the account state tree, and the current blockchain status. For example, a smart contract can read current block number and block hash from blockchain.

In CKB, we only allow a script to read deterministic inputs. If a user wants to read information from the blockchain, the user must include the block’s hash in the tx in the first place. Thus, all information that a script can read is deterministic. We called it off-chain determinism, which is a fundamental feature of CKB.

The advantages

Off-chain determinism brings some benefits, the most important one is that once we verify a tx, we know the tx will always be valid or invalid since it is deterministic. The verification result doesn’t depend on blockchain status.

The determinism introduces a verification strategy in the CKB, in which we only verify a tx once before pushing it into the memory pool. When we package txs into a block, we only need to check the tx inputs are still unspent; this cost is tiny compared to doing a complete verification for a tx.

This off-chain determinism of tx is not only bound to one node; We can guarantee that a tx is valid even if we send it across the P2P network. Because a tx, if valid, will remain valid permanently, so the CKB only broadcasts valid txs to other nodes. If a malicious node tries to broadcast invalid txs, network peers will immediately ban it once they fail to validate a tx sent by the malicious node. As invalid txs can’t get broadcast into the CKB network, the CKB only packages valid txs into blocks, which I think is a major advantage of CKB.

Note that it is infeasible in Ethereum because Ethereum tx is not off-chain determinism, a tx may fail on block number 42 even if it is valid on block number 41. Thus, we never know whether a failed tx was sent intentionally by a malicious peer or failed because the blockchain tip state changed. Thus, Ethereum chooses another way, where Ethereum allows nodes to broadcast invalid txs and package them into the block. The protocol then enables miners to peanalize failed txs by charging the maximum fee from the sender’s account. This mechanism aims to enhance security by incentivizing users to send only valid txs; even if you are an honest user, you may send a failed tx by accidentally running into an invalid contract state. (I have lost about 50$ in a failed tx when I tried to deposit into Curve.)

Whether to include only valid txs in the block or include failed txs in the block then to penalize the sender, such different design philosophies come from different answers to the simple question: should we allow a script to read the current block number?

Layer1 robustness vs. user experience

From what I understood, between the layer1 robustness and the user experience, the design of CKB clearly chose the former.

And I believe this is a correct choice; the only important thing of the layer1 blockchain is to provide robustness and secured service. And this trade-off doesn’t mean that CKB has no concern for users’ experience. Remember the slogan of CKB: a layer1 built for layer2. And in this case, layer1 is made for robustness, and layer2 is made for user experience.

In layered architectures, most people would use a higher-layer technique, only a few people need to access from the lower-layer. For example, most users use HTTP or WebSocket; less people use TCP or UDP, and almost no one uses IP directly.

From a smart contract developers’ perspective, you might find this design incomprehensible; but if you look at the layered architecture, the design is a natural fit for the layer1 blockchain.

Once the layer2 facility is launched, developers will be able to access the features provided by layer 2 comfortably, not only for reading block numbers (which is simple) but also for more powerful features ( leaving room for the developer’s own imagination).

12 Likes

翻译:链下确定性 ( Off-Chain Determinism )

如何获取当前的区块编号?

我已经反复的听到开发者抱怨说为什么他们没有办法在他们的脚本(智能合约)中读取当前的区块编号这件事情了

如同每位开发者所知,从智能合约读取当前的区块高度是个以太坊以及其他使用 EVM 的区块链上的基本功能。那如果 CKB 真如我们所说,是下一代的区块链,那为什么他没办法做到这件上一代公链就能做到的事情呢?

嗯,我猜应该没啥人可以回答这个问题, CKB 核心团队也比较没有经常解释 CKB 的独特设计,这点我觉得核心团队应该要做的更好。

所以我决定要写这篇文章来解释这个设计的权衡之处:为什么我们不能在智能合约(脚本)中读取当前的区块编号呢。

正确的方式

让我们从验证 CKB 脚本如何读取区块编号的正确方式开始吧!

我的观察是多数的开发者是透过请求区块编号来计算两个动作之间的区块时间,抽象用术语来说,一个用户在区块高度 X 时进行存款,并且在高度 Y 时取出。在取出这个动作发生之后,智能合约就需要去计算区块 X 到 区块 Y 之间的区块数,并且根据用户的存款时间去发送奖励(注:例如流动性挖矿即是如此)或者做其他类似的事情。

以太坊上面可以直接的读取区块编号以及计算奖励。然而,在 CKB 中,我们必须采用其他方法而不是直接读取区块高度:

  1. 我们可以使用transaction valid since的功能来限制取款交易,以确保至少在存款完后的 Z 个区块后,才会发生取款交易。但是,这样以来用户可以把取款的时间延长至区块 Z 之后 ,这在某些情况下可能不合适。

  2. 我们可以采取两步取款来定位区块高度。首先第一步是借由 CKB syscalls 来读取用户存款的区块高度 X ,并且记录 X 这个编号在新的「准备取款 Cell 」中;第二步就是我们可以使用 syscall 来读取准备取款的 cell Y 以及从这个 cell 的 data 字段来读取 X。
    因此我们一样可以计算出区块时间是 Y-X。
    (译注:想想 NervosDao 的设计就是如此)

链下确定性

那么这样做的目的是什么呢? 开发者显然更喜欢更简单、更直观的方法,那么为什么 CKB 要使用如此困难的方法来完成简单的工作呢?!

这都是因为链下确定性。让我们先解释两个问题: 以太坊合约的输入是什么? CKB脚本的输入是什么?

如果我们将合约(或脚本)视为函数“f”,那么我们可以将智能合约的计算表示为以下形式:

在以太坊上 :

output = f(tx, state tree, current blockchain)

在 CKB 上:

output = f(tx, deterministic blockchain)

在以太坊中,一个合约可以从三个输入中检索信息: tx、帐户状态树和当前区块链状态。
例如,智能合约可以从区块链中读取当前区块编号和区块哈希。

在CKB中,我们只允许脚本读取确定性的输入。如果用户想从区块链读取信息,那么用户首先必须在交易中包含区块哈希。因此,脚本可以读取的所有信息都是确定的。我们称之为链下确定性,这是CKB的一个基本特征。

优点

链下确定性带来了一些好处,最重要的一点是,一旦我们验证了一个交易,我们就知道它会是有效的或无效的,因为输入输出都是确定的,这个验证结果不依赖于区块链状态。

这个确定性在 CKB 中引入了一个验证策略,那就是在将交易其推入内存池前我们只验证一次。当我们将交易打包到一个区块中时,我们只需要检查交易输入是否仍然是未使用的,这和对交易进行完整的验证相比成本很小。

交易的这种链下确定性不仅绑定到一个节点; 我们可以保证一个交易是有效的,即使我们通过P2P网络发送它。因为如果一个交易有效,那么它将永远有效,所以 CKB 只广播有效的交易到其他节点。如果恶意节点试图广播无效的交易,那么一旦无法验证恶意节点发送的交易,网络中的其他节点将立即禁止它。由于无效的交易不能广播到 CKB 网络中,所以 CKB 只将有效的交易打包到区块,我认为这是CKB 的一个主要优势。

注意哦,这件事在以太坊中是不可行的,因为以太坊的交易并不是链下确定性的,一个交易可能在区块编号 42 上失败,即使它在区块号 41上是有效的。因此,我们永远不知道失败的交易是恶意节点故意发送的,还是因为区块链当前状态改变而失败的。所以以太坊选择了另一种方式,就是允许节点广播无效的消息并将其打包到块中。然后,该协议允许矿工通过向发送者的账户收取最高费用来对失败的交易进行处罚。该机制旨在鼓励用户只发送有效的短信,以加强安全; 但即使你是一个诚实的用户,您也可能由于意外地运行到一个无效的合约状态而发送一个失败的交易。( 当我试图将钱存入Curve时,我在一次失败的交易中损失了大约50美元。)

在区块中只包含有效的交易,还是在区块中包含失败的交易并惩罚发送者,这两种不同的设计思想来自于对一个简单问题的不同回答:我们应该允许脚本(智能合约)读取当前区块吗?

Layer1 的强健性 vs. 用户体验

根据我的理解,在 Layer1 的强健性(鲁棒性)和用户体验之间,CKB 的设计明显选择了前者。

我相信这是一个正确的选择; Layer1 区块链唯一重要的事情是提供强健性性和安全的服务。这种权衡并不意味着 CKB 不关心用户的体验。记住 CKB 的口号 - 为 Layer2 建造 Layer1。在这种情况下,Layer1 是为了强健性,Layer2 (以上)才是为了用户体验。

在分层架构中,大多数人会使用高层的技术,只有少数人需要从底层直接去访问。例如,大多数用户使用 HTTP 或 WebSocket ; 很少有人使用 TCP 或 UDP,几乎没有人直接使用 IP。

image
译注:图片为译者添加,便于不了解互联网分层架构者参照这个互联网的分层图来了解作者所言
Source:khan Academy

从智能合约开发者的角度来看,您可能会发现这种设计难以理解 ,但是如果你看一下分层的架构,你会发现 Nervos 的设计非常适合 Layer1 区块链。

一旦 Nervos 的 Layer2 设施启动,开发者将能够轻松访问 Layer2 提供的功能,不仅能用 L2 读取区块编号(这很简单),而且还用其他 L2 更强大的功能(这里留给开发人员自己想象的空间)。

3 Likes

Nice work, can you post it to 中文 sub-topic?

Why not! My pleasure !
Learn a lot from your piece.

Interesting!
Hard to meet a L1 core team would be willing to admit that the some applications should be built on their L2 only.
Surly,I have no idea whether it is true or not but I respect your brave demonstration especially around the bull market.
Here comes a question when I realized the tradeoff of layered architecture of Nervos : In long term, what kinds of applications should be deployed on L1? And what kinds of applications would be recommended to be built on upper layer?
Possible to have an incomplete guide for dApp builder to understand which layer would be their best choice on Nervos? Of course,more detail on it will be helpful,like payment applications recommended to develop on GPN or Rollup…etc.

Hi, just my personal opinion, I can’t represent the core team :smile:

what kinds of applications should be deployed on L1? And what kinds of applications would be recommended to be built on upper layer?

I think it depends on what features of layer2 the application utilized; for example, if the application costs large cycles to execute the contract, it is better to build it on layer2, or maybe an application needs to share lots of states between different users, which is hard to achieve in the cell model; thus it is better to build it on rollup-based layer2.

It is not a choice between layer1 or layer2. An application can utilize both of them depends on different scenarios. For example, an application does most operations on layer1. Thus, users do not need to deposit on the layer2, but the application can move some functions to layer2 like the random generation or complexity cryptography verification.

Don’t worry! I am very happy to learn from your personal insight.
And I never regard the individual idea as the team’s attitude, even Vitalik can’t represent all the Ethereum.
But what makes me encouraged is that the dev team is willing to share their thoughts which the western devs usually do.
And

I agree with this point, in recent, I was thinking about the justice of aggregator of Gliaswap and optx .
And the reason why I asked this question is that I believe there must be some treasured time and resource could be reserved if we have known which kinds of dApp is meaningless to be built on L1.
Then the ecosystem devs could save their cost and time and spend it on the right point to make more brilliant products such as Portal Wallet.