比特币入门之使用PRC应用开发接口

1、RPC API概述html

比特币定义了RPC API来容许第三方应用经过节点软件访问比特币网络。 事实上,bitcoin-cli就是经过这个接口来实现其功能的,也就是说, 咱们能够在本身的C#程序中彻底实现bitcoin-cli的功能。node

JSON RPC采用JSON语法表示一个远程过程调用(Remote Procedure Call) 的请求与应答消息。例如对于getbalance调用,请求消息与应答消息的格式 示意以下:git

在请求消息中使用method字段声明要调用的远程方法名,使用params字段 声明调用参数列表;消息中的jsonrpc字段声明所采用的JSON RPC版本号, 而可选的id字段则用于创建响应消息与请求消息之间的关联,以便客户端 在同时发送多个请求后能正确跟踪其响应。github

响应消息中的result字段记录了远程调用的执行结果,而error字段 则记录了调用执行过程当中出现的错误,id字段则对应于请求消息中的同名 字段值。json

JSON RPC是传输协议无关的,但基于HTTP的普遍应用,节点一般都会提供基于 HTTP协议的实现,也就是说将JSON PRC消息做为HTTP报文的内容载荷进行传输:windows

bitcoind在不一样的运行模式下,会在不一样的默认端口监听HTTP RPC API请求:api

  • 主网模式:8332
  • 测试网模式:18332
  • Regtest开发模式:18443

能够在bitcoind的配置文件中使用rpcbind选项和rpcport选项修改监听端结点, 例如,设置为本地7878端口:数组

rpcbind=127.0.0.1
rpcport=7878

2、使用curl测试RPC API网络

curl是一个支持URL语法的多协议命令行数据传输工具,能够从 官网下载:app

curl支持HTTP、FTP等多种协议,所以咱们可使用它来验证节点基于HTTP旳rpc接口 是否正常工做。例如,使用以下的命令访问节点旳getnetworkinfo接口:

~$ curl -X POST -d '{
> "jsonrpc":"1.0",
> "method":"getnetworkinfo",
> "params":[],
> "id":"123"
> }'  http://user:123456@localhost:18443

curl提供了不少选项用来定制HTTP请求。例如,可使用-X选项声明HTTP请求 的方法,对于JSON RPC来讲,咱们老是使用POST方法;-d选项则用来声明请求中包含 的数据,对于JSON RPC调用,这部分就是请求消息,例如咱们按照getnetworkinfo调用的 要求进行组织便可;命令的最后,也就是RPC调用消息的发送目的地址,即节点RPC API的访问URL。

默认状况下curl返回的结果是没有格式化的JSON字符串,对机器友好,但并不适合人类查阅:

若是你但愿结果显示的更友好一些,能够级联一个命令行的json解析工具例如jq

~$ curl -X POST -s -d '{...}' http://user:123456@localhost:18443 | jq

jq是一个轻量级的命令行JSON处理器,你能够从官网 下载它。

curl -X POST -s -d '{"method":"getnetworkinfo","params":[],"id":123,"jsonrpc":"1.0"}' \
      http://user:123456@localhost:18443 | jq

3、在C#代码中访问RPC API

天然,咱们也能够在C#代码中来调用节点旳JSON RPC开发接口,能够借助于一个 http协议封装库来执行这些发生在HTTP之上的远程调用,例如.NET内置的HttpClient:

例如,下面的代码使用HttpClient调用比特币节点的getnetworkinfo接口:

 首先下载bitcoin: https://bitcoin.org/zh_CN/download,若是使用主网络须要同步240G的数据,这里在本地以私链模式运行。私链模式运行也比较容易配置,只须要在bitcoin.conf中配置regtest=1。在windows下,bitcoin.conf的默认路径为%APPDATA%\bitcoin\bitcoin.conf。个人电脑在C:\Users\Administrator\AppData\Roaming\Bitcoin目录下。默认状况下bitcoind并不会自动建立上述路径下的bitcoin.conf配置文件,所以须要 自行制做一份放入上述目录。若是你没有现成的配置文件可用,能够从github拷贝一份:https://github.com/bitcoin/bitcoin/blob/master/share/examples/bitcoin.conf。关于bitcoin.conf的配置能够参考个人另外一博客。

