比特幣入門之使用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。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息