很 多刚接触区块链的开发者面临的问题,是如何上手实践基础的区块链应用开发技术,如何了解它的关键技术实践点。本文介绍如何采用Docker容器技术,快速 构建私有节点的比特币测试网络(bitcoin-testnet),并结合Node.js程序语言例子,说明如何调用比特币钱包节点提供的RPC接口服 务,实现涉及比特币区块链的具体应用功能。

安装基础环境

本 文采用Ubuntu14.04 Desktop 操作系统作为基础环境。对于常用的Microsoft Windows7 64bit桌面操作系统,可以安装Oracle VM VirtualBox虚拟机软件来进一步安装Ubuntu14.04 Desktop版操作系统。系统安装成功后,缺省带有Node.js软件,如果要查询是否已安装Node.js及相应版本的命令参考如下:

$ apt-cache policy nodejs

$ node -v

$ npm -v

如果尚未安装Node.js,可以参考下述命令进行安装:

$sudo add-apt-repository ‘deb https://deb.nodesource.com/node trusty main’

$sudo apt-get update

$sudo apt-get install nodejs

确认已安装Node.js后,可以安装对应的RPC支持库。常用的Node.js的RPC支持库有多个,我们这里选用开源项目kapitalize。

在Ubuntu终端命令行界面输入以下命令进行安装:

$npm install kapitalize

关于kapitalize开源项目的更多说明可以参考下述网址:

https://github.com/shamoons/Kapitalize

安装Docker环境

Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上。

在Ubuntu14.04操作系统上快速安装Docker运行环境的方法如下:

1)在命令行下,输入下述命令安装Docker容器支持软件。

$sudo apt-get install docker.io

2)创建软连接。

$sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker

3)查看Docker版本。

$docker –version

如看到提示信息类似“Docker version 1.6.2, build 7c8fca2”则说明你已经安装Docker成功了。

安装和运行比特币测试网络

1)下载比特币测试网络(bitcoin-testnet)的Docker镜像。

$sudo docker pull freewil/bitcoin-testnet-box

2)运行Docker镜像。

$sudo docker run -t -i -p 19001:19001 -p 19011:19011 freewil/bitcoin-testnet-box

注意:上述命令中的19001和19011是配置给两个节点提供RPC服务的端口。

3)进入Docker运行环境后,输入下面的命令来启动比特币测试网络:

$ make start

启动成功后,将在本机模拟运行两个比特币测试钱包节点,组成一个私有范围的比特币测试网络。

输入下面的命令可以查看测试网络节点状态信息,从中可以了解到比特币测试网络的配置和运行状态,比如协议版本、区块链长度和挖矿计算难度等内容,具体可以通过网络或技术书籍进一步查询了解:

$ make getinfo  

4)初始化和测试区块链数据。

在Docker运行窗口里依次输入下面的命令来初始化创建基本的区块链数据,供进一步的程序示例来使用。

make generate    //模拟新产生1个区块记录

make generate BLOCKS=200     //模拟新产生200个区块记录

5)查看最新的钱包状态包括 balance 余额信息,这时可以留意看到第一个钱包节点的账户余额发生了变动,新的余额即通过模拟区块挖矿产生的测试比特币。

make getinfo

6)给作为示例的测试钱包地址转账10个BTC。

make sendfrom1 ADDRESS=mkiytxYA6kxUC8iTnzLPgMfCphnz91zRfZ AMOUNT=10

注意:这里的示例地址mkiytxYA6kxUC8iTnzLPgMfCphnz91zRfZ是比特币测试网络下的钱包地址(以字符m起始),与比特币正式网络下的钱包地址(一般以数字1或3起始)是有区别的。

7)模拟新产生10个区块记录,让上面的转账交易得到足够有效的确认。

make generate BLOCKS=10    

8)查看最新的钱包状态包括余额信息, 这时可以留意看到第一个钱包节点的balance 账户余额发生了改变 ,差额部分即已经交易转账支出的10个BTC加上少许的矿工费用。

make getinfo