这里regtest=1使用私链模式,server=1启动rpc,rpcuser=usertest、rpcpassword=usertest 设置用户名、密码。

#testnet=0
regtest=1
proxy=127.0.0.1:9050
#bind=<addr>
#whitebind=<addr>
#addnode=69.164.218.197
#addnode=10.0.0.2:8333
#connect=69.164.218.197
#listen=1
#maxconnections=
server=1
#rpcbind=<addr>
rpcuser=usertest
rpcpassword=usertest
#rpcclienttimeout=30
#rpcallowip=10.1.1.34/255.255.255.0
#rpcallowip=1.2.3.4/24
#rpcallowip=2001:db8:85a3:0:0:8a2e:370:7334/96
#rpcport=8332
#rpcconnect=127.0.0.1
#txconfirmtarget=n
#paytxfee=0.000x
#keypool=100
#prune=550
#min=1
#minimizetotray=1
View Code

启动以后以下图所示:会有一个regtest标记。

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace RPCHttpClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.Run(async () =>
            {
                HttpClient httpClient = new HttpClient();

                byte[] authBytes = Encoding.ASCII.GetBytes("usertest:usertest");
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authBytes));

                string payload = "{\"jsonrpc\":\"1.0\",\"method\":\"getnetworkinfo\",\"params\":[],\"id\":7878}";

                StringContent content = new StringContent(payload, Encoding.UTF8, "application/json");
                HttpResponseMessage rsp = await httpClient.PostAsync("http://127.0.0.1:18443", content);

                string ret = await rsp.Content.ReadAsStringAsync();
                Console.WriteLine(ret);
                Console.ReadLine();
            }).Wait();
        }
    }
}
View Code

在上面的代码中,咱们首先实例化一个HttpClient对象并设置HTTP验证信息,而后调用该对象 的PostAsync()方法向节点旳RPC端口发送请求消息便可完成调用。

 4、序列化与反序列化

在应用逻辑里直接拼接RPC请求字符串,或者直接解析RPC响应字符串,都不是件使人舒心的事情, 咱们须要改进这一点。

更干净的办法是使用数据传输对象(Data Transfer Object)来 隔离这个问题,在DTO层将 C#的对象序列化为Json字符串,或者从Json字符串 反序列化为C#的对象,应用代码只须要操做C#对象便可。

咱们首先定义出JSON请求与响应所对应的C#类。例如:

如今咱们获取比特币网络信息的代码能够不用直接操做字符串了:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;

namespace RPCHttpDTO
{
    class RpcRequestMessage
    {
        [JsonProperty("id")]
        public int Id;

        [JsonProperty("method")]
        public string Method;

        [JsonProperty("params")]
        public object[] Parameters;

        [JsonProperty("jsonrpc")]
        public string JsonRPC = "1.0";

        public RpcRequestMessage(string method, params object[] parameters)
        {
            Id = Environment.TickCount;
            Method = method;
            Parameters = parameters;
        }
    }
     

    class RpcResponseMessage
    {
        [JsonProperty("id")]
        public int Id { get; set; }

        [JsonProperty("result")]
        public object Result { get; set; }

        [JsonProperty("jsonrpc")]
        public string JsonRPC { get; set; }
    }
    
}
View Code
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace RPCHttpDTO
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.Run(async () =>
            {
                HttpClient httpClient = new HttpClient();

                byte[] authBytes = Encoding.ASCII.GetBytes("usertest:usertest");
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authBytes));

                RpcRequestMessage reqMsg = new RpcRequestMessage("getnetworkinfo");
                Console.WriteLine("=> {0}", reqMsg.Method);

                string payload = JsonConvert.SerializeObject(reqMsg);


                StringContent content = new StringContent(payload, Encoding.UTF8, "application/json");
                HttpResponseMessage rsp = await httpClient.PostAsync("http://localhost:18443", content);

                string ret = await rsp.Content.ReadAsStringAsync();
                RpcResponseMessage rspMsg = JsonConvert.DeserializeObject<RpcResponseMessage>(ret);
                Console.WriteLine("<= {0}", rspMsg.Result);
                Console.ReadLine();
            }).Wait();
        }
    }
}
 
    
View Code

