本文主要记录以太坊C++客户端Aleth的源码分析和相关实验过程和结果。本文将讲解两部分的内容,一是转账交易和智能合约的入口代码在哪里?二是通过实验验证转账交易和智能合约交易这两种不同交易所对应的不同的输入数据。
读者不安装实验环境不影响阅读本文章。
实验环境准备:如果读者想要完成本文所示的验证实验,可以参考本人前面三篇文章,分别涉及Aleth客户端的按照,here;私有区块链的搭建和智能合约的运行,here。这些实验环境准备工作是为了能够修改Aleth源码,并在本地编译和运行以太坊区块链。
1. 转账交易和智能合约的入口代码在哪里?我们在命令行中输入aleth --help,这个对应的是在aleth子项目下的main函数(Aleth客户端源码中有aleth子项目,本文中,两者以大小写区分)。这是整个Aleth项目的总入口,但是,Aleth客户端提供了RPC-JSON交互接口。不同编程语言实现的以太坊客户端虽然实现方式不一样,但是他们所提供的RPC-JSON接口是一样的。RPC,表示远程程序调用,是进程间通信的一种方式;JSON,是一种数据编码方式,广泛应用在网站后台和前台数据交互中,比如百度地图所提供的公共接口就以JSON格式。Aleth的RPC-JSON接口的代码在项目web3jsonrpc下面的Eth.h文件里面。其中跟转账交易和智能合约交易有关的接口主要有两个,分别为:
virtual std::string eth_sendTransaction(Json::Value const& _json) override;
virtual std::string eth_call(Json::Value const& _json, std::string const& _blockNumber) override;
上面第一个函数的说明和使用的例子可以参考,here,第二个函数的说明可以参考,here。
eth_sendTransaction函数的作用是(1)如果data字段(属性)包含代码,那么就创建一个智能合约交易。(2)否则,创建一个转账交易。总之,这个函数都会创建一个交易。
Creates new message call transaction or a contract creation, if the data field contains code.
eth_call函数的作用是,用于调用一个智能合约,而不在区块链中创建任何交易。
Executes a new message call immediately without creating a transaction on the block chain.
首先,我已经在我的电脑上面下载了源码,并使用Visual Studio2017导入。然后,我分别在上面这两个函数的前面写上下图所示的打印语句。然后,编译新改动的web3jsonrpc项目以及aleth项目。
按照这篇文章的方法,《私有区块链的搭建和智能合约的运行,here》,我在本地搭建了一个只有一个节点的私有区块链,并使用Remix连接这个区块链。下面便是本人执行的结果。
在Remix上面编译智能合约:pragma solidity ^0.4.0;
contract Greeter{
string public yourName;
function Greeter() public {
yourName = "World";
}
function set(string name) public{
yourName = name;
}
function hello() constant public returns (string){
return yourName;
}
}
按照上图方式Deploy智能合约,点击这个按钮之后,需要在Aleth启动的命令行窗口中输入密码。然后就出现了下图所示的结果,显示程序调用了Eth::eth_sendTransaction方法。函数输入的数据主要包括四个,分别为发送人地址,发送的值,gas limit的值(最多希望使用的gas),以及data。data是智能合约编译之后的结果。注意,这里接收者的地址(to字段)是没有数据的。
我们在remix中设置智能合约的name字段,如下图所示,看一下会调用哪一个函数,以及参数是什么?
下图便是运行set函数之后的结果。它显示eth_sendTransaction被调用了,此时to字段不为空,表示的是智能合约的地址,是智能合约发起者和Nonce值的哈希的结果。data的字段不再表示智能合约编译后的数据,而是所调用的方法名字的参数。这一点可以通过查看Remix可以知道,如下第二张图所示。
上图红圈部分表示的是调用智能合约set函数的时候的参数的值。
接下来,我们调用智能合约的hello函数,该函数不需要参数,只返回name。
它调用了eth_call函数,意味着这个过程没有创建任何交易,也即是不会在区块链中存储任何东西。
什么时候调用eth_sendTransaction,什么时候调用eth_call函数呢?如果需要创建一个新的智能合约,或者创建一个转账交易,或者在调用智能合约的时候修改了智能合约的值(全局变量),就会调用前者。如果没有修改智能合约的值,就调用后者。这里给予验证。首先,我们向上面的智能合约代码中新增两个函数:
function set_no_param() public{
yourName = "new name";
}
function hello_with_param(string _name) constant public returns (string){
return _name;
}
笔者可以猜测一下这两个函数分别调用了eth_sendTransaction或者eth_call?下面是执行结果:
当执行set_no_param函数时,结果如下两张图所示,表示调用了eth_sendTransaction函数。
当执行智能合约hello_with_param函数是,调用了eth_call函数,结果如下两张图所示。第一张图的input数据域对应第二张图的data数据域。
下面是智能合约的完整代码:
pragma solidity ^0.4.0;
contract Greeter{
string public yourName;
function Greeter() public {
yourName = "World";
}
function set(string name) public{
yourName = name;
}
function set_no_param() public{
yourName = "new name";
}
function hello_with_param(string _name) constant public returns (string){
return _name;
}
function hello() constant public returns (string){
return yourName;
}
}
下面是向Aleth c++源码中添加的代码,目的是打印所调用函数的参数。
if (_json.isObject() && !_json.empty()){
if (!_json["from"].empty())
cout << "from: " << jsToAddress(_json["from"].asString()) << endl;
if (!_json["to"].empty() && _json["to"].asString() != "0x" &&
!_json["to"].asString().empty())
cout << "to: " << jsToAddress(_json["to"].asString()) << endl;
if (!_json["value"].empty())
cout << "value: " << jsToU256(_json["value"].asString()) << endl;
if (!_json["gas"].empty())
cout << "gas: " << jsToU256(_json["gas"].asString()) << endl;
if (!_json["gasPrice"].empty())
cout << "gasPrice: " << jsToU256(_json["gasPrice"].asString()) << endl;
if (!_json["data"].empty()) // ethereum.js has preconstructed the data array
cout << "data: " << jsToBytes(_json["data"].asString(), OnFailed::Throw) << endl;
if (!_json["code"].empty())
cout << "code: " << jsToBytes(_json["code"].asString(), OnFailed::Throw) << endl;
if (!_json["nonce"].empty())
cout << "nonce: " << jsToU256(_json["nonce"].asString()) << endl;
}
读者阅读《以太坊技术详解与实战》这本书有利于理解本文内容。书中2.7章的内容对应本文的实验部分。书籍中只讲了是什么,没有通过实验验证究竟是不是这样。本文算是一个补充。
谢谢