这是「关于唯链雷神区块链,你可能还不知道那些事儿」系列的第二篇,第一篇参见《区块链如何对抗重放攻击?硬核详解唯链 TXID 方案》。本篇唯链首席科学家 Peter Zhou 阐述并演示了唯链雷神区块链特有的「强制交易依赖性」功能,指的是如果一笔交易的执行依赖另一笔交易,只有在被依赖的交易出现在账本上且没有被撤销的情况下,这笔交易才会被接受并处理。

原文标题:《关于唯链雷神区块链,你可能还不知道那些事儿(第二部分)- 强制交易依赖性》
作者:Peter Zhou,唯链首席科学家,英国南安普顿大学计算机博士,于 2017 年加入唯链,主要负责唯链雷神区块链的科研研发及知识产权保护等工作

区块链如何保证交易安全?硬核详解唯链「强制交易依赖性」功能Peter Zhou,唯链首席科学家,英国南安普顿大学计算机博士,先后以研究员和资深研究员的身份任职于英国肯特大学和芬兰奥卢大学,参与了欧盟和芬兰科学院重要科研项目,拥有十年计算机科研研发以及在国际一流学术杂志和会议上发表科研成果之经验,于 2017 年加入唯链,主要负责唯链雷神区块链的科研研发及知识产权保护等工作

唯链雷神区块链提供了一个安全的机制,使用户可以强制让一笔交易的发生建立在另一笔交易成功的基础之上。换言之,一旦启用了这个功能,系统将会检查当前交易所依赖的上一笔交易的状态。只有依赖的上一笔交易状态显示为成功,当前的交易才会被系统接受并处理。

交易成功的条件有两个:

  1. 交易被记录在了账本里;
  2. 交易被已经成功执行,或者用区块链的语言来说交易没有被撤销。

第二个要求尤其重要,因为看到一笔交易被记录在账本里并不能保证它已经被成功执行了。一笔交易可以在被记录的同时状态显示为「被撤销」,这意味着系统其实没有执行交易的内容。在实际操作中,一笔交易可能因为交易发起者提供的 gas 不足而被撤销。此外,交易还可能被调用的智能合约函数所撤销。比方说,如果交易发起者的通证余额不足,那么这笔交易就会被执行通证转账的合约函数所撤销。

在雷神区块链上,你只要提供所依赖的交易的 TXID,就可以让你的交易依赖于该笔交易。我们没有对于被依赖的交易设置任何限制,比如「谁是发起者」、「交易什么时候执行」或者「交易的内容是什么」。这为开发者提供了很多开发灵活性。值得注意的是,正是因为唯链雷神区块链的 TXID 设计,我们才能实现以上功能。你可以通过我的上一篇文章来了解更多关于 TXID 的内容。

DependsOn

接下来让我介绍一下这个机制是怎样在唯链雷神区块链上实现的。
这个机制是由交易模型中的 DependsOn 字段(请查看在 $THORDIR/tx/transaction.go 中定义的 body 结构部分)和一些额外的验证逻辑实现的,这些验证逻辑被实现在以下用于验证一个区块中的交易的代码中(具体请查看 $THORDIR/consensus/validator.go 中的 verifyBlock 函数)。

// check depended tx
if dep := tx.DependsOn(); dep != nil {
    found, reverted, err := findTx(*dep)
    if err != nil {
        return nil, nil, err
    }
    if !found {
        return nil, nil, consensusError("tx dep broken")
    }
    if reverted {
        return nil, nil, consensusError("tx dep reverted")
    }
}

现在我们来仔细看一下以上这段代码。根据定义,DependsOn 字段是一个指向它所依赖的交易的 TXID 的指针。这段代码首先做的是通过 dep := tx.DependsOn() 获取当前交易的 DependsOn 字段值。如果该字段值不为空,我们执行 found, reverted, err := findTx(*dep) 来检查所依赖的交易的状态。剩下的代码先检查搜以来的交易是否存在,然后再检查它是否被撤销。一旦所依赖的交易不符合这两个条件中的任何一个,当前交易就不会被系统执行。

代码演示

我做了一个简单的演示,来让大家能够更好理解这个强制交易依赖性机制。这段演示代码是用 Typescript 写的,你可以通过这个 链接 找到并下载代码。我在这段代码中用到了两个最新开发的工具:connex-frameworkconnex.driver-nodejs。这两个工具能够让开发者在 NodeJS 环境中方便地通过 Connex 接口与唯链雷神区块链进行交互。
我在代码中硬编码了两个账户地址,分别叫做 acc1acc2 以及 acc1 的私钥,并且用了 测试网水龙头 来获取足够的数字资产来运行这段演示代码。

const sk = '0x29a9...1a7';
const acc1 = '0x858...2c4';
const acc2 = '0x914...85d';

这个演示做了什么?

