上一篇文章介绍了以太坊的基础知识,我们了解了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的话,就是默认的http://localhost:8545。

geth --rpc --rpccorsdomain "http://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 https://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实例的更多相关文章

  1. 智能合约开发solidity编程语言开发一个以太坊应用区块链投票实例

    智能合约开发用solidity编程语言部署在以太坊这个区块链平台,本文提供一个官方实战示例快速入门,用例子深入浅出智能合约开发,体会以太坊构建去中心化可信交易技术魅力.智能合约其实是"执行合 ...

  2. 以太坊go-ethereum客户端JSON-RPC API调用(一)

    前几篇博客主要介绍了go-ethereum客户端不同环境的搭建,今天这篇博客是建立在前几排博客的基础上.当搭建完成之后,我们可以通过各种方式与节点进行交互(JavaScript Console.JSO ...

  3. C#以太坊基础入门

    在这一部分,我们将使用C#开发一个最简单的.Net控制台应用,来接入以太坊节点,并打印 所连接节点旳版本信息.通过这一部分的学习,你将掌握以下技能: 如何使用节点仿真器 如何在命令行访问以太坊节点 如 ...

  4. 以太坊web3开发初步学习

    以太坊web3开发初步学习 此文是对https://learnblockchain.cn/2018/04/15/web3-html/的学习再理解. 以太坊智能合约通过使用web3.js前端和智能合约交 ...

  5. golang学习笔记19 用Golang实现以太坊代币转账

    golang学习笔记19 用Golang实现以太坊代币转账 在以太坊区块链中,我们称代币为Token,是以太坊区块链中每个人都可以任意发行的数字资产.并且它必须是遵循erc20标准的,至于erc20标 ...

  6. 以太坊go-ethereum常见问题汇总

    (1)什么是 Ethereum? 以太坊是一个分散的智能合同平台,由Ether的加密货币提供支持. (2) 听说过以太坊,但什么是Geth,Mist,Ethminer,Mix? Geth: 以太坊节点 ...

  7. 以太坊智能合约开发,Web3.js API 中文文档 ethereum web3.js入门说明

    以太坊智能合约开发,Web3.js API 中文文档 ethereum web3.js入门说明 为了让你的Ðapp运行上以太坊,一种选择是使用web3.js library提供的web3.对象.底层实 ...

  8. rpc接口调用以太坊智能合约

    rpc接口调用以太坊智能合约 传送门: 柏链项目学院   在以太坊摸爬滚打有些日子了,也遇到了各种各样的问题.这几天主要研究了一下如何通过rpc接口编译.部署和调用合约.也遇到了一些困难和问题,下面将 ...

  9. 以太坊预言机与WEB API(原创,转载请说明原址)

    什么是预言机? 从链外获得数据,提供区块链与现实世界事件之间的连接,提供外部信息的平台 预言机自身也是一种智能合约,它允许区块链连接到任何现有的API 是这个预言机去调用各种 WEB API的接口 这 ...

随机推荐

  1. 如果没有UX经验,如何创建个人UX作品集?

    以下内容由Mockplus团队翻译整理,仅供学习交流,Mockplus是更快更简单的原型设计工具. 一直以来,这是设计行业的悖论. 当今,许多活跃于我们用户体验行业的专业人士在开始个人职业生涯时都面临 ...

  2. bzoj 3996: [TJOI2015]线性代数

    Description 给出一个N*N的矩阵B和一个1*N的矩阵C.求出一个1*N的01矩阵A.使得 D=(A*B-C)*A^T最大.其中A^T为A的转置.输出D Input 第一行输入一个整数N,接 ...

  3. JavaScript中的数组对象遍历、读写、排序等操作

    以百度前端技术学院的js任务三为例,复习一下关于js数组的几个点 题目 <!DOCTYPE> <html> <head> <meta charset=&quo ...

  4. jquery获取焦点和失去焦点事件代码

    input失去焦点和获得焦点 鼠标在搜索框中点击的时候里面的文字就消失了. 我们在做网站的时候经常会用到搜索框的获得焦点和失去焦点的事件,因为懒,每次都去写非常的烦,于是就一劳永逸,遇到类似情况就来调 ...

  5. HTTPS从认识到线上实战全记录

    前言 关于HTTPS,基本上你想知道的都在这里了.本文原标题<HTTPS原理与实践>,下图是本文配套PPT的目录截图: [TOC] 原理篇 认识HTTPS 先说一下,本文可能有些地方由于描 ...

  6. JMeter常见错误解决方法

    1.Windows 平台,双击 jmeter/bin 目录下 jmeter.bat 文件,jmeter 无法启动且报错如下: 此问题是没有配置 jdk 环境变量所致,配置好 jdk 环境变量即可. 2 ...

  7. javamelody 使用

    javamelody 扩展API如何获取监控参数 https://github.com/javamelody/javamelody/wiki/ExternalAPI#json

  8. 微服务时代TestOps工程师学习总结

    TestOps很新鲜,也是近期衍生的新型职位.那TestOps主要目的是推动整个研发体系与发布体系更多在质量方面.可以这样理解DevOps是从研发推动配合运维和测试,而TestOps是从测试角度推动研 ...

  9. PHP 微信公众号-创建菜单-配置

    1.服务号 2.基本配置 注意: URL: 确保能访问到你对应的文件 Token:随意设置,但是要与文件里的一致 3.网页授权 注意:填写网站域名 4.更具实际需求创建菜单

  10. vue2.0 样式表引入的方法 css sass less

    在引入样式之前,首先要了解static.assets两个文件夹的区别. 从字面上可以看出,static用来存放静态文件,assets用来存放资源文件: static存放的文件不会被编译,打包后直接赋值 ...