开发运行第一个示例程序

下文以Node.js开发语言为例,演示如何调用RPC接口来执行一些最基本的操作,比如导入比特币私钥,和发送一个最简单的转账交易。开发者通过该示例可以了解到最基本的区块链开发方法。

示例源码(含注释)可以从下述网址下载:

http://ppkpub.org/sample/RpcTestnet.js

然后在命令行下输入以下命令即可运行并看到运行结果:

node RpcTestnet.js

注意:每运行一次测试代码后,都需要到Docker运行环境的命令行下输入”make generate BLOCKS=10“,模拟产生新的区块记录,让测试代码产生的交易记录得到有效的确认。

在 理解此简单示例程序的基础上,经过对Bitcoin协议的进一步了解,我们可以调用RPC接口进一步开发出更复杂功能,如自行构建特定交易数据包(比如备 注信息、多重签名输出等)来满足特定业务需求。欲进一步深入学习研究区块链应用开发的朋友,可以从网上搜索相关学习资料,也可以参看文末推荐的《区块链技术指南》一书。

关于PPk开放小组

PPk 开放小组(The PPk Public Group)是一个开放的网络技术极客小组,集合了一群对比特币等加密货币感兴趣的P2P技术爱好者,小组成员多具有10多年以上通信和互联网行业技术研 发从业背景,对于互联网业态的发展趋势有着独立判断和独特理念,关注其以区块链为代表的底层技术的潜在价值,并尝试融合区块链、IPFS等创新P2P技术 来定义个实现一些中性、开放、开源的基础协议和工具集。

电子邮箱: ppkpub@gmail.com

互联网站: ppkpub.org

《区块链技术指南》新书介绍

更多区块链的技术细节,包括比特币、以太坊、超级账本、共识算法、侧链、闪电网络、比特币开发技术、以太坊智能合约开发等等,请参考邹均博士等作者合著的新书《区块链技术指南》,机械工业出版社:

 

RpcTestnet.js

 


//************************************************//
//   Bitcoin-Testnet RPC sample of node.js        //
//          PPk Public Group ? 2016.              //
//           http://ppkpub.org                    //
//     Released under the MIT License.            //
//************************************************//
//对应比特币测试网络(Bitcoin testnet)的RPC服务接口访问参数
var RPC_USERNAME='admin1'; 
var RPC_PASSWORD='123';
var RPC_HOST="127.0.0.1";
var RPC_PORT=19001;

//测试使用的钱包地址
TEST_ADDRESS='mkiytxYA6kxUC8iTnzLPgMfCphnz91zRfZ'; //测试用的钱包地址,注意与比特币正式地址的区别
TEST_PRIVATE_KEY='cTAUfueRoL1HUXasWdnETANA7uRq33BUp3Sw88vKZpo9Hs8xWP82'; //测试用的钱包私钥
TEST_WALLET_NAME='TestWallet1';  //测试的钱包名称 

MIN_DUST_AMOUNT=10000;  //最小有效交易金额,单位satoshi,即0.00000001 BTC
MIN_TRANSACTION_FEE=10000; //矿工费用的最小金额,单位satoshi

console.log('Hello, Bitcoin-Testnet RPC sample.');
console.log('     PPk Public Group ? 2016      ');

//初始化访问RPC服务接口的对象
var client = require('kapitalize')()

client
    .auth(RPC_USERNAME, RPC_PASSWORD)
    .set('host', RPC_HOST)
    .set({
        port:RPC_PORT
    });

//显示当前连接的比特币测试网络信息
client.getInfo(function(err, info) {
  if (err) return console.log(err);
  console.log('Info:', info);
});

//查看当前钱包下属地址账户余额变动情况
client.listaccounts(function(err, account_list) {
  if (err) return console.log(err);
  console.log("Accounts list:\n", account_list);
});

