以太坊RPC

以太坊提供了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会很麻烦,所以最方便的还是调用已有的接口。

使用POSTMAN

如果是做查询区块号为18的区块,则body是
{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x12",true],"id":1}

参考文献:
ethereum/wiki/JSON RPC
以太坊RPC机制与API实例

你可能感兴趣的:(以太坊RPC)