以太坊提供了RPC服务,可以在geth启动时通过参数设置
geth启动选项参数
--rpc 启动HTTP-RPC服务(基于HTTP的)
--ws 启动WS-RPC服务(基于WebService的)
--rpcapi value 指定需要调用的HTTP-RPC API接口,默认只有eth,net,web3
--rpcport value HTTP-RPC服务器监听端口(default: 8545)
--rpcport value HTTP-RPC服务器监听端口(default: 8545)
例子:geth --rpc --rpcapi "db,eth,net,web3,personal"
执行RPC调用的方式有很多,可以使用web3提供的接口、直接发送Json请求(缺点是拼json会很麻烦)、使用go-ethereum/ethclient包提供的函数(缺点是只有eth接口)、也可以自己定义接口来调用。下面代码是使用go-ethereum/ethclient包中的函数的例子。
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/mobile"
)
func main() {
// NewEthereumClient函数只是创建一个EthereumClient结构,并设置了HTTP连接的一些参数如的head的一些属性,并没有节点建立连接
cli, err := geth.NewEthereumClient("http://127.0.0.1:8545")
if err != nil {
fmt.Printf("create new ethereum rpc client err:%s\n", err.Error())
} else {
fmt.Println("create new ethereum rpc client success")
}
eth_ctx := geth.NewContext()
block, err2 := cli.GetBlockByNumber(eth_ctx, 18)
fmt.Printf("ethereum mobile Context:%+v\n", eth_ctx)
if err2 != nil {
fmt.Printf("get block err:%s\n", err2.Error())
} else {
fmt.Printf("block:%+v\n", block)
}
}
连的节点是本地运行的私有链,并且在go-ethereum源码中加了一些日志,执行结果:
mylog:DialContext:u:{Scheme:http Opaque: User: Host:127.0.0.1:8545 Path: RawPath: ForceQuery:false RawQuery: Fragment:};
mylog:u.Scheme:http
create new ethereum rpc client success
mylog:JSON-RPC: Client CallContext
mylog:Client.isHTTP:true
ethereum mobile Context:&{context:0xc4200ac008 cancel:}
block:Block(#18): Size: 650.00 B {
MinerHash: fd55c05ae10a5b0159b3c2d5803c6aa9469c95f5f063b9c400a2c36b49616ab3
Header(84b2cfd65e3197bdfe3f748ecebb040953af5eb73a05d8595757cf42cb40a492):
[
ParentHash: 7892a0b31d50d67ae20d4a7ec5c24a6fe85f2f264e9f1639aa2388081305a0bd
UncleHash: 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347
Coinbase: bdc61c81f67983288a6c375a884661edc77286d0
Root: 0f30637bfc5bd6e123c6a0c38bdc743c94050626a984f9943eaf38367100b3e3
TxSha 354d185cfa88e50f1a425e5b89500122e4445e9ec737e7a18cdd61b9350ab72b
ReceiptSha: a769d28981014fb6095462148a6300cd0b43fa050d75eb6f5b7595cfd13136bb
Bloom: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Difficulty: 131072
Number: 18
GasLimit: 131877941
GasUsed: 21000
Time: 1527044372
Extra: ׃��geth�go1.10�darwin
MixDigest: 70c2bb422b1b834d5173d279e508ffee9dada454650fc3cf63e95deb3073cf32
Nonce: 58b7495f112ccac2
]
Transactions:
[
TX(57a3b17f84358098b728fc0f70f0697f175f8ba00d386c88eac0815b3afd6aad)
Contract: false
From: 2154bdd7070c99d1a25ff589a08b01dfd6eb65de
To: bdc61c81f67983288a6c375a884661edc77286d0
Nonce: 0
GasPrice: 0x430e23400
GasLimit 0x15f90
Value: 0xde0b6b3a7640000
Data: 0x
V: 0x41
R: 0x45d4952c0190373c56e62ad15e54db54c0246385371b23c70bab4126b51927f8
S: 0x618e4bb76a36482254352d7e5096c0dff4c1f495218d57c874fc3d8153915ea4
Hex: f86d80850430e2340083015f9094bdc61c81f67983288a6c375a884661edc77286d0880de0b6b3a76400008041a045d4952c0190373c56e62ad15e54db54c0246385371b23c70bab4126b51927f8a0618e4bb76a36482254352d7e5096c0dff4c1f495218d57c874fc3d8153915ea4
]
Uncles:
[]
}
分析:
go-ethereum/mobile包是发起RPC请求的客户端直接使用的包。
该包中有EthereumClient
结构提供了Ethereum API的接入。
// EthereumClient provides access to the Ethereum APIs.
type EthereumClient struct {
client *ethclient.Client
}
ethclient.Client在ethclient包中,包装了rpc.Client,rpc.Client代表与RPC服务的一个连接。
// Client defines typed wrappers for the Ethereum RPC API.
type Client struct {
c *rpc.Client
}
RPC请求客户端在使用时,首先传入想要接入的节点的url作为参数,调用mobile包中的NewEthereumClient函数。创建了EthereumClient实例,并与节点建立连接。建立的RPC连接有三种形式:HTTP、WebSocket、IPC,当传入http://127.0.0.1:8545
时,建立的是HTTP连接。
// NewEthereumClient connects a client to the given URL.
func NewEthereumClient(rawurl string) (client *EthereumClient, _ error) {
rawClient, err := ethclient.Dial(rawurl)
return &EthereumClient{rawClient}, err
}
设置HTTP连接的参数会调用rpc包http.go文件中的DialHTTPWithClient函数。
// DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP
// using the provided HTTP Client.
func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) {
req, err := http.NewRequest(http.MethodPost, endpoint, nil)
if err != nil {
return nil, err
}
// Content-Type和Accept是application/json,即发送的数据类型和接收的数据类型都是json
req.Header.Set("Content-Type", contentType)
req.Header.Set("Accept", contentType)
initctx := context.Background()
return newClient(initctx, func(context.Context) (net.Conn, error) {
return &httpConn{client: client, req: req, closed: make(chan struct{})}, nil
})
}
通过HTTP来做JSON-RPC调用时,需要一个geth.Context实例,通过调用mobile包中的NewContext函数,创建一个空的geth.Context实例。
// NewContext returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming requests.
func NewContext() *Context {
return &Context{
context: context.Background(),
}
}
mobile包中封装了请求区块、区块头、交易等函数,这些函数调用ethclient包中的相关函数,再调用更底层rpc包中封装的函数。
即mobile包-->ethclient包-->rpc包。如mobile包中根据区块号查找区块的函数最后会调用rpc包中的CallContext函数。
// CallContext扮演JSON-RPC调用角色
// CallContext performs a JSON-RPC call with the given arguments. If the context is
// canceled before the call has successfully returned, CallContext returns immediately.
//
// The result must be a pointer so that package json can unmarshal into it. You
// can also pass nil, in which case the result is ignored.
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
fmt.Printf("mylog:JSON-RPC: Client CallContext\n")
msg, err := c.newMessage(method, args...)
if err != nil {
return err
}
op := &requestOp{ids: []json.RawMessage{msg.ID}, resp: make(chan *jsonrpcMessage, 1)}
fmt.Printf("mylog:Client.isHTTP:%+v\n",c.isHTTP)
if c.isHTTP {
err = c.sendHTTP(ctx, op, msg)
} else {
err = c.send(ctx, op, msg)
}
if err != nil {
return err
}
// dispatch has accepted the request and will close the channel it when it quits.
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)
}
}
使用POSTMAN
使用POSTMAN发送请求时,注意设置下Content-type和Accept。
body是{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":67}
这种方式虽然直接,但是自己拼json会很麻烦,所以最方便的还是调用已有的接口。
如果是做查询区块号为18的区块,则body是
{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x12",true],"id":1}
参考文献:
ethereum/wiki/JSON RPC
以太坊RPC机制与API实例