5、使用JSON RPC封装库

 除了直接使用HTTP协议库来访问比特币节点,在开源社区中也有一些直接针对 比特币RPC协议的封装,例如MetacoSA的NBitcoin

NBitcoin是.NET平台上最完整的比特币开发库,实现了不少相关的比特币改进建议(Bitcoin Improvement Proposal)。 与RPC协议封装相关的类主要在NBitcoin.RPC命名空间下,入口类为RPCClient, 它表明了对一个的比特币RPC访问端结点的协议封装。

例如,下面的代码建立一个指向本机的私有链节点RPC的RPCClient实例:

//using NBitcon.RPC;
string auth = "user:123456";        //rpc接口的帐号和密码
string url = "http://localhost:18443"     //本机私有链的默认访问端结点
Network network  = Network.RegTest;      //网络参数对象
RPCClient client = new RPCClient(auth,url,network);  //实例化

比特币有三个不一样的网络:主网、测试网和私有链,分别有一套对应的网络参数。 在NBitcoin中,使用Network类来表征比特币网络,它提供了三个静态属性分别 返回对应于三个不一样网络的Network实例。在实例化RPCClient时须要传入与节点 对应的网络参数对象,例如当链接的节点是主网节点时,须要传入Network.Main, 而当须要本地私有链节点时,就须要传入Network.RegTest

一旦实例化了RPCClient,就可使用其SendCommand()SendCommandAsync() 方法调用比特币节点的RPC接口了。容易理解,这两个方法分别对应于同步调用 和异步调用,除此以外,二者是彻底一致的。

例如,下面的代码使用同步方法调用getnetworkinfo接口返回节点软件版本号:

//using Newtonsoft.Json.Linq;
RPCRequest req = new RPCRequest{           //RPC请求对象
  Method = "getnetworkinfo",
  Params = new object[]{}
};
RPCResponse rsp = client.SendCommand(req); //返回RPC响应对象
Console.WriteLine(rsp.ResultString); //ResultString返回原始的响应字符串

SendCommand/SendCommandAsync的重载

若是你注意到实例化RPCRequest对象最重要的是Method和Params这两个属性,就容易 理解应该有更简单的SendCommand/SendCommandAsync方法了。下面是最经常使用的一种, 只须要传入方法名和动态参数列表,不须要本身再定义RPCRequest数据:

public RPCResponse SendCommand(string commandName, params object[] parameters)

例如,下面的代码分别展现了无参和有参调用的使用方法:

client.SendCommand("getnetworkworkinfo");  //无参调用
client.SendCommand("generate",1);          //有参调用

容易理解,这个重载在内部帮咱们构建了RPCRequest对象。

从响应结果中提取数据

RPCResponse的ResultString属性返回原始的JSON响应字符串,所以从中提取 数据的一个办法就是将其转换为C#的动态对象,这是最简明直接的方法:

dynamic ret = JsonConvert.DeserializeObject(rsp.ResultString);
Console.WriteLine(ret.networks[0].name);

另外一种提取数据的方法是使用RPCResponse的Result属性,它返回一个JToken对象, 所以能够很是方便地使用JPath表达式来提取指定路径的数据。

例如,下面的代码从getnetworkinfo的响应结果中提取并显示节点启用的全部网络 接口名称:

IEnumerable<JToken> names = rsp.Result.SelectTokens("networks[*].name"/*JPath表达式*/); 
foreach(var name in names) Console.WriteLine(name);

若是你不熟悉JToken和JPath,那么JToken的使用方法能够访问其 官网文档, 关于JPath表达式能够访问这里

 首先须要引入NBitcoin。

using NBitcoin;
using NBitcoin.RPC;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks;