这个演示程序做了下列几件事:

  1. 发起交易 TX1,从 acc1 转 1 VET 到 acc2。提供足够的 gas 来确保这笔交易能顺利执行;
  2. 发起交易 TX2,从 acc1 转 1 VTHO 到 acc2。不提供足够的 gas 使得这笔交易被撤销;
  3. 发起交易 TX3,从 acc1 转 1 VET 到 acc2,让这笔交易依赖于一笔不存在的交易;
  4. 发起交易 TX4,从 acc1 转 1 VET 到 acc2,让这笔交易依赖于 TX1
  5. 发起交易 TX5,从 acc1 转 1 VET 到 acc2,让这笔交易依赖于 TX2
  6. 在发送完所有交易随后的连续五个新区块里,查看以上五笔交易的状态。你会看到 TX3TX5 无法被找到,因为它们依赖的交易没有满足条件。

请注意,出于方便的考虑,我在演示中创建的交易都依赖于同一个地址发起的交易。在实际操作中,你可以让你的交易依赖任何交易,你只需要提供所依赖交易的 TXID 即可。

运行结果

现在让我们来看一下这段演示代码输出的结果。

Sent TX1 with ID = 0x6f7874438429ce31d89b68ceb1dacf3fba012149b57f9781f6d9b5c707eedde5
...
Sent TX2 with ID = 0x9f8f9bec9593f74d9353ac7f1087afa32646638155e6a448dc35d60788d958b5
...
Sent TX3 with ID = 0x319b70009a8234107a2d08c3dbf9e93396330892badb03b5c4cebac391d68c2d
TX3 depends on an nonexisting TXID
...
Sent TX4 with ID = TX1 found! If reverted: false
0x1c30f07aae653711ab3284062afe557f1674c56d625114326230b041d79f9931
TX4 depends on TX1
...
Sent TX5 with ID = 0xd92e5ffcf3e9ff11f33c59b814869c23df75c7cc49773fcb95ccc8b9bd420a43
TX5 depends on TX2
...

最先输出的几行告诉了我们一些在演示代码中创建并发送到测试网上的五笔交易的相关信息。我们可以看到它们的 TXID 以及依赖的交易。请注意这个演示故意把 TX3DependsOn 字段值设为 0x1,这意味着 TX3 依赖于一个 TXID 为 0x1 的交易。目前这样的一笔交易还不存在。

--------------------------
Block Number = 3170840
--------------------------
...
Checking TX1 with ID = 0x6f7874438429ce31d89b68ceb1dacf3fba012149b57f9781f6d9b5c707eedde5
...
TX1 found! If reverted: false
...
Checking TX2 with ID = 0x9f8f9bec9593f74d9353ac7f1087afa32646638155e6a448dc35d60788d958b5
...
TX2 found! If reverted: true
...
Checking TX3 with ID = 0x319b70009a8234107a2d08c3dbf9e93396330892badb03b5c4cebac391d68c2d
...
TX3 not found!
...
Checking TX4 with ID = 0x1c30f07aae653711ab3284062afe557f1674c56d625114326230b041d79f9931
...
TX4 not found!
...
Checking TX5 with ID = 0xd92e5ffcf3e9ff11f33c59b814869c23df75c7cc49773fcb95ccc8b9bd420a43
...
TX5 not found!
...

在发送完这些交易后,每当程序检测到一个新的区块诞生,它就会开始检查这些交易的状态。这样的操作会持续五个连续的新区块。在我运行这段代码的时候,第一个新区块的高度为 3170840。其对应的结果显示 TX1TX2 已经被记录在了账本上。与此同时,我们可以看到 TX2 被系统所撤销。

--------------------------
Block Number = 3170841
--------------------------
...
Checking TX3 with ID = 0x319b70009a8234107a2d08c3dbf9e93396330892badb03b5c4cebac391d68c2d
...
TX3 not found!
...
Checking TX4 with ID = 0x1c30f07aae653711ab3284062afe557f1674c56d625114326230b041d79f9931
...
TX4 found! If reverted: false
...
Checking TX5 with ID = 0xd92e5ffcf3e9ff11f33c59b814869c23df75c7cc49773fcb95ccc8b9bd420a43
...
TX5 not found!
...

在下一个区块中(区块高度 3170841),TX4 被记录进了账本上,而 TX3TX5 没有出现。这个结果是和我们的预期一致的。TX3TX5 在随后的区块高度也没有出现。

小结

在这篇文章中,我阐述并演示了唯链雷神区块链一个特有的功能,这个功能使用户能够建立一个强制性的交易依赖关系。具体来说,如果一笔交易的执行依赖另一笔交易,只有在被依赖的交易出现在账本上且没有被撤销的情况下,这笔交易才会被接受并处理。

来源链接:bbs.vechainworld.io