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。