namespace RPCNbitcoin
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.Run(async () => {
                RPCClient client = new RPCClient("usertest:usertest", "http://localhost:18443", Network.RegTest);
                RPCRequest req = new RPCRequest
                {
                    Method = "getnetworkinfo",
                    Params = { }
                };
                RPCResponse rsp = await client.SendCommandAsync(req);
                dynamic ret = JsonConvert.DeserializeObject(rsp.ResultString);
                Console.WriteLine("network#0 => {0}", ret.networks[0].name);

                var names = rsp.Result.SelectTokens("networks[*].name");
                foreach (var name in names) Console.WriteLine(name);
                Console.ReadLine();
            }).Wait();
        }
    }
}
View Code

 6、NBitcoin的RPC封装完成度

在大多数状况下,使用RPCClient的SendCommand或SendCommandAsync方法, 就能够完成比特币的RPC调用工做了。考虑到比特币RPC接口自己的不稳定性, 这是万能的使用方法。

不过看起来NBitcoin彷佛是但愿在RPCClient中逐一实现RPC接口,虽然 这一任务尚未完成。例如,对于getbalance调用,其对应的方法为 GetBalance和GetBalanceAsync,所以咱们也能够采用以下的方法获取钱包余额:

Money balance = client.GetBalance();
Console.WriteLine("balance: {0} BTC", balance.ToUnit(MoneyUnit.BTC)); //单位:btc
Console.WriteLine("balance: {0} SAT", balance.Satoshi);               //单位:sat

显然,NBitcoin的预封装方法进行了额外的数据处理以返回一个Money实例, 这比直接使用SendCommand会更方便一些:

 

所以若是NBitcoin已经实现了你须要的那个RPC接口的直接封装,建议首选直接封装方法, 能够在这里 查看RCPClient的官方完整参考文档。

下表列出了部分在RPCClient中已经实现的RPC接口及对应的同步方法名,考虑到空间问题, 表中省去了异步方法名,相信这个清单会随着NBitcoin的开发愈来愈长:

分类 RPC接口 RPCClient方法 备注
P2P网络 addnode AddNode 添加/删除P2P节点地址
  getaddednodeinfo GetAddedNodeInfo 获取添加的P2P节点的信息
  getpeerinfo GetPeerInfo 获取已链接节点的信息
区块链 getblockchaininfo GteBlockchainInfo 获取区块链的当前信息
  getbestblockhash GetBestBlockHash 获取最优链的最近区块哈希
  getblockcount GetBlockCount 获取本地最优链中的区块数量
  getblock GetBlock 获取具备指定块头哈希的区块
  getblockhash GetBlockHash 获取指定高度区块的块头哈希
  getrawmempool GetRawMemPool 获取内存池中的交易ID数组
  gettxout GetTxOut 获取指定的未消费交易输出的详细信息
工具类 estimatefee EstimateFee 估算千字节交易费率
  estimatesmartfee EstimateSmartFee  
未公开 invalidateblock InvalidateBlock  
钱包 backupwallet BackupWallet 备份钱包文件
  dumpprivkey DumpPrivateKey 导出指定地址的私钥
  getaccountaddress GetAccountAddress 返回指定帐户的当前地址
  importprivkey ImportPrivKey 导入WIF格式的私钥
  importaddress ImportAddress 导入地址以监听其相关交易
  listaccounts ListAccounts 获取帐户及对应余额清单
  listaddressgroupings ListAddressGroupings 获取地址分组清单
  listunspent ListUnspent 获取钱包内未消费交易输出清单
  lockunspent LockUnspent 锁定/解锁指定的交易输出
  walletpassphrase WalletPassphrase 解锁钱包
  getbalance GetBalance 获取钱包余额
  getnewaddress GetNewAddress 建立并返回一个新的钱包地址

值得指出的是,NBitcoin采用了PASCAL命名规则来生成RPC接口对应的方法名称, 即每一个单词的首字母大写。

 

using NBitcoin;
using NBitcoin.RPC;
using System;

