如何通过UTXO交易实现支持EVM规范的智能合约
扫描二维码
随时随地手机看文章
Qtum账户抽象层(AAL)实现简析
Qtum设计上以比特币UTXO为基础账户模型,并实现了支持EVM规范的智能合约,这是通过账户抽象层(Account Abstract Layer, AAL)来完成的。AAL对UTXO账户和EVM合约账户之间进行了适配,这样通过AAL可以使用UTXO交易输出实现在链上创建智能合约,发送交易到合约账户用于触发合约的执行,完成执行后AAL最终对执行结果进行处理并适配至UTXO。由于采用了AAL,合约开发者不需关心对合约操作相关的UTXO转换细节,即可使用EVM的特性进行开发而且兼容现有以太坊的智能合约。本文通过对从UTXO交易到智能合约执行的实现代码进行解读,初略分析了AAL的工作过程。
1.UTXO交易新增的脚本操作码
Qtum 针对UTXO交易脚本新增了三个操作码OP_CREATE,OP_CALL和OP_SPEND,目的是用于为UTXO和EVM账户模型之间的转换提供操作支持。这些操作码定义在opcodetype枚举类型中:
这个三个操作码分别有以下作用:
OP_CREATE用于智能合约的创建;
OP_CALL用于合约的执行;
OP_SPEND用于合约余额的花费。
为了在区块生成过程中,识别并处理由这几个操作码控制的交易,在用于UTXO模型交易的类CTransaction中增加了HasCreateOrCall()和HasOpSpend()函数,用于新区块中对mempool中的交易处理,并在脚本操作码解析的EvalScript()函数中增加了相应的处理。
2.UTXO交易到EVM模型交易的转换
产生新的区块时,除了对UTXO交易进行常规的参数合法性、共识规则、DDOS攻击检查等之外,还需要使用操作码检查函数HasCreateOrCall()判断交易输出是否包含OP_CREATE或OP_CALL,分别对应着EVM需要执行合约创建或合约调用。这部分有以下的处理过程:
2.1 进行EVM模型的账号参数提取
合约在EVM的执行用到了data、gasPrice、gasLimit、VM version这几个参数,这些参数是通过RPC调用sendtocontract 发送的,sendtocontract会生成一个UTXO交易,并在交易输出中使用了OP_CALL操作码,之后交易会广播到区块链网络上。AAL中从UTXO到EVM的适配是通过QtumTxConverter类实现的,在这一步中该类的成员函数extracTIonQtumTransacTIons()和parseEthTXParams()完成对所有此类UTXO交易输出的参数提取。代码片段如下:
以上代码首先判断如果opcode 为OP_CALL,则说明地址为vecAddr的合约已经创建,因此直接转换成EVM格式的地址receiveAddress,否则为OP_CREATE,对应合约的创建,没有该字段,所以不做提取。接下来依次完成了data、gasPrice、gasLimit、VM version的提取,这些都是EVM执行bytecode时必不可少的参数。
2.2 进行EVM账户模型的交易转换
交易转换是通过QtumTxConverter类的函数 createEthTX()完成,使用前面一步提取的参数和UTXO的交易输出vout创建了QtumTransacTIon类型的交易。由于QtumTransacTIon派生自EVM中的dev::eth::Transaction类,因此和EVM执行相关的操作QtumTransaction类都支持。
首先代码etp.receiveAddress == dev::Address()判断该合约是EVM状态中没有而需要新创建的还是EVM状态已经包含的合约,差别只在于合约地址。然后,QtumTransaction()构造函数完成了部分的交易参数构造,接下来的语句提取交易的发送者(sender),之后设置交易HASH。一个UTXO交易支持多个输入和输出,Qtum的AAL设计考虑到了这种情况,因此AAL支持一个交易输出包含UTXO账号和合约账号,通过最后设置的nOut指示该交易的nOut输出是发送到智能合约的,所以该输出将触发合约执行。这样就按照EVM的账号模型完成了交易的转换。
3.合约执行及执行结果的UTXO转换
合约的执行会改变状态(由QtumState类的实例化对象globalState统一管理),对于合约的状态,Qtum沿用了EVM定义,所以能兼容所有的符合EVM规范的智能合约。但是账户金额的转移(transfer),Qtum做了UTXO的转换,这意味着智能合约和普通的UTXO模型账号之间能完成交互,这是AAL实现UTXO支持智能合约的重要的一环。下面简要介绍一下合约执行和状态结果的转换过程。
3.1 合约执行环境构建及合约执行
合约的执行是对合约处理中很关键的一步,直接对合约的状态产生影响。通过ByteCodeExec类实现了EVM对合约bytecode的执行,主要函数是performByteCode()。这一步的主要流程是使用上面提取的交易参数,来进行虚拟机执行环境的构建,之后完成合约的执行,其代码如下:
首先是构建合约执行环境,由BuildEVMEnvironment()完成。可以看到这个执行环境是针对每个独立交易进行的,这样就最大限度的把不同交易的合约执行过程隔离开,避免合约执行过程中的交叉影响。然后构建一个新的sealEngine类,该类是EVM执行引擎,由createSealEngine()函数具体完成。中间对出现的可能状态异常进行检查,之后globalState-》execute()完成合约的执行,这里使用到了构建的执行环境envInfo和EVM执行引擎se。
3.2 合约执行结果的UTXO转换
合约执行完成后的结果保存在vector《ResultExecute》 result,vector向量理记录了每个合约执行产生的EVM账户间transfer关系,AAL通过把这些transfer转换成UTXO交易,完成了从EVM账户模型到UTXO模型交易的转换。这一处理是通过processingResults()函数实现的,以下是代码片段。
首先定义了ByteCodeExecResult类型的resultBCE变量,用于保存转换的结果。使用操作码OP_SPEND,用于实现交易的花费,这是因为比特币的UTXO通过私钥签名在交易输入解锁后来实现余额花费的,而EVM执行涉及不同账户之间的transfer,所以需要通过OP_SPEND实现这些transfer到UTXO模型交易的转换。如果execRes.excepted不为None,即合约执行异常,则将余额返还给合约调用者。否则,如果没有异常,则将扣除消耗的gas之后的剩余gas返还给合约的调用者。对于合约执行中出现的transfer其UTXO交易保存在result[i].tx中。因此,经过这一步处理合约执行产生的不同UTXO账户之间的交易就保存在valueTransfers向量中了,最终这些交易会包含进新的区块中。至此AAL模块就完成了从EVM交易到UTXO的转换。
4.总结
AAL通过新增的UTXO脚本操作码,协助完成合约的创建、执行和花费。在合约创建和执行前,需要进行UTXO交易到EVM模型交易的转换,之后使用构建的EVM执行环境和引擎,完成合约的执行。AAL最终对合约的执行结果进行处理并从EVM适配至UTXO,这样就实现了基于UTXO的智能合约。AAL使得Qtum兼容符合EVM规范的智能合约,为Dapp提供一个新的基础平台,同时UTXO的优点使得诸如并行处理、隐私性等优点能得以保留。