第三章 以太坊区块链交互机制
本章主要介绍的内容是如何与以太坊区块链进行交互,包括JSONRPC与web3的介绍。由于我水平有限,不懂什么专业的名词,都是按照我自己的理解论述的,看起来比较low,大家见谅。
1.基本原理
1.1.以太坊的交互通信架构
这时候,就需要上这张图了,清晰简明扼要优美。
要完成应用与以太坊底层区块链的交互。基本流程如上图所示:
(1)应用app调用web3(官方推荐的,基于javascript)或者自行开发的api包(目前冠杰同学已经完成部分开发),将一个应用层的命令转化为web3命令。比如应用层需要查底层区块当前高度,就可以调用web3.eth.blockNumber方法;web3还有很多其他方法,请参考https://github.com/ethereum/wiki/wiki/JavaScript-API。如果要使用自己开发的api包,那么有其他问题。
(2)web3或者自行开发的api包,调用方法转化为一个JSON格式的数据包,通过HTTP协议发送给pyethapp开放的jsonrpc端口。(pyethapp默认端口为4000,testrpc/geth默认端口为8545)。比如web3.eth.blockNumber的JSONRPC包,大致是这样的:{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}
(3)pyethapp接收到JSON包,对于本地调用,自行执行命令并返回;对于外部调用,自行对底层pyethereum操作并返回。比如获取账户地址是本地调用,而发送交易是外部调用。(关于本地调用与外部调用,详细信息请见以太坊原理解析)
tips:此外pyethapp可能还会有一些扩展功能,比如绑定智能合约代码编译器solidity,绑定solidity之后,可以在pyethapp上编译智能合约代码。
1.2内部调用与外部调用
就像之前介绍的,以太坊并不是所有内容全部保存在区块链上,所以也不是所有调用都会记录到区块中。调用方式包括两种:外部调用与内部调用。
外部调用就是标准的调用方式:由一个外部账户发送一起消息,节点在接收消息之后,根据消息的内容,执行转账操作或者在以太坊虚拟机中执行智能合约的代码。
内部调用是简单的内部调用。对于节点来说,很多内容都是不需要在以太坊虚拟机中执行消息即可获知的,比如当前区块高度,比如某个账户余额(之前说过账户余额,也就是世界状态是在内存中保存的,而不是在区块中保存)。执行内部调用时,不需要消耗gas。
tips:
小扩展一下,以太坊中有一种特殊的eth_call方法,它是一个内部调用方法,并不会记录到区块上。但是它可以直接执行,会在result字段返回执行结果。
tips:这里有我的一个私人小想法:我们可以看到,以太坊完全不关心一条消息的执行结果,它只关心我需要记录什么消息。
以太坊区块链有“世界计算机”这么个说法。我们可以将全世界的代码(智能合约)放到区块链中,获得某些合约授权的用户可以执行相应合约,这么看来,这工作模式是很像一台世界计算机。但是与计算机的区别在于,使用计算机的时候,人们更多关注的是程序的运行结果,比如无论是上网还是玩游戏,人们需要的更多的关注于返回的内容(上网的过程是:我们提交了一个消息,服务器返回一个网页,我们关注的是返回的网页内容;玩游戏的过程是:移动和攻击的实质都是发送一条消息,我们关注的是计算机返回人物的位置变化或者对手HP的衰减)。而以太坊返璞归真,只关注消息,因为存储的内容才是真正有价值的。
实际上,不只是对于以太坊,对于所有的区块链系统,不上链的消息都是没有价值的。
1.3以太坊虚拟机
以太坊虚拟机(Ethereum Virtual Machine,EVM)类似于JVM,是以太坊中智能合约的运行环境。它被沙箱封装起来,被完全隔离。也就是说运行在EVM内部的代码不能接触到网络、文件系统或者其它进程。甚至智能合约与其它智能合约也只有有限的接触。这是出于安全性的考虑。
EVM负责执行智能合约。随着智能合约的执行,会消耗相应的gas。EVM会按照之前介绍的gas机制,该返还的返还,该回滚的回滚。
附以太坊部分操作消耗gas表(随版本更新可能有所不同):
2. JSONRPC介绍
项目文档:https://github.com/ethereum/wiki/wiki/JSON-RPC
2.1 JSONRPC原理
其实在上面流程中,我们已经可以看到JSONRPC的作用了。JSONRPC就是以JSON格式的数据,进行RPC(远程过程调用,Remote Procedure Call)调用。
JSON格式是一种轻量级的数据交互格式,类似于xml。而所谓的RPC,其实就是调用远程的函数,模拟调用本地的函数。本质上是一种通信协议,发过去消息,返回消息的执行结果。JSONRPC与传统的RPC区别只是发送的消息结构采用了JSON格式。
目前,以太坊一般性的JSONRPC调用方式,是通过HTTP协议发送JSONRPC 2.0报文。
除RPC之外,以太坊还向外部提供了IPC的方式可以调用合约。
2.2 JSONRPC监听接口
2.2.1 JSONRPC默认监听端口
以太坊各版本默认的监听端口不太相同。如下所示。
另外,testrpc这个模拟的开发环境,默认的监听端口为8545。
2.2.2修改JSONRPC监听端口
对于不同版本,在启动节点时,可以输入以下不同命令,开启不同的JSONRPC监听端口。
本教程中后续章节主要会采用python版本介绍。
2.2.3 JSONRPC调用实战
如果要查看当前区块链的高度(有多少个区块),只需要调用eth_blockNumber方法。调用的方法是向本机的监听端口发送相应的JSONRPC请求。curl是Linux系统中一个可以发送HTTP包的命令,使用curl发送一个HTTP的POST包,完整命令是:
$ curl -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}'127.0.0.1:4000
返回结果也是JSON格式的,返回信息是{"jsonrpc":"2.0", "id": 83, "result": "0x23b67"}
如下图所示:
tips:那调用合约的消息是什么样子呢?这个问题会在智能合约的章节给大家解答。
3 web3.js介绍
项目文档:https://github.com/ethereum/wiki/wiki/JavaScript-API
3.1 web3.js的安装与配置
3.1.1 web3的安装
web3.js(以下简称web3)是一个基于javascript完成的一个以太坊API。它本质上就是封装了上面的JSONRPC或者IPC的方法。(注意演示使用的python版本目前并不支持IPC,但是实质上web3.js是支持IPC的)
四种自动安装方式(建议采用第一种,先安装npm,之后使用npm install web3):
●npm: npm install web3
●bower: bower install web3
●meteor: meteor add ethereum:web3
●vanilla: link the dist./web3.min.js
3.1.2 web3的配置
只需要在javascript脚本中构造web3实例就可以使用它了!构造方法如下:
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
//这里的HttpProvider是连接对象,就是区块链开放的RPC端口。
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
这样就生成了一个web3对象,可以通过它调用web3封装号的JSONRPC调用方法。
tips:web3采用的RPC模式,是一种仿同步的调用方式。如果要使用异步请求方式。
web3.eth.getBlock(48,function(error, result){
if(!error)
console.log(result)
else
console.error(error);
})
3.2 web3基本命令
3.2.1批量请求
var batch = web3.createBatch();
batch.add(web3.eth.getBalance.request('0x0000000000000000000000000000000000000000','latest', callback));
batch.add(web3.eth.contract(abi).at(address).balance.request(address, callback2));
batch.execute();
然而批量请求并不会变快……批量请求实质是一个顺序的处理序列。在很多情况下,同时发出很多单一的请求速度会更快,因为这样处理请求是异步的。
3.2.2 web3常用命令
简单介绍获取区块链高度的方法,其他调用方法的详细方式请在项目文档中查询。在javascript脚本中写下下列代码即可:
var number = web3.eth.blockNumber;
console.log(number);
3.3 web3调用代码实例
这里是一个web3调用合约示例。需注意使用web3需要在nodejs环境下。
4自行开发api包
组成相应的JSONRPC格式的请求,通过HTTP协议发送到底层。并对返回的JSON报文进行解析。
详情请咨询冠杰同学。
5. pyethapp命令
项目文档:https://github.com/ethereum/pyethapp/wiki/The_Console
5.1 pyethapp控制台的调用
pyethapp是python版本以太坊的接口层,我们除了可以通过JSONRPC发送消息外,也可以直接利用pyethapp的控制台直接与底层进行交互。
运行
$ pyethapp run
之后,按ctrl+c之后enter可以进入控制台
或者直接输入命令
$ pyethapp run --console
可以直接进入控制台。
5.2 pyethapp控制台的基本命令
在pyethapp控制台可以执行的一些命令:
(1)查看日志
打印最后10行日志。
lastlog()
打印最后20行日志.
lastlog(20)
打印所有级别为INFO的日志
lastlog(level="INFO")
打印所有以app开头的日志
lastlog(prefix="app")
这些标记可以在一起试用
lastlog(n=100, level="DEBUG", prefix="app")
(2)eth对象
eth对象功能非常强大,主要作用是对区块进行查询或者操作,可以试试help(eth)查看帮助~
eth.latest可以看到最新块的内容。eth.pending可以看到尚未挖出的块的内容。
之后可以使用命令看到,父区块是谁,矿工账户的余额。还可以进行赋值、判断是否相等的操作,语法与python很像。
还可以查看当前区块难度,或者用to_dict()方法直接看整个区块内容。
我们可以看到这个区块是一个空块,没有交易。如果有交易的话,我们还可以使用命令
eth.latest.get_transactions()
查看当前区块上的所有交易。
之后用eth.latest.get_transactions()[0].log_dict()就可以看到当前区块第一个交易的详细内容。结构是类似这样子的:
{'data': '',
'gasprice': 10000000000000,
'hash': '6cf99d59ee667dd56cdc34c5b4f5349fdc2377ea9b715c27d1a9d2ba17796636',
'nonce': 5047,
'r': 37949441106074605966811028217872197823963701877937951182924847227904959108149L,
's':7840037590794696459998581397264706147072541541675627162552725158699447211262L,
'sender': '00fc572550f3bdfe84f72e3eaa99d02a43f69733',
'startgas': 90000,
'to': 'd63b635a458b99f7e900477e2d261d5d13e45d59',
'v': 27,
'value': 14444999}
同样的,我们可以用eth.latest.get_transactions()[0].value取的消息中相应的value值
为了方便,可以尝试一下各种乱七八糟的赋值和取值。比如tx = eth.latest.get_transactions()[0],就可以再使用tx.value取值了。
5.3 pyethapp的扩展命令
我们还可以通过这个客户端发送交易或者部署合约,非常强大。
5.3.1发送交易
发送交易之前,需要先解锁账户,否则账户处于lock状态是不能发送消息的。
eth.services.accounts.accounts[0].unlock('123456')
之后通过eth.transact()发送消息
tips:如果不知道eth.transact()函数需要哪些输入,输入eth.transact?就知道了,或者也可以用help(eth.transact)。
再看一下新区块的内容
对比一下之前空区块的数据,可以看到transactions字段有了数据,这就是交易上链之后的区块的样子。
5.3.2部署合约
官方网站给出的通过控制台部署合约的方法是这样子:
In [60]: code ="""
....: contract NameReg{
....:event AddressRegistered(bytes32 indexedname, address indexed account);
....:mapping (address => bytes32) toName;
....:
....:function register(bytes32 name) {
....:toName[msg.sender] = name;
....:AddressRegistered(name, msg.sender);
....:}
....:
....:function resolve(address addr) constantreturns (bytes32 name) {
....:return toName[addr];
....:}
....: }
....: """
In [61]: evm_code = solidity.compile(code)
In [62]: abi = solidity.mk_full_signature(code)
In [64]: eth.transact?
Signature: eth.transact(to, value=0, data='', sender=None, startgas=25000, gasprice=10000000000000)
In [67]: tx = eth.transact(to='', data=evm_code, startgas=500000)
In [68]: eth.find_transaction(tx)
Out[68]: {'block': , 'index': 0, 'tx': }
在合约部署之后,又如何调用合约中的方法,与合约进行互动呢?
我们必须先明确一点,要调用一个合约需要两个属性,一个是合约的abi,另一个是合约的地址。合约的abi是合约的所有方法,以及方法输入,输出等等的介绍,我们在编译的时候就能生成。那部署的合约地址我们怎么知道呢?合约的部署地址其实在tx.creates里。
In [69]: tx.creates.encode('hex')
Out[69]: '01ac2517022e28782fbbbe01e7d614cf0b21a89e'
In [70]: eth.new_contract?
Signature: eth.new_contract(abi, address, sender=None)
In [71]: namereg = eth.new_contract(abi, tx.creates)
之后再去调用这个合约的方法:
In [72]: tx = namereg.register('alice')
In [73]: eth.find_transaction(tx)
Out[73]: {}
In [75]: eth.find_transaction(tx)
Out[75]: {'block': , 'index': 0, 'tx': }
In [76]: namereg.resolve(eth.coinbase)
Out[76]: 'alice\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
现在回过头,看看我们之前在配置pyethapp的私有链的过程中,执行的账户解锁等命令都是通过控制台进行操作的,功能非常强大。
不过,虽然pyethapp的控制台能实现很多的功能,但是我们一般情况下还是不会使用它来部署合约的:一方面,在控制台部署合约的方法,相比api或者web3来说不太方便;另一方面,pyethapp是python版本目前特有的,go版本的geth与pyethapp的操作就略有不同,所以不具有普适性。
所以在这里就不过多介绍了。有兴趣的,可以自行访问以太坊项目wiki查看介绍。
�������Rwѓ���