namespace RPCNbitcoinAdvanced
{
    class Program
    {
        static void Main(string[] args)
        {
            RPCClient client = new RPCClient("usertest:usertest", "http://localhost:18443", Network.RegTest);

            Money balance = client.GetBalance();
            Console.WriteLine("balance => {0} btc", balance);

            BitcoinAddress address = client.GetNewAddress();
            Console.WriteLine("address => {0}", address);

            uint256 txid = client.SendToAddress(address, Money.Coins(0.1m));
            Console.WriteLine("sent 0.1 btc to above address.");

            client.Generate(100);
            Console.WriteLine("mined a block.");

            UnspentCoin[] coins = client.ListUnspent(0, 9999, address);
            foreach (var coin in coins)
            {
                Console.WriteLine("unspent coin => {0} btc", coin.Amount);
            }
            Console.ReadLine();
        }
    }
}

 7、利用UTXO计算钱包余额

咱们知道,比特币都在UTXO上存着,所以容易理解,钱包的余额 应该就是钱包内全部的地址相关的UTXO的汇总:

首先查看钱包余额:

Money balance = client.getBalance();

而后使用listunspent接口列出钱包内地址相关的UTXO:

UnspentCoin[] coins = client.ListUnspent(); //listunspent接口封装方法
long amount = 0;
foreach(var coin in coins){          //累加全部utxo的金额
    amount += coin.Amount.Satoshi;   
}

ListUnspent()方法返回的结果是一个数组,每一个成员都是一个UnspentCoin 对象:

最后咱们比较一下:

if(balance.Satoshi == amount){ Console.WriteLine("verified!"); }

using NBitcoin;
using NBitcoin.RPC;
using System;
using System.Threading.Tasks;

namespace CalcBalance
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.Run(async () => {
                RPCClient client = new RPCClient("usertest:usertest", "http://localhost:18443", Network.RegTest);
                Money balance = await client.GetBalanceAsync();
                Console.WriteLine("getbalance => {0}", balance.Satoshi);
                UnspentCoin[] coins = await client.ListUnspentAsync();
                long amount = 0;
                foreach (var coin in coins)
                {
                    amount += coin.Amount.Satoshi;
                }
                Console.WriteLine("unspent acc => {0}", amount);

                if (balance.Equals(Money.Satoshis(amount))) Console.WriteLine("verified successfully!");
                else Console.WriteLine("failed to verify balance");
                Console.ReadLine();
            }).Wait();
        }
    }
}
View Code

8、让网站支持比特币支付

使用bitcoind,咱们能够很是快速地为网站增长接受比特币支付的功能:

当用户选择采用比特币支付其订单时,网站将自动提取该订单对应的 比特币地址(若是订单没有对应的比特币地址,则可使用getnewaddress建立一个), 并在支付网页中显示订单信息、支付地址和比特币支付金额。为了方便 使用手机钱包的用户,能够将支付信息以二维码的形式在页面展示出来:

用户使用比特币钱包向指定的地址支付指定数量的比特币后,便可点击 [已支付]按钮,提请网站检查支付结果。网站则开始周期性地调用节点 的getreceivedbyaddress命令来检查订单对应地址的收款状况,一旦 收到足量比特币,便可结束该订单的支付并启动用户产品或服务的交付。 默认状况下,getreceivedbyaddress将至少须要六个确认才会报告 地址收到的交易。

除了使用getreceivedbyadress命令来轮询收款交易,另外一种检查 用户支付的方法是使用bitcoind的walletnotify选项。当bitcoind检测 到钱包中的地址发生交易时,将会调用walletnotify选项设置的脚本, 并传入交易id做为参数,所以能够在脚本中进一步获取交易详细信息。 例如在下面的配置文件中,当钱包中的地址发生交易时,将触发 tx-monitor.sh脚本:

walletnofity=/var/myshop/tx-monitor.sh %s

这是一个至关朴素的方案,但很容易实现。此外,若是你须要实时进行 法币和比特币的换算,还可使用blockchain.info 提供的相关api。

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息