区块验证

与交易一样,网络中的节点也会对区块执行验证操作。验证要实现的是每个区块都可以在任意节点上独立地进行验证,且不依赖于任何外部特征。有关交易验证的更多信息,请查看第3章

区块验证

调用脚本和验证脚本

网络上的每个全节点都从其他节点那里接收完整的NEO区块链数据。对于接收到的每个区块,都会独立地执行验证,以确保接收到的区块不是由恶意节点或故障节点发送的。这样节点就不必信任其他的节点,从而创建了一个安全且去信任的系统。对于收到的每个区块,节点会通过使用验证脚本中的公钥来验证调用脚本中签名的方式,对区块中的所有交易进行验证。可以将这个调用脚本视为能解锁UTXO的密钥,证明该脚本有权限花费这个UTXO,同时将验证脚本作为工具传递给每个节点进行验证。有关交易验证的更多信息,请查看第3章

除了交易内的调用脚本和验证脚本之外,区块本身也包含有一个调用脚本和验证脚本。验证脚本有时也被称为见证人

当查看第2部分,区块结构中所介绍的高度为3,649,960的区块时,我们可以在其script字段中看到这两个脚本。为方便起见,我们把这部分信息提取出来了:

"script": {
	"invocation": "4013a82dff8a58ff750703cc32852899124917ded6bd7a7d66bf31d693890488717ab4ee258c0806286d3c2d49da4f9f52d1c6a20843ab5a9a0b4e867ed3d4c5644087875bbc17e8300bb2ee82b3623833be4693fe378cde10e380ce64fe4f1cdba6acb70b6d9d3c52efaa776a6c8a5f91cf3a48b6df79edeae1cd26259b00add96640a1053731b59c6687965e942600301b68f79252e9aa08047115e649930df679d853438bc95c88c9cfa7aa9737d51f82d25dde5b5435cd8266a132a726d7d01f294043df832e612d220c3ce639cbca4ab18f9ce21dfd8718340dae4ca49fd239a4ee06d294e9d583bc4e0da5cccb5eb0f35e7b836d7c633b1e9ca20c0c0af429644740c352f2425c56001a292cc28cba9e736913f9c116580796efacda4270ac104659173ee1bc4bed706c0f3ea90c9b8201653abe74a30e627c443855af6c08c90acb",
	"verification": "5521024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d21025bdf3f181f53e9696227843950deb72dcd374ded17c057159513c3d0abe20b6421035e819642a8915a2572f972ddbdbe3042ae6437349295edce9bdc3b8884bbf9a32103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae"
}

这看起来似乎是一些完全随机的神秘代码。 然而,这种神秘其实是有理可循的。 让我们再深入挖掘下。

操作码

可以点击此处查看NEO的操作码。 验证区块时,这些操作码会告诉NeoVM如何执行操作。 我们来看看这个验证字符串。 由于这是以16进制表示的字节码,我们一字节一字节的来分析。它的工作方式是解释每个操作码并将其推送到执行栈,一次一个操作码。 最后由NeoVM来执行该堆栈以便对区块进行验证。

5521024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d21025bdf3f181f53e9696227843950deb72dcd374ded17c057159513c3d0abe20b6421035e819642a8915a2572f972ddbdbe3042ae6437349295edce9bdc3b8884bbf9a32103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae

1.我们从第一个字节开始:0x55。 根据OpCodes表可知:

/// <summary>
/// 数字5被推送至堆栈
/// </ summary >
PUSH5 = 0x55

因此将数字5加入至堆栈

2.接下来:0x21。 获取接下来的33个字节并推送至堆栈。

/// <summary>
/// 将33个字节推送至计算栈
/// </summary>
PUSHBYTES33 = 0x21

3.接下来的33个字节被推送到堆栈:

024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d

4.在这33个字节之后,再次遇到了操作码0x21(… 6662d4a59ad548df0e7d 21 025bdf3f …)。 这又意味着将接下来的33个字节推入堆栈。操作码0x21总共出现了7次,因此执行7次将33字节推入堆栈的操作。

