当前位置:首页 » 区块链精品文章 » 正文

6.2 把握比特币“交易”数据结构

1836 人参与  2018年09月30日 14:30  分类 : 区块链精品文章  评论

本节以比特币测试网络作为开发试验环境,解析比特币交易(Transaction)的数据结构,并以Node.js为例来说明如何自行组织特定需要的交易数据,并在签名后广播,最终被矿工节点确认生效。

6.2.1 了解比特币的“交易”数据结构

交易是比特币系统的信息载体和最小单元,而块(Block)就是将若干个这样的“交易”基础单元“打包装箱”,贴上“封条”,再按一定的机制和先后顺序将这些块串联起来,就构成了区块链(Blockchain)。

对于基于比特币区块链的应用开发,“交易”是最直接用到,也是最关键的数据结构。除了“交易”外,还需要掌握比特币区 块链相关的一些基础术语的含义,包括钱包的私钥、公钥和地址、区块、区块链等,这些在本书前面的章节已有深入介绍。在本节我们侧重对“交易”的数据结构做 更深入的剖析和了解。

一笔比特币交易是一个含有输入值和输出值的数据结构,该数据结构植入了将一笔资金从初始点(输入值)转移至目标地址(输出值)的代码信息。一笔比特币交易包含的一些字段如表6-1所示。

表6-1 比特币交易字段

image.png

注意:锁定时间字段定义了能被加到区块链里的最早的交易时间。在大多数交易 里,它被缺省设置成0,用来表示立即执行。如果锁定时间大于0并且小于5亿,就被视为区块高度,意指在这个指定的区块高度之前,该交易不会被包含在区块链 里。如果锁定时间大于5亿,则被当作是一个UNIX纪元时间戳(从1970年1月1日以来的秒数),并且在这个指定时点之前,该交易不会被包含在区块链 里。锁定时间的使用相当于将一张纸质支票的生效时间予以后延。

基于区块链技术的应用开发,实际上主要就是在交易的输出数据结构上做文章,来承载具体的业务逻辑,比如ODIN开源项目就是将标识属性数据按一定格式嵌入比特币的多重交易输出数据块中。

6.2.2 交易记录的实例解析

下面是一个比特币交易的原始数据示例(将原始二进制数据按字节以十六进制形式输出,便于分析)。


0100000002eb2121e4e727bdb28525e79d39a90bd711b9e8413c054b29ffc4bb4775e69f82010000006b48
3045022100df82cf6c95b4eb64e4e9cee3af88a94c65fa81650e824d515f089192b7e3c09c0220119c1fcf
d9354755ea815cf714c181b56784b8f98f59f33e977c8939cd6f75db0121022e9f31292873eee495ca9744
fc410343ff373622cca60d3a4c926e58716114b9ffffffffc9f3b07ebfca68fd1a6339d0808fbb013c90c6
095fc93901ea77410103489ab7010000008a47304402206b993231adec55e6085e75f7dc5ca6c19e42e744
cd60abaff957b1c352b3ef9a022022a22fec37dfa2c646c78d9a0753d56cb4393e8d0b22dc580ef1aa6ccc
ef208d0141042ff65bd6b3ef04253225405ccc3ab2dd926ff2ee48aac210819698440f35d785ec3cec92a5
1330eb0c76cf49e9e474fb9159ab41653a9c1725c031449d31026affffffff031027000000000000475121
022e9f31292873eee495ca9744fc410343ff373622cca60d3a4c926e58716114b9210250504b2d42455441
506565722d506565722d6e6574776f726b207075626c696352aee07b9a3b000000001976a914391ef5239d
a2a3904cda1fd995fb7c4377487ea988ac00000000000000000d6a0b436f6465206973204c617700000000

通过上述解析,可以理解和掌握了比特币交易记录的常见组织格式,充分利用其中加注下划线的数据内容块来嵌入自定义的一些二进制数据内容,就可以实现自己特定的业务逻辑了。

6.2.3 运行示例程序

这里的示例程序是演示将一段特定内容的字符串按一定格式嵌入比特币交易的备注数据块中,这样就可以被存入比特币区块链上。

代码清单6-2 示例程序OpreturnTestnet.js源码


