码迷,mobileinhere.cn
首页 > windows程序 > 详细

以太坊rpc机制与api实例

时间:2018-01-22 19:12:37      阅读:9059      评论:0      收藏:0      [点我收藏+]

标签:exec   ide   register   字节   line   hex   func   忘记   hapi   

上一篇文章介绍了以太坊的基础知识,我们了解了web3.js的调用方式是通过以太坊rpc技术,本篇文章旨在研究如何开发、编译、运行与使用以太坊rpc接口。

关键字:以太坊,rpc,json-rpc,client,server,api,web3.js,api实例,postman

以太坊json rpc api

geth命令api相关

之前介绍过这些api都可以在geth console中调用,而在实际应用中,纯正完整的rpc的调用方式,

geth --rpc --rpcapi "db,eth,net,web3,personal"

这个命令可以启动http的rpc服务,当然他们都是geth命令下的,仍旧可以拼接成一个多功能的命令串,可以了解一下上一篇介绍的geth的使用情况。下面介绍一下api相关的选项参数:

api and console options:
  --rpc                  启动http-rpc服务(基于http的)
  --rpcaddr value        http-rpc服务器监听地址(default: "localhost")
  --rpcport value        http-rpc服务器监听端口(default: 8545)
  --rpcapi value         指定需要调用的http-rpc api接口,默认只有eth,net,web3
  --ws                   启动ws-rpc服务(基于webservice的)
  --wsaddr value         ws-rpc服务器监听地址(default: "localhost")
  --wsport value         ws-rpc服务器监听端口(default: 8546)
  --wsapi value          指定需要调用的ws-rpc api接口,默认只有eth,net,web3
  --wsorigins value      指定接收websocket请求的来源
  --ipcdisable           禁掉ipc-rpc服务
  --ipcpath              指定ipc socket/pipe文件目录(明确指定路径)
  --rpccorsdomain value  指定一个可以接收请求来源的以逗号间隔的域名列表(浏览器访问的话,要强制指定该选项)
  --jspath loadscript    javascript根目录用来加载脚本 (default: ".")
  --exec value           执行javascript声明
  --preload value        指定一个可以预加载到控制台的javascript文件,其中包含一个以逗号分隔的列表

我们在执行以上启动rpc命令时可以同时指定网络,指定节点,指定端口,指定可接收域名,甚至可以同时打开一个console,这也并不产生冲突。

geth --rpc --rpcaddr <ip> --rpcport <portnumber>

我们可以指定监听地址以及端口,如果不谢rpcaddr和rpcport的话,就是默认的localhost:8545。

geth --rpc --rpccorsdomain "localhost:3000"

如果你要使用浏览器来访问的话,就要强制指定rpccorsdomain选项,否则的话由于javascript调用的同源限制,请求会失败。

admin.startrpc(addr, port)

如果已进入geth console,也可以通过这条命令添加地址和端口。

postman,http请求api

postman是一个可以用来测试各种http请求的客户端工具,它还有其他很多用途,但这里只用它来测试上面的http-rpc服务。
技术分享图片

看图说话,我们指定了请求地址端口,指定了http post请求方式,设置好请求为原始json文本,请求内容为:

{"jsonrpc":"2.0","method":"web3_clientversion","params":[],"id":67}

是用来请求服务器当前web3客户端版本的,然后点击"send",得到请求结果为:

{
    "jsonrpc": "2.0",
    "id": 67,
    "result": "geth/v0.0.1-stable-930fa051/linux-amd64/go1.9.2"
}

以太坊go源码调用rpc

我们就以最常用的api:eth_getbalance为例,它的参数要求为:

parameters
- data, 20 bytes - address to check for balance.
- quantity|tag - integer block number, or the string "latest", "earliest" or "pending", see the default block parameter

该api要求的参数:

  • 第一个参数为需检查余额的地址
  • 第二个参数为整数区块号,或者是字符串“latest","earliest"以及"pending"指代某个特殊的区块。

在go-ethereum项目中查找到使用位置ethclient/ethclient.go:

func (ec *client) balanceat(ctx context.context, account common.address, blocknumber *big.int) (*big.int, error) {
    var result hexutil.big
    err := ec.c.callcontext(ctx, &result, "eth_getbalance", account, toblocknumarg(blocknumber))
    return (*big.int)(&result), err
}
func (ec *client) pendingbalanceat(ctx context.context, account common.address) (*big.int, error) {
    var result hexutil.big
    err := ec.c.callcontext(ctx, &result, "eth_getbalance", account, "pending")
    return (*big.int)(&result), err
}

