ETH中继能提供的功能
1. 接受其他服务端脸上的相关服务请求,中继处理请求业务,将处理后的响应返回给用户。如查询余额等2. 发起交易的功能,包括ETH转账和ERC20转账3. 用户的钱包创建4. 对链上区块相关事件的监听,目前可以实现的监听事件包括但不限于如下:a. ERC20 Token的授权Approve事件b. Token转账Transfer的结果事件c. WETH Token置换ETH事件d. 新区块生成事件e. 遍历一个区块事件f. 区块分查事件
区块遍历
在中继开发中,事件的监听是开发难度最高的。
事件监听的技术原理主要是通过获取一个区块内部的交易信息并解析交易信息内的“EventLog”(日志事件)来达到目的。
目前ETH提供web.js库,该库中有区块事件监听函数,但并不完美,有以下不足:
- 如果客户端的进程被杀死,监听动作就会被丢失
- 如果对上一次最后遍历成功的区块号进行存储,重新启动的时候将会造成时间段内新生成的区块的数据丢失
- 完整的监听流程比较消耗客户端的设备资源,影响用户体验
为什么要监听?
中心化Exchange就要做这样的事情,比如充值或者提现,都需要对链上的数据进行监控,要链上转账成功后,再修改中心化服务端的数据库,如果不监控,就提现而言,如果链上交易失败,但是用户在中心化的数值减少了,就会有问题。
在ETH机制中,链上的授权,交易,合约发布等事件都是一条条的交易信息,可以把每一笔交易信息提取出来,进行划分处理,就可以实现不同的功能和业务。
RPC接口(Remote Process Call,远程过程调用)
RPC接口在基于通讯协议方面有多种实现方法,但是主要是如下两种:
- 同“RESTful API”一样,基于应用层HTTP/HTTPS协议实现
- 基于传输层的TCP协议实现,也被称为Socket(嵌套字)的实现
两种方式的区别在于请求和响应的位置,一个是在应用层,一个是在传输层。
RPC接口和“RESTful API”接口,在HTTP/HTTPS协议实现,在速度方面几乎没有差别,
但是基于传输层的TCP协议实现的RPC接口,除了数据传输流的层级上比”RESTful”接口少而整体比它快外,传输时的整体数据报层面还少了HTTP/HTTPS的头部数据量及组装的时间损耗。也即是说,当RPC接口基于传输层的TCP协议实现时,RPC接口不仅请求和响应的速度比“RESTful”接口快,而且数据量也相对要少。
常见的RPC框架有以下几种:
- JSON-RPC
- XML-RPC
- Protobuf-RPC
- SOAP-RPC
简言之:RPC协议,就是规范了一种客户端和实现了RPC接口的服务端交互的数据格式。
RPC接口实现的大致流程:
- 服务调用方案按照规范好了的编码方式把某个RPC接口的函数和参数进行序列化编码
- 发送到服务的提供方,即服务器端
- 服务器端再通过反序列化后把对应的参数提取出来
- 然后通过调用相关函数
- 最后把结果返回给服务端的调用方
相伴ETH RPC接口的重要三个参数
在了解ETH RPC接口之前,先了解3个参数,因为这3个参数会出现在每一个接口中。
- genesis,代表的是最早的,早期的源码版本中等同于 earliest
- pending,代表的是等待或者挂起状态中的
- latest,代表的是最新完成的
ETH RPC接口
1.eth_blockNumber
该接口可以根据传参(genesis,pending,latest)获取 3 种类型的区块高度
- genesis, 代表的是创世区块,因为可以指定最早的区块号,所以创世区块不一定是0
- pending,代表的是当前正在被minter开采的区块,代表正在打包交易的区块,一个区块在打包完一定的交易后才会完整上链
- latest,当前链上最新生成的区块的高度
2.eth_getBlockByNumber
该接口根据区块高度获取区块的部分信息,是在遍历区块时主要用来获取区块数据信息的一个接口。该接口可以提供如下数据:
- 区块头部的部分字段
- 所有打包在这个区块中的交易hash数组
也就是说,返回的数据的结构体是Block,即这个结构体所含有的信息,都可以拿得到。
// Block表示以太坊区块链中的一个完整的区块。type Block struct {// block的header指向的是一个的结构体,其中带有该block的所有属性信息header *Header// 叔块block的数组uncles []*Header// 当前该区块所有打包的交易记录transactions Transactions// 缓存,应该是该区块的的hash及区块大小hash atomic.Valuesize atomic.Value// total difficulty,当前区块总共的难度值=前一个区块的td+当前区块header中的difficulty。td *big.Int// 区块被接受的时间ReceivedAt time.Time// 记录区块是从哪个P2P网络传过来的ReceivedFrom interface{}}
3.eth_getTransactionByHash
该接口可以根据一笔交易记录的哈希值,可以获取这笔交易的详细信息。
该接口提供了交易查询功能,在获取交易区块信息后,从区块信息中获取被打包的交易的哈希值,进行全部交易信息的提取。处于pending(等待或者挂起)状态的交易,返回的将会是空,也可以根据接口的这一特点来判断一笔交易是否成功。
获取数据的结构体是Transaction。
// Transaction is an Ethereum transaction.type Transaction struct {inner TxData// Consensus contents of a transactiontimetime.Time // Time first seen locally (spam avoidance)// cacheshash atomic.Valuesize atomic.Valuefrom atomic.Value}// TxData is the underlying data of a transaction.//// This is implemented by DynamicFeeTx, LegacyTx and AccessListTx.type TxData interface {txType() byte // returns the type IDcopy() TxData // creates a deep copy and initializes all fieldschainID() *big.IntaccessList() AccessListdata() []bytegas() uint64gasPrice() *big.IntgasTipCap() *big.IntgasFeeCap() *big.Intvalue() *big.Intnonce() uint64to() *common.AddressrawSignatureValues() (v, r, s *big.Int)setSignatureValues(chainID, v, r, s *big.Int)}
4.eth_getTransactionReceipt
该接口可以根据一个交易的哈希值来获取这笔交易收据信息。
该接口返回的数据在一定程度上和eth_TransactionByHash是相同的,但该接口返回的数据更加详细,例如交易的日志Logs,这也是进行事件监听最主要的数据源。和eth_getTransactionByHash一样,处于pending状态的交易,返回的将会是空。返回的结构体是Receipt
// Receipt represents the results of a transaction.type Receipt struct {// Consensus fields: These fields are defined by the Yellow PaperTypeuint8`json:"type,omitempty"`PostState []byte `json:"root"`// 当这笔交易是成功的,该值为true,即 1 ,否则为 falseStatusuint64 `json:"status"`// 该字段和GasUsed不一样,它所代表的是当前的交易 所在区块的列表中,// 当前TransactionIndex之上的包含的其他所有交易的Gas之和。// 例如,TransactionIndex = 4,// 那么它的CumulativeGasUsed数值就是下标为 01234的交易的GasUsed数值之和CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`Bloom Bloom`json:"logsBloom" gencodec:"required"`// 由当前交易生成的事件(Event)日志Logs[]*Log `json:"logs"gencodec:"required"`// Implementation fields: These fields are added by geth when processing a transaction.// They are stored in the chain database.TxHash common.Hash `json:"transactionHash" gencodec:"required"`// 合约地址,只有在当前交易是合约创建的情况才会有值// 对应的是新创建合约的ETH地址,其他情况为空ContractAddress common.Address `json:"contractAddress"`GasUsed uint64 `json:"gasUsed" gencodec:"required"`// Inclusion information: These fields provide information about the inclusion of the// transaction corresponding to this receipt.BlockHashcommon.Hash `json:"blockHash,omitempty"`BlockNumber*big.Int`json:"blockNumber,omitempty"`TransactionIndex uint`json:"transactionIndex"`}
5.eth_getBalance
该接口获取的是某个ETH地址的ETH数值,即ETH余额。它不是获取ERC20 Token或者其他Token的余额。
6.eth_sendRawTransaction 和 eth_sendTransaction
具体请跳转,是所有交易的触发接口。其中eth_sendRawTransaction 可以完成的交易类型包含eth_sendTransaction.
7.eth_getTransactionCount
该接口可以根据一个ETH钱包地址获取当前钱包地址的交易序列号Nonce,该接口的第二参数是genesis、pending、latest,各个参数的分别对应的是下面的作用:
- 取genesis时: 获取当前ETH地址第一次发起交易时的Nonce序列号
- 取pending时: 获取当前ETH地址提交了正处于pending状态等待被区块打包的交易订单所对应的Nonce序列号。如果当前查询的地址没有处于pending状态的交易,那么返回的是与latest一样的Nonce序列号。
- 取latest时: 获取当前ETH地址提交了且被区块打包成功的交易订单所对应的Nonce序列号加1的值。比如地址A最后一笔成功交易对应的Nonce为8,那么当调用接口传入参数为latest的时,获取的结果时9,即8+1.
- 在eth_getTransaction中,Nonce查询满足:pending>=latest>= genesis
8.eth_getCode
该接口根据ETH地址判断当前地址时合约地址还是非合约地址。如果不是,返回值是”0x”,否则,返回的是智能合约当初创建时的十六进制码。
9.eth_estimateGas
该接口用来估算一笔交易要消耗多少GasLimit,该所估算出来的值没有涉及GasPrice。
入参中的data是被估算的数据量,估算的结果数值和数据量成正比。
这个接口在钱包发起交易的时让用户选择燃料费为多少的功能上会用到,提现在燃料费进度条中的最大值上。最大值一般就是这个接口返回的数值。
10.eth_call
这是ETH用来访问智能合约的万能RPC接口,也就是说,任何智能合约上的公有函数都能使用这个接口访问。
eth_getBalance是用来查询ETH余额的,但是ERC20 Token 余额,就需要通过eth_call来进行查询。这个接口的入参和 eth_sendRawTransaction/eth_sendTransaction入参是一样的。
eth_call接口是一个只读接口,不会改变区块链上的数据,可以访问ETH智能合约中的所有函数。
eth_call只读特性从源码上层面的理解
EVM在执行合约代码时,
先,使用区块号blockNumber找出对应的区块信息,
再,系统地根据这个区块的数据实例化一个名为status的变量,由status来实例化对应的EVM实例,
然后,根据区块提供的信息来实例化合约代码的变量值,即智能合约的函数在被执行前它的变量值是由当前区块高度决定的。
也就是说,eth_call在调用transfer函数时会直接在代码的内存层面进行值的修改,并没有广播出去和修改到数据层面。
节点链接
目前获取节点链接的的方案有两种
- 自行购买服务器,然后启动ETH节点服务程序,例如Geth,再获取节点程序提供的接口链接。
- 使用第三方服务平台提供的节点链接
对于第一种方法,操作流程是先下载对应的ETH节点版本源码,然后根据文档的编译方法进行自编译,生成可执行程序,再部署到服务器。需要注意的是,ETH节点的源码不仅仅有版本号不同的版本,还有不同计算机语言的版本,官方的Geth是Golang语言开发的版本。
第一种方整体实施起来的优缺点:
(1) 可自定义性高
- 可以自行配置节点启动的配置文件
- 可自行修改源码进行二次开发后再编译
- 可自行设置服务网关、节点集群等相关的运行方式
- 可参与公链minter,赚取ETH的收入
(2) 技术要求难度相对来说比较高
- 要求使用者必须掌握和了解配置文件中各项属性的含义及其影响
- 要求使用者必须懂一些服务端运维的知识
- 要求使用者懂得对应节点编写语言的编译命令,甚至使用过该门语言
(3) 自运维成本高
- 要求监控节点服务的运行情况
- 要求实现节点程序,因为某些问题被杀死后能够进行自动重启
- 在集群情况下,要求保证各服务的稳定性
(4) 需要自付费服务器等产品的花销
第二种方案
- 适用于学习阶段 和 线上业务不需要高度自定义化的情况使用
第二种方案和第一种方案对比,优缺点相反。
第二种方案也是最方便和便宜的,因为只需要一个链接即可。
第二种方案的实施
链接获取
可以根据 https://infura.io/ 来免费获取节点链接。
Infura是国外一个托管ETH节点的集群,还允许开发者免费申请属于自己的ETH节点链接(包括主网和测试网),功能比较齐全和方便。
Detail go to 153 and 159。