//检查测试帐号是否已存在于测试节点
client.getaccount(TEST_ADDRESS,function(err, result) {
  if (err || result!=TEST_WALLET_NAME ) { //如不存在,则新导入测试帐号私钥
      console.log('Import the test account[',TEST_WALLET_NAME,']:',TEST_ADDRESS);
      client.importprivkey(TEST_PRIVATE_KEY,TEST_WALLET_NAME,function(err, imported_result) {
          if (err) return console.log(err);
          console.log('Imported OK:', imported_result);
          
          doSample();
      });
  }else{ //如已存在,则直接执行示例
      console.log('The test account[',TEST_WALLET_NAME,'] existed. Address:',TEST_ADDRESS);
      
      doSample();
  }
 
});

// 示例实现功能
function doSample(){
    //获取未使用的交易(UTXO)用于构建新交易的输入数据块
    client.listunspent(6,9999999,[TEST_ADDRESS],function(err, array_unspent) {
      if (err) return console.log('ERROR[listunspent]:',err);
      console.log('Unspent:', array_unspent);

      var array_transaction_in=[];
      
      var sum_amount=0;
      for(var uu=0;uu<array_unspent.length;uu++){
          var unspent_record=array_unspent[uu];
          if(unspent_record.amount>0){
              sum_amount+=unspent_record.amount*100000000; //注意:因为JS语言缺省不支持64位整数,此处示例程序简单采用32位整数,只能处理交易涉及金额数值不大于0xFFFFFFF即4294967295 satoshi = 42.94967295 BTC。 实际应用程序需留意完善能处理64位整数
              array_transaction_in[array_transaction_in.length]={"txid":unspent_record.txid,"vout":unspent_record.vout};
              
              if( sum_amount > (MIN_DUST_AMOUNT+MIN_TRANSACTION_FEE) )
                break;
          }
      }
      
      //确保新交易的输入金额满足最小交易条件
      if (sum_amount<MIN_DUST_AMOUNT+MIN_TRANSACTION_FEE) return console.log('Invalid unspent amount');

      console.log('Transaction_in:', array_transaction_in);

      //生成测试新交易的输出数据块,此处示例是给指定目标测试钱包地址转账一小笔测试比特币
      //注意:输入总金额与给目标转账加找零金额间的差额即MIN_TRANSACTION_FEE,就是支付给比特币矿工的交易成本费用
      var obj_transaction_out={
          "mieC38pnPwMqbMAN6sGWwHRQ3msp7nRnNz":MIN_DUST_AMOUNT/100000000,   //目标转账地址和金额
          "mkiytxYA6kxUC8iTnzLPgMfCphnz91zRfZ":(sum_amount-MIN_DUST_AMOUNT-MIN_TRANSACTION_FEE)/100000000  //找零地址和金额,默认用发送者地址
        };
      
      console.log('Transaction_out:', obj_transaction_out);
      
      //生成交易原始数据包
      client.createrawtransaction(array_transaction_in,obj_transaction_out,function(err2, rawtransaction) {
          if (err2) return console.log('ERROR[createrawtransaction]:',err2);
          console.log('Rawtransaction:', rawtransaction);
          
          //签名交易原始数据包
          client.signrawtransaction(rawtransaction,function(err3, signedtransaction) {
              if (err3) return console.log('ERROR[signrawtransaction]:',err3);
              console.log('Signedtransaction:', signedtransaction);
              
              var signedtransaction_hex_str=signedtransaction.hex;
              console.log('signedtransaction_hex_str:', signedtransaction_hex_str);
              
              //广播已签名的交易数据包
              client.sendrawtransaction(signedtransaction_hex_str,false,function(err4, sended) { //注意第二个参数缺省为false,如果设为true则指Allow high fees to force it to spend,会在in与out金额差额大于正常交易成本费用时强制发送作为矿工费用(谨慎!)
                  if (err4) return console.log('ERROR[sendrawtransaction]:',err4);
                  console.log('Sended TX:', sended);
                  
                  client.listaccounts(function(err, account_list) {
                      if (err) return console.log(err);
                      console.log("Accounts list:\n", account_list); //发送新交易成功后,可以核对下账户余额变动情况
                    });
              });
          });
      });
    });
}