结合上面的rpc api和下面的go源码的调用,可以看到在go语言中的调用方式:要使用客户端指针类型变量调用到上下文call的方法,传入第一个参数为上下文实例,第二个参数为一个hexutil.big类型的结果接收变量的指针,第三个参数为调用的rpc的api接口名称,第四个和第五个为该api的参数,如上所述。

  • 跟踪到ec.c.callcontext,callcontext方法是ec.c对象的。
 client defines typed wrappers for the ethereum rpc api.
type client struct {
    c *rpc.client
}

可以看到ethclient/ethclient.go文件中将原rpc/client.go的client结构体进行了一层包裹,这样就可以区分出来属于ethclient的方法和底层rpc/client的方法。下面贴出原始的rpc.client的结构体定义:

 client represents a connection to an rpc server.
type client struct {
    idcounter   uint32
    connectfunc func(ctx context.context) (net.conn, error)
    ishttp      bool

     writeconn is only safe to access outside dispatch, with the
     write lock held. the write lock is taken by sending on
     requestop and released by sending on senddone.
    writeconn net.conn

     for dispatch
    close       chan struct{}
    didquit     chan struct{}                   closed when client quits
    reconnected chan net.conn                   where write/reconnect sends the new connection
    readerr     chan error                      errors from read
    readresp    chan []*jsonrpcmessage          valid messages from read
    requestop   chan *requestop                 for registering response ids
    senddone    chan error                      signals write completion, releases write lock
    respwait    map[string]*requestop           active requests
    subs        map[string]*clientsubscription  active subscriptions
}

ethclient经过包裹以后,可以使用本地client变量调用rpc.client的指针变量c,从而调用其callcontext方法:

func (c *client) callcontext(ctx context.context, result interface{}, method string, args ...interface{}) error {
    msg, err := c.newmessage(method, args...)  看来callcontext还不是终点,todo:进到newmessage方法内再看看。
     结果处理
    if err != nil {
        return err
    }
     requestop又一个结构体,封装响应参数的,包括原始请求消息,响应信息jsonrpcmessage,jsonrpcmessage也是一个结构体,封装了响应消息标准内容结构,包括版本,id,方法,参数,错误,返回值,其中rawmessage在go源码位置json/stream.go又是一个自定义类型,属于go本身封装好的,类型是字节数组[]byte,也有自己的各种功能的方法。
    op := &requestop{ids: []json.rawmessage{msg.id}, resp: make(chan *jsonrpcmessage, 1)}

     通过rpc不同的渠道发送响应消息:这些渠道在上面命令部分已经介绍过,有http,webservice等。
    if c.ishttp {
        err = c.sendhttp(ctx, op, msg)
    } else {
        err = c.send(ctx, op, msg)
    }
    if err != nil {
        return err
    }

     todo:对wait方法的研究
     对wait方法返回结果的处理
    switch resp, err := op.wait(ctx); {
    case err != nil:
        return err
    case resp.error != nil:
        return resp.error
    case len(resp.result) == 0:
        return errnoresult
    default:
        return json.unmarshal(resp.result, &result) 顺利将结果数据编出
    }
}

先看wait方法,它仍旧在rpc/client.go中:

func (op *requestop) wait(ctx context.context) (*jsonrpcmessage, error) {
    select {
    case <-ctx.done():
        return nil, ctx.err()
    case resp := <-op.resp:
        return resp, op.err
    }
}

select的使用请参考这里。继续正题,进入ctx.done(),done属于go源码context/context.go:

 see blog.golang.org/pipelines for more examples of how to use
 a done channel for cancelation.
done() <-chan struct{}

想知道done()咋回事,请转到我写的另一篇博文go并发模式:管道与取消,那里仔细分析了这一部分内容。

从上面的源码分析我感觉go语言就是一个网状结构,从一个结构体跳进另一个结构体,它们之间谁也不属于谁,谁调用了谁就可以使用,没有显式继承extends和显式实现implements,go就是不断的封装结构体,然后增加该结构体的方法,有时候你甚至都忘记了自己程序的结构体和go源码封装的结构体之间的界限。这就类似于面向对象分析的类,定义一个类,定义它的成员属性,写它的成员方法。