// ************************************************//
//  RPC sample based Bitcoin-Testnet 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_PUBKEY_HEX='022e9f31292873eee495ca9744fc410343ff373622cca60d3a4c926e5
   8716114b9';                 // 十六进制表示的钱包公钥TEST_HASH160='391ef5239da2a3904cda1fd995fb7c4377487ea9';
                                       // HASH160格式的钱包公钥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.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);
           doRpcSample();
       });
   }else{ // 如已存在,则直接执行示例




       console.log('The test account[',TEST_WALLET_NAME,'] existed. Address:',
           TEST_ADDRESS);
       doRpcSample();
   }
});
// 示例实现功能function doRpcSample(){
   // 获取未使用的交易用于生成新交易




   client.listunspent(6,9999999,[TEST_ADDRESS],function(err2, array_unspent) {
       if (err2) return console.log('ERROR[listunspent]:',err2);
       console.log('Unspent:', array_unspent);
       // 测试数据定义

       var TEST_DATA='Peer-Peer-network is the future!';
       console.log('TEST_DATA=',TEST_DATA);
       // 将原始字节字符串转换为用十六进制表示

       var str_demo_hex=stringToHex(TEST_DATA);
       console.log('str_demo_hex=',str_demo_hex);
       // 生成输入交易定义块

       var min_unspent_amount=MIN_DUST_AMOUNT*1+MIN_TRANSACTION_FEE;
       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;
               array_transaction_in[array_transaction_in.length]={"txid":
                   unspent_record.txid,"vout":unspent_record.vout};
               if( sum_amount > min_unspent_amount )
                   break;
           }
       }
       // 确保新交易的输入金额满足最小交易条件

           if (sum_amount<=min_unspent_amount) return console.log
               ('Invalid unspent amount');
       console.log('Transaction_in:', array_transaction_in);
       // 构建原始交易数据




       var rawtransaction_hex = '01000000';    // Bitcoin协议版本号,UINT32
       rawtransaction_hex += byteToHex(array_transaction_in.length) ;
                                                       // 设置输入交易数量

       for(var kk=0;kk<array_transaction_in.length;kk++){
           rawtransaction_hex += reverseHex(array_transaction_in[kk].txid)+
               uIntToHex(array_transaction_in[kk].vout);
           rawtransaction_hex += "00ffffffff"; // 签名数据块的长度和序列号,


                                                       // 00表示尚未签名




       }
       rawtransaction_hex += byteToHex(2);     // 设置输出交易数量

       // 使用op_return对应的备注脚本空间来嵌入自定义数据

       rawtransaction_hex += "0000000000000000";  
       rawtransaction_hex += byteToHex(2+str_demo_hex.length/2) + "6a" +
           byteToHex(str_demo_hex.length/2) +str_demo_hex;
       // 最后添加一个找零输出交易

       var charge_amount = sum_amount - MIN_TRANSACTION_FEE;
       console.log('sum_amount:', sum_amount);
       console.log('min_unspent_amount:', min_unspent_amount);
       console.log('charge_amount:', charge_amount);
       console.log('uIntToHex(',charge_amount,')=', uIntToHex(charge_amount));
       rawtransaction_hex += uIntToHex(charge_amount)+"00000000";
                                                       // 找零金额,UINT64
       rawtransaction_hex += "1976a914" + TEST_HASH160 +"88ac";
                                                       // 找零地址为发送者的钱包地址

       rawtransaction_hex += "00000000";       // 锁定时间,默认设置成0,表示立即

                                                       // 执行,这是整个交易数据块的结束字段

       console.log('Rawtransaction:', rawtransaction_hex);
       // 签名交易原始数据包

       client.signrawtransaction(rawtransaction_hex,function(err3,
           signedtransaction) {
           if (err3) return console.log('ERROR[signrawtransaction]:',err3);
           console.log('Signedtransaction:', signedtransaction);
           if (!signedtransaction.complete) return console.log
               ('signrawtransaction failed');
           var signedtransaction_hex_str=signedtransaction.hex;
           console.log('signedtransaction_hex_str:', signedtransaction_hex_str);
           // 广播已签名的交易数据包
           client.sendrawtransaction(signedtransaction_hex_str, false,
               function(err4, sended){
               // 注意第2个参数默认为false,如果设为true则指Allow high fees to
               // force it to spend
               // 会强制发送交易,并将输入与输出金额差额部分作为矿工费用(谨慎!)
               if (err4) return console.log('ERROR[sendrawtransaction]:',err4);
               console.log('Sended TX:', sended);
           });
       });
   });
}
// 1字节整数转换成十六进制字符串function byteToHex(val){
   var resultStr='';
   var tmpstr=parseInt(val%256).toString(16);
   resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;  
   return resultStr;
}
// 将HEX字符串反序输出function reverseHex(old){
   var array_splited=old.match(/.{2}|.+$/g);
   var reversed='';
   for(var kk=array_splited.length-1;kk>=0;kk--){
       reversed += array_splited[kk];
   }
   return reversed;
}
// 32位无符号整数变成十六进制数,并按翻转字节顺序function uIntToHex(val){
   var resultStr='';
   var tmpstr=parseInt(val%256).toString(16);
   resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;  
   tmpstr=parseInt((val%65536)/256).toString(16);
   resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;  
   tmpstr=parseInt(parseInt(val/65536)%256).toString(16);
   resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;  
   tmpstr=parseInt(parseInt(val/65536)/256).toString(16);
   resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;  
   return resultStr;
}
// 将Ascii或Unicode字符串转换成十六进制表示function stringToHex(str){
   var val="";
   for(var i = 0; i < str.length; i++){
       var tmpstr=str.charCodeAt(i).toString(16);  // Unicode
       val += tmpstr.length==1? '0'+tmpstr : tmpstr;  
   }
   return val;
}上述源码可以从下述网址下载,并保存到测试环境下(保存文件名为Opreturn-Testnet.js)。http:// ppkpub.org/sample/OpreturnTestnet.js然后在命令行下输入以下命令,即可运行并看到运行结果。node OpreturnTestnet.js


来源:我是码农,转载请保留出处和链接!

本文链接:http://www.54manong.com/?id=1012

区块链是什么  

微信号:qq444848023    QQ号:444848023

加入【我是码农】QQ群:864689844(加群验证:我是码农)

<< 上一篇 下一篇 >>

网站分类

标签列表

最近发表

全站首页 | 数据结构 | 区块链| 大数据 | 机器学习 | 物联网和云计算 | 面试笔试

本站资源大部分来自互联网,版权归原作者所有!