一共执行7次将33字节添加到堆栈的操作
0x024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d
0x025bdf3f181f53e9696227843950deb72dcd374ded17c057159513c3d0abe20b64
0x035e819642a8915a2572f972ddbdbe3042ae6437349295edce9bdc3b8884bbf9a3
0x03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c
0x03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a
0x02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554
0x02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093

5.快成功了! 现在只剩下最后2个字节了。 下一个是0x57。 所以向堆栈加入数字7。

/// <summary>
/// 数字7被推送至堆栈
/// </summary>
PUSH7 = 0x57

6.要解析的最后一个字节是0xae。 查看这个字节码可知:

/// <summary>
/// 根据一组m个签名(一个数组或值m后跟着m个签名)验证一组n个公钥(一个数组或值n后跟着n个公钥)。 使用多方签名的方式对交易进行验证,并将布尔输出值加入至主堆栈的顶部。
/// </summary>
CHECKMULTISIG = 0xAE

因此最终的堆栈是这样的:

验证脚本的完整堆栈
5
0x024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d
0x025bdf3f181f53e9696227843950deb72dcd374ded17c057159513c3d0abe20b64
0x035e819642a8915a2572f972ddbdbe3042ae6437349295edce9bdc3b8884bbf9a3
0x03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c
0x03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a
0x02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554
0x02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093
7
CHECKMULTISIG

当我们按照相同的过程来解码调用脚本的字节码时,我们提出了5个16进制字符串,每个64字节长。

调用脚本的完整堆栈
0x13a82dff8a58ff750703cc32852899124917ded6bd7a7d66bf31d693890488717ab4ee258c0806286d3c2d49da4f9f52d1c6a20843ab5a9a0b4e867ed3d4c564
0x87875bbc17e8300bb2ee82b3623833be4693fe378cde10e380ce64fe4f1cdba6acb70b6d9d3c52efaa776a6c8a5f91cf3a48b6df79edeae1cd26259b00add966
0xa1053731b59c6687965e942600301b68f79252e9aa08047115e649930df679d853438bc95c88c9cfa7aa9737d51f82d25dde5b5435cd8266a132a726d7d01f29
0x43df832e612d220c3ce639cbca4ab18f9ce21dfd8718340dae4ca49fd239a4ee06d294e9d583bc4e0da5cccb5eb0f35e7b836d7c633b1e9ca20c0c0af4296447
0xc352f2425c56001a292cc28cba9e736913f9c116580796efacda4270ac104659173ee1bc4bed706c0f3ea90c9b8201653abe74a30e627c443855af6c08c90acb

解释

我们做到了!我们已经解析了调用脚本和验证脚本中的内容,现在让我们来看看其中所代表的含义。这些脚本的目的是允许任意节点都能验证该区块是由共识节点创建并达成共识的。为此,共识节点会使用自己的私钥对区块进行签名。当7个共识节点中有5个对同一区块签名的话,就表示节点达成了共识且该区块是有效的。这5个签名包含在调用脚本中。

现在我们需要做的就是验证这5个签名的有效性,以便对这个区块进行验证。为此,区块内会包含有验证脚本。通过这种方式,区块就可以告知每个节点如何执行验证操作。验证区块的节点会执行验证脚本,如果脚本验证通过,则表明该区块是有效的。

我们已经了解了验证脚本的含义。其中使用到了创建该区块所涉及到的7个共识节点的公钥。这种方式是使用了类似于多方签名交易的概念,并将这个应用于区块中。最后一个操作码CHECKMULTISIG表明了前面这些字节数据的意义。语法解释为七分之五的多方签名,因此堆栈上的7个33字节长的字符串会被解释为这7个节点的公钥。

如果使用验证脚本中七分之五的公钥对调用脚本中5个签名的验证执行通过的话,则认为区块是有效的。

返回目录