web3与rpc的关系

这里再多啰嗦一句,重申一下web3和rpc的关系:

to make your app work on ethereum, you can use the web3 object provided by the web3.js library. under the hood it communicates to a local node through rpc calls. web3.js works with any ethereum node, which exposes an rpc layer.

翻译过来就是为了让你的api工作在以太坊,你可以使用由web3.js库提供的web3对象。底层通过rpc调用本地节点进行通信。web3.js可以与以太坊任何一个节点通信,这一层就是暴露出来的rpc层。

开发自己的api

设定一个小需求:就是将余额数值乘以指定乘数,这个乘数是由另一个接口的参数来指定的。

在ethapi中加入

var rateflag uint64 = 1
 start forking command.
 rate is the fork coin‘s exchange rate.
func (s *publicblockchainapi) forking(ctx context.context, rate uint64) (uint64) {
     attempt: store the rate info in context.
     context.withvalue(ctx, "rate", rate)
    rateflag = rate
    rate = rate + 1
    return rate
}

然后在ethclient中加入

 forking tool‘s client for the ethereum rpc api
func (ec *client) forkingat(ctx context.context, account common.address, rate uint64)(uint64, error){
    var result hexutil.uint64
    err := ec.c.callcontext(ctx, &result, "eth_forking", account, rate)
    return uint64(result), err
}

保存,make geth编译,然后在节点目录下启动

geth --testnet --rpc console --datadir node0

然后进入到postman中测试,可以看到
技术分享图片
乘数已经改为3(输出4是为了测试,实际上已在局部变量rateflag保存了乘数3)
然后我们再发送请求余额测试,
技术分享图片
可以看到返回值为一串16进制数,通过转换结果为:417093750000000000000,我们原始余额为:139031250000000000000,正好三倍。

rpc客户端

我们上面已经在rpc服务端对api进行了增加,而客户端调用采用的是postman发送post请求。而rpc客户端在以太坊实际上有两种:一个是刚才我们实验的,在网页中调用json-rpc;另一种则是geth console的形式,而关于这种形式,我还没真正搞清楚它部署的流程,只是看到了在源代码根目录下build/_workspace会在每一次make geth被copy进去所有的源码作为编译后环境,而我修改了源码文件,_workspace下文件,均未生效,可能还存在一层运行环境,我并没有修改到。但这无所谓了,因为实际应用中,我们很少去该console的内容,直接修改web3.js引入到网页即可。下面介绍一下配合上面自己的api,如何修改web3.js文件:

上面讲过了web3.js的结构,是一个node.js的module结构,因此我们先决定将这个api放到eth对象下,检查eth对应的id为38,找到对象体,在methods中增加对应api调用操作,

var forking = new method({
    name: ‘forking‘,
    call: ‘eth_forking‘,
    params: 1,
    inputformatter: [null],
    outputformatter: formatters.outputbignumberformatter
});

然后在对象体返回值部分将我们新构建的method添加进去,

return [
    forking,
    ...

改好以后,我们将该文件引用到页面中去,即可通过web3.eth.forking(3)进行调用了。

总结

本文介绍了rpc的概念,rpc的流行框架,以太坊使用的rpc框架为json-rpc。接着描述了如何启动json-rpc服务端,然后使用postman来请求json-rpc服务端api。通过这一流程,我们仔细分析并跟踪了源码中的实现,抽丝剥茧,从最外层的json-rpc的调用规范到源码中外层封装的引用,到内部具体实现,期间对各种自定义结构体进行了跟踪研究,直到go源码库中的结构体,研究了服务端从接收客户端请求到发送响应的过程。最后我们仔细研究了web3.js文件的结构并且做了一个小实验,从服务端到客户端模仿者增加了一个自定义的api。希望本文对您有所帮助。

更多文章请转到醒者呆的博客园

以太坊rpc机制与api实例

标签:exec   ide   register   字节   line   hex   func   忘记   hapi   

原文地址:www.cnblogs.com/evsward/p/ether-rpc.html

(1)
(2)
   
举报
评论 一句话评论(0
0条  
登录后才能评论!
2014 mobileinhere.cn 版权所有 京icp备13008772号-2
华人娱乐注册