本文目的
通过上一篇笔记python-bitcoinrpc下载比特币数据Windows(完成)可以直接通过不同的API获得解析后的区块数据或交易记录,本文分析这些数据能给我们提供哪些对分析比特币网络有用的信息。
Block原始数据维度
利用bitcoinrpc提供的getblock接口可以帮助我们获取区块数据。该接口需要输入区块哈希,另外一个可选变量有三个取值,0,1,2。
- 取值为0时,返回原始的16进制编码的数据
- 取值为1时,返回解码后的字典数据
- 取值为2时,基本和取值为1时返回结果一致,只在键为tx的值中包含了交易的全数据
上图展示了在pycharm里面运行以下代码获得的rawdata返回结果:
from bitcoinrpc.authproxy import AuthServiceProxy
rpc_connection = AuthServiceProxy("http://%s:%s@127.0.0.1:8332"%('xxx', 'xxx'))best_block_hash = rpc_connection.getbestblockhash() #获取当前最长链的最后一个区块哈希
rawdata = rpc_connection.getblock(best_block_hash) #获取指定区块哈希的区块数据
rawdata中包含的数据维度有:
# 区块数据维度
hash #当前区块的哈希
confirmations #当前区块已被确认数(即后续跟着的区块数+1,或者可以直接用当前区块链总高度—当前区块高度+1计算获得),需要注意的是,该数值会随着新区块的不断增加而变化,没必要作为数据维度之一存入数据库
strippedsize #剔除隔离见证数据后的区块字节数
size #区块字节数
weight #BIP141定义的区块权重
height #当前区块高度(第一个区块高度为0,于2009-01-04 02:15生成)
version #区块版本(代表支持不同的软、硬分叉方案)
versionHex #用16进制表示的区块版本
merkleroot #区块的默克尔树根,是根据当前区块内所有交易哈希生成的默克尔树的根节点哈希
tx #当前区块包含的交易列表,列表中是交易id
time #当前区块创建的时间戳(unix timestamp)
mediantime #过去11个区块创建时间戳的中值(unix timestamp)
nonce #用于挖矿工作量证明的随机数
bits #难度目标,标识了当前区块头Hash之后要小于等于的目标值(target)
difficulty #区块挖矿的难度值
chainwork #当前区块链的工作量加总
nTx #当前区块包含的交易总数
previousblockhash #当前区块链接的前序区块哈希
nextblockhash #(如有)链接当前区块的后序区块哈希
涨知识Tips:
- BIP即Bitcoin Improvement Proposal的首字字母缩写,意译为“比特币改进提议”,每个BIP都牵涉到对比特币源码的改动。而BIP仅仅是提议,实施与否需要社区达成共识。
- BIP141定义了当前的比特币隔离见证激活方案,大概方法是保存区块数据时移除比特币交易过程中的签名字段,从而在不扩大区块大小的情况下实现“变相扩容”。
- 区块头里面的默克尔树根是根据nTx笔交易的哈希作为叶子节点生成的默克尔树的根哈希,可参考这篇文章比特币区块结构Merkle树及简单支付验证分析中的示意图理解。
- unix时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
- mediantime用于确认区块:只有当某个区块的时间戳大于mediantime,并且小于某个调整后的网络时间(p2p网络节点报时中值+2小时?)时,这个区块才会被确认。
上面理清了区块数据的维度,其中有助于分析比特币交易网络的维度主要有:
- hash(可作为区块的唯一查询标识)
- height(可作为区块的唯一查询标识并用于排序)
- tx(最重要的交易数据)
- time(时间戳)
- nTx (当前区块包含的交易总数)
获取交易数据
获取比特币交易数据可以通过两种途径:
- 使用上述getblock接口并将verbosity设置为2,可获得该区块内所有交易的详细数据
rawdata = rpc_connection.getblock(block_hash, 2)
txlist = rawdata['tx']
for txdata in txlist:
...
-
使用getrawtransaction接口并将verbose设置为True:
getrawtransaction.png
txdata = rpc_connection.getrawtransaction(tx_id,True)
这两种获得交易数据的方法都能返回一致的结果,下图是返回结果对比,第一种方式适用于一次性获取某一个区块里的交易列表;第二种方式适用于查询某个已知txid的特定交易数据,当然,在单独查询交易数据时也会返回交易所属区块的信息(黄框所示)
交易数据维度
从返回的交易数据,我们可以获得以下维度的数据:
# 交易数据维度
txid #交易id,和所要查询的交易id一致
hash #交易哈希,对采用隔离验证(segwit)的交易生成的哈希,与txid不同,若交易未采用隔离验证,则与txid一致
version #版本
size #交易数据占据的字节数
vsize #隔离验证后交易数据占据的字节数(总是小于size)
weight #BIP141定义的方法计算占据权重,是vsize的4倍取整
locktime #该交易能被加入到区块内的最早时间
vin #交易的发送地址列表
vout #交易的接收地址列表
hex #txid16进制编码后的序列
涨知识Tips:
- 每个交易都可有多个发送地址和多个接收地址,每个发送地址都会附带一个由发送地址持有人秘钥生成的scriptSig,即unlocking script,用于解锁该地址的余额,这个scriptSig在密码学上被称为“witness”。
- 交易的txid,是对交易信息进行两次sha256的结果。
- 打包交易的节点有可乘之机,作恶节点可以通过对发送者提供的witness进行包装生成一个新的txid,而验证节点验证整个交易时察觉不出txid被篡改。这样作恶节点就可以欺骗发送者交易未成功发送,但实际上交易已经以另一个txid上链了,导致发送者掉入重复支付陷阱。
- BIP141建议的隔离验证,是将witness从交易数据中分离出来,放在区块底部,这样按原来方式算txid就不包括验证信息了,而包括验证信息生成的即为hash:
txID: [nVersion][txins][txouts][nLockTime]
hash: [nVersion][marker][flag][txins][txouts][witness][nLockTime]
这就是交易数据里面txid和hash有可能不一样的原因。- 有关segregated witness如何扩容的解释可参考这篇Medium文章
因为在txin引用前序交易时都是指向前序交易的txid,故在保存交易数据时以txid作为其唯一标识比较方便。其他维度中对分析比特币交易网络比较有用的就是vin和vout了,在下面详细展开。
vin和vout包含的内容
每一个区块可能包含多笔交易,但第一笔交易都是系统直接向矿工发送,金额是当下挖矿奖励+该区块内的手续费加总。数据格式如下:
vin=[{"coinbase": "xxxxxx很长的一串字符串xxxxxx",
"sequence": 0}]
vout=[{"value": Decimal("12.64059504"),
"n": 0,
"scriptPubKey": {"asm": "xxx",
"hex": "xxx",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": ["xxx"]}
},
{"value": Decimal("0E-8"),
"n": 1,
"scriptPubKey": {"asm": "xxx",
"hex": "xxx",
"type": "nulldata"}
},
...]
vin是发送方地址列表,如果一个vin列表中只有一个字典,并且其中存在键“coinbase”,就可以判断该笔交易是矿工奖励。
vout是接收方地址列表,可能有一至多个接收方,每个接收方用一个字典表示,一般包含:
value #接收到的比特币数额,八位小数
n #接收方的排序号,从0开始,便于后续花费时引用该笔txid,并指明是第n笔
scriptPubKey #接收方信息
- asm #用于分析scriptPubKey的脚本,不同type对应的asm不同
- hex #asm的16进制编码
- type #接收方地址类型,可以是pubkeyhash, scripthash, witness_v0_scripthash, multisig, nulldata等
- reqSigs #要求的签名数
- addresses #接收方地址,是个列表,多重签名时可能会有多个地址
实际上,vout的接收方可有多种类型,具体可参考An Analysis of Non-standard Transactions这篇论文【1】,里面详细介绍了标准交易类型和非标准交易类型。
标准交易类型(Standard Type):
- P2PKH(比特币网络默认类型)
- P2PK (多出现在矿工接收奖励和手续费的coinbase交易中)
- Multi-signature (多签)
- P2SH(将另一笔交易的script哈希后作为新script,压缩字节)
- P2WPKH、P2WSH:隔离见证采用后P2PKH的另一种实现方式,字节占用更少,且签名从unlocking script中移出
- nulldata (OP_RETURN): 隔离见证采用后用于存储见证信息的默克尔树根( Merkle root of the witness tree),在coinbase交易的接收方出现,该笔output将直接从UTXO中移除,后续不能被花费,转移的比特币数量一般都为零。vout中没有reqSigs和addresses这两对键值。
论文【1】及Bitcoin Wiki对Transaction的描述信息中介绍了几种交易类型的脚本形式(asm),例如:
# Pay-to-PubkeyHash(P2PKH)
scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig: <sig> <pubKey>
其中scriptPubKey是locking script, 保存在vouts里,scriptSig是发送方用于解锁发送地址中比特币的签名信息(unlocking script),在非coinbase交易中的vin出现:
vin=[{"txid": "xxx",
"vout": 0,
"scriptSig": {"asm": "xxx",
"hex": "xxx"},
"sequence": 4294967295},
...]
注意:非coinbase交易的vin没有直接给出发送方地址信息,而是告诉我们该笔交易花费的前序交易“txid”及“vout”,“vout”是某一笔花费在该前序交易中的接收方排序,即前面分析的vout中的“n”对应的值。通过这个信息需要再去查询指定“txid”的第“vout”个接收方地址,才能获得该笔交易的发送方地址。另外一个数据维度,sequence,是不经常用到的一个序列号,用于更改带时间锁的交易,详细可参考官方说明:locktime-and-sequence-number。
原则上,节点验证程序会通过isStandard()及isStandardTx()两个函数分别验证所有outputs和inputs是否采用标准交易形式。但一些矿工节点修改了程序,导致区块里也会打包一些非标准交易,但它们所占的比例很少(0.02%)。
非标准交易类型(Nonstandard Type):
- 疑似DDOS攻击:例如Transaction: 2a0597e665ac3d1cabeede95cedf907934db7f639e477b3c77b242140d8cf728在scriptPubKey中写入上百个OP_CHECKSIG(消耗验证节点的计算量),该笔output可以被后续花费。
- P2PKH NOP: 在P2PKH的script形式上末尾加了OP_NOP,用于测试未来的某些新功能,例如Transaction: db3f14e43fecc80eb3e0827cecce85b3499654694d12272bf91b1b2b8c33b5cb,该笔交易可以被后续花费。
- P2PKH 0: script形式与P2PKH一样,但是在public key hash的位置填写的是0,由于0不是有效公钥哈希,导致该笔output无法被后续花费,也没有接收方地址,转移的金额相当于销毁了。
总结
本文分析了JSON-bitcoinrpc的若干接口,
- 通过getblockhash()接口,可获得指定height的区块hash;
- 通过getblock()接口,可获得指定区块hash的区块数据,设定第二个参数为2可获得全量交易数据列表;
- 通过getrawtransaction()接口,可获得单个指定txid的交易数据;
- 通过getbestblockhash()接口,可获得当前最长链最后一个被确认区块的hash。
另,下面代码示例可实现批量获取返回结果:
commands1 = [["getblockhash", height] for height in range(100)]
block_hashes = rpc_connection.batch_(commands1)
commands2 = [["getblock", h] for h in block_hashes]
blocks = rpc_connections.batch_(commands2)
对分析比特币交易网络有用的信息主要就是:
- 区块相关的数据
- hash(可作为区块的唯一查询标识)
- height(可作为区块的唯一查询标识并用于排序)
- time(时间戳)
- nTx (当前区块包含的交易总数)
- 交易相关的数据
- txid
- vin,判断是否coinbase,引用的前序交易txid及排序号vout
- vout, 接收方列表(每个接收方包括value, 排序号n, scriptPubKey中的type, reqSigs, addresses)
- blockhash 交易所属区块的哈希
- 派生数据
- 手续费,发送方总金额—接收方总金额的差值为支付给矿工的手续费
- 找零,推断找零地址及找零余额
- multi-input heuristic
- 通过hash和txid是否一致判断该交易是否采用隔离见证
由于数据量庞大,如果要存在数据库中,下一步是设计数据仓库结构,以便能够有效地查询到所需数据。
(记于2019年12月31日 漠水云)