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
能够在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
启动以后以下图所示:会有一个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(); } } }
在上面的代码中,咱们首先实例化一个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; } } }
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(); } } }
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(); } } }
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(); } } }
8、让网站支持比特币支付
使用bitcoind,咱们能够很是快速地为网站增长接受比特币支付的功能:
当用户选择采用比特币支付其订单时,网站将自动提取该订单对应的 比特币地址(若是订单没有对应的比特币地址,则可使用getnewaddress
建立一个), 并在支付网页中显示订单信息、支付地址和比特币支付金额。为了方便 使用手机钱包的用户,能够将支付信息以二维码的形式在页面展示出来:
用户使用比特币钱包向指定的地址支付指定数量的比特币后,便可点击 [已支付]按钮,提请网站检查支付结果。网站则开始周期性地调用节点 的getreceivedbyaddress
命令来检查订单对应地址的收款状况,一旦 收到足量比特币,便可结束该订单的支付并启动用户产品或服务的交付。 默认状况下,getreceivedbyaddress
将至少须要六个确认才会报告 地址收到的交易。
除了使用getreceivedbyadress
命令来轮询收款交易,另外一种检查 用户支付的方法是使用bitcoind的walletnotify选项。当bitcoind检测 到钱包中的地址发生交易时,将会调用walletnotify选项设置的脚本, 并传入交易id做为参数,所以能够在脚本中进一步获取交易详细信息。 例如在下面的配置文件中,当钱包中的地址发生交易时,将触发 tx-monitor.sh脚本:
walletnofity=/var/myshop/tx-monitor.sh %s
这是一个至关朴素的方案,但很容易实现。此外,若是你须要实时进行 法币和比特币的换算,还可使用blockchain.info 提供的相关api。