钱包节点

检测交易就需要调用比特币钱包的RPC接口了,所以我们首先要部署一个比特币钱包节点。官方的节点在https://bitcoin.org/en/bitcoin-core/。但是为了之后同时支持USDT之类的比特币代币,我们这里使用Omni COREOmni COREbitcoin-core是兼容了,并且bitcoin-coreOmni CORE的子集。

钱包节点RPC接口

RPC接口的说明文件可以从这里找到https://bitcoin.org/en/developer-reference#bitcoin-core-apis

例如获取最新的区块数

GetBlockCount

The getblockcount RPC returns the number of blocks in the longest blockchain.

Parameters: none

Result

Name Type Presence Description
result number (int) Required(exactly 1) The current block count

Example

bitcoin-cli getblockcount

那么用Postman请求实际上是这样的请求

使用Golang调用钱包RPC接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var client *gorequest.SuperAgent
var rpcURI string

// StRpcRespError rpc返回错误信息
type StRpcRespError struct {
	Code    int64  `json:"code"`
	Message string `json:"message"`
}

func (e *StRpcRespError) Error() string {
	return fmt.Sprintf("%d %s", e.Code, e.Message)
}

// StRpcReq rpc请求信息
type StRpcReq struct {
	Jsonrpc string        `json:"jsonrpc"`
	ID      string        `json:"id"`
	Method  string        `json:"method"`
	Params  []interface{} `json:"params"`
}

// StRpcResp rpc通用返回字段
type StRpcResp struct {
	ID    string          `json:"id"`
	Error *StRpcRespError `json:"error"`
}

// InitClient 初始化客户端
func InitClient(omniRPCHost, omniRPCUser, omniRPCPwd string) {
	rpcURI = omniRPCHost
	client = gorequest.New().SetBasicAuth(omniRPCUser, omniRPCPwd).Timeout(time.Minute * 5)
}

// doReq 调用http请求
func doReq(method string, arqs []interface{}, resp interface{}) error {
	_, body, errs := client.Post(rpcURI).Send(StRpcReq{
		Jsonrpc: "1.0",
		ID:      hcommon.GetUUIDStr(),
		Method:  method,
		Params:  arqs,
	}).EndBytes()
	if errs != nil {
		return errs[0]
	}
	err := json.Unmarshal(body, resp)
	if err != nil {
		return err
	}
	return nil
}

// RpcGetBlockCount 获取block number
func RpcGetBlockCount() (int64, error) {
	resp := struct {
		StRpcResp
		Result int64 `json:"result"`
	}{}
	err := doReq(
		"getblockcount",
		nil,
		&resp,
	)
	if err != nil {
		return 0, err
	}
	if resp.Error != nil {
		return 0, resp.Error
	}
	return resp.Result, nil
}

检测交易流程

用时序图展示这个流程就是

比较麻烦的是,vin中没有包含地址信息,所以我们还需要根据vin的txid再查询一遍rawtransaction。为了减少请求,我们把这个数据存入map,如果有则直接读取,没有便请求rpc。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
vinTxMap := make(map[string]*omniclient.StTxResult)
rpcVinTx, ok := vinTxMap[vin.Txid]
if !ok {
    rpcVinTx, err = omniclient.RpcGetRawTransactionVerbose(vin.Txid)
    if err != nil {
        hcommon.Log.Errorf("err: [%T] %s", err, err.Error())
        return
    }
    vinTxMap[vin.Txid] = rpcVinTx
}

整合代码

具体实现的代码已经传到了github https://github.com/moremorefun/go-dc-wallet

功能入口在cmd/btc_block_seek/main.go