Here’s the table of contents:
使用图数据分析比特币区块链
????整个过程就是从一种格式(区块链数据)获取数据,并将其转换为另一种格式(图形数据库)。唯一让这种转换比典型数据转换更棘手的是对于数据格式的理解;在开始之前,理解[比特币数据的结构 会有所帮助【在第一部分会详细说明】。另外,可以参考一些开源工具实现区块链数据导入,bitcoin-to-graph。区块链(Blockchain)和比特币(Bitcoin)是什么关系?参考链接文章!
????一旦将区块链导入到图数据库中,就可以在图形数据库上执行SQL数据库无法执行的分析。例如,你可以跟踪比特币的路径,看看两个不同的地址是否连接:
一、比特币是如何运作的,区块链是什么
????比特币是一种计算机程序。运行该程序之后,它可以连接到其他运行相同程序的计算机,并共享一个文件。然而,比特币最酷的地方在于,任何人都可以向这个共享文件添加数据,任何已经写入文件的数据都不会被篡改。因此,比特币创建了一个在分布式网络上共享的安全文件。
1.1、比特币可以用来做什么?
????在比特币中,添加到该文件中的每条数据都是一笔交易。因此,这个去中心化文件被用作数字货币(即加密货币)的“账本”。这个“账本”被称为区块链。
1.2、在哪里可以找到区块链?
????如果你运行比特币核心程序,区块链将被存储在你电脑上的一个文件夹中:
- Linux: ~/.bitcoin/blocks
- Mac: ~/Library/Application Support/Bitcoin/blocks
- Windows: C:\Users\YourUserName\Appdata\Roaming\Bitcoin\blocks
????当您打开这个目录时,您应该注意到不是一个大文件,而是多个名为blkxxxx .dat的文件。这是区块链数据,但是分散在多个较小的文件中。
二、区块链数据是什么样子的
????blk.dat文件包含块blocks 和交易transactions 的序列化数据。
2.1、块Blocks
????块被magic bytes分隔,接下来是即将到来的块的大小。每个块以一个块头开始【块是一个交易列表的基本容器单位】:
000000206c77f112319ae21489b66774e8acd379044d4a23ea7498000000000000000000821fe1890186779b2cc232d5dbecfb9119fd46f8a9cfd1141649ff1cd907374487d8ae59e93c011832ec0399
2.2、交易Transactions
????在区块头之后,有一个字节告诉您区块中即将到来的事务数。在此之后,您将依次获得序列化的事务数据。交易只是另一段代码,但它们在结构上更有意思。比特币交易就是一堆描述比特币移动的数据。它接收输入,并创建新的输出。
????每笔交易都有相同的模式:
- 选择输出(就是得到输入的过程):解锁这些输入,这样就可以消费了。
- 创建输出:将这些输出锁定到一个新地址。
????在一系列交易之后,你会得到这样的交易结构。这是区块链的简化图。如你所见,它看起来像一个图【类似于引言的那个图】:
三、如何将区块链数据导入到图数据库中
????通过第一 和第二 部分基本弄清楚了区块链数据表示什么(它看起来很像一个图),可以继续将它导入到图数据库中了。
- 读取blk.dat文件。
- 解码每一个区块和交易。
- 将解码的块/事务转换为一个Cypher查询。
????以下是如何在数据库中表示区块、交易和地址的可视化指南:
3.1、块Blocks
- 创建一种
:block 节点,并将其连接到所构建的前一个区块;将区块头中的每个字段设置为该节点的属性。 - 为每个区块的节点,创建一种
:coinbase 节点,代表了区块提供的“新”比特币。
3.2、交易Transactions
- 创建一种
:tx 节点,并将它连接到我们刚才创建的:block 节点;设置:tx 节点的属性为(version, locktime)。 - 合并已有
:output 节点,并将他们关联[:in] 到:tx 节点;设置unlocking code 做为关系的属性。 - 创建这次交易产生的新的
:output 节点;设置这些节点上各自的values 和locking 代码。
3.3、地址Addresses
- 创建一种’:address’节点,并将
:output 节点连接到它;同时,设置address 属性在这个节点上。(如果不同的输出连接到相同的地址,那么它们将连接到相同的地址节点。)
四、Cypher查询
????下面是一些示例Cypher查询,您可以使用它们作为向图数据库插入块和交易数据的基准查询。(注意点:需要解码区块头和交易数据,以获得Cypher查询的参数。)
4.1、块Block 数据处理
MERGE (block:block {hash:$blockhash})
CREATE UNIQUE (block)-[:coinbase]->(:output:coinbase)
SET
block.size=$size,
block.prevblock=$prevblock,
block.merkleroot=$merkleroot,
block.time=$timestamp,
block.bits=$bits,
block.nonce=$nonce,
block.txcount=$txcount,
block.version=$version,
MERGE (prevblock:block {hash:$prevblock})
MERGE (block)-[:chain]->(prevblock)
{
"blockhash": "00000000000003e690288380c9b27443b86e5a5ff0f8ed2473efbfdacb3014f3",
"version": 536870912,
"prevblock": "000000000000050bc5c1283dceaff83c44d3853c44e004198c59ce153947cbf4",
"merkleroot": "64027d8945666017abaf9c1b7dc61c46df63926584bed7efd6ed11a6889b0bac",
"timestamp": 1500514748,
"bits": "1a0707c7",
"nonce": 2919911776,
"size": 748959,
"txcount": 1926,
}
4.2、交易Transaction 数据处理
????这个查询使用FOREACH语句,它作为一个条件,只在$addresses参数实际包含一个地址时才创建:address节点。通常,FOREACH语句使用在需要动态 创建图数据的场景中。
MATCH (block :block {hash:$hash})
MERGE (tx:tx {txid:$txid})
MERGE (tx)-[:inc {i:$i}]->(block)
SET tx += {tx}
WITH tx
FOREACH (input in $inputs |
MERGE (in :output {index: input.index})
MERGE (in)-[:in {vin: input.vin, scriptSig: input.scriptSig, sequence: input.sequence, witness: input.witness}]->(tx)
)
FOREACH (output in $outputs |
MERGE (out :output {index: output.index})
MERGE (tx)-[:out {vout: output.vout}]->(out)
SET
out.value= output.value,
out.scriptPubKey= output.scriptPubKey,
out.addresses= output.addresses
FOREACH(ignoreMe IN CASE WHEN output.addresses <> '' THEN [1] ELSE [] END |
MERGE (address :address {address: output.addresses})
MERGE (out)-[:locked]->(address)
)
)
{
"txid":"2e2c43d9ef2a07f22e77ed30265cc8c3d669b93b7cab7fe462e84c9f40c7fc5c",
"hash":"00000000000003e690288380c9b27443b86e5a5ff0f8ed2473efbfdacb3014f3",
"i":1,
"tx":{
"version":1,
"locktime":0,
"size":237,
"weight":840,
"segwit":"0001"
},
"inputs":[
{
"vin":0,
"index":"0000000000000000000000000000000000000000000000000000000000000000:4294967295",
"scriptSig":"03779c110004bc097059043fa863360c59306259db5b0100000000000a636b706f6f6c212f6d696e65642062792077656564636f646572206d6f6c69206b656b636f696e2f",
"sequence":4294967295,
"witness":"01200000000000000000000000000000000000000000000000000000000000000000"
}
],
"outputs":[
{
"vout":0,
"index":"2e2c43d9ef2a07f22e77ed30265cc8c3d669b93b7cab7fe462e84c9f40c7fc5c:0",
"value":166396426,
"scriptPubKey":"76a91427f60a3b92e8a92149b18210457cc6bdc14057be88ac",
"addresses":"14eJ6e2GC4MnQjgutGbJeyGQF195P8GHXY"
},
{
"vout":1,
"index":"2e2c43d9ef2a07f22e77ed30265cc8c3d669b93b7cab7fe462e84c9f40c7fc5c:1",
"value":0,
"scriptPubKey":"6a24aa21a9ed98c67ed590e849bccba142a0f1bf5832bc5c094e197827b02211291e135a0c0e",
"addresses":""
}
]
}
五、使用区块链数据做一些查询分析
????如果已经使用上面的Cypher查询插入了块和交易数据,那么就可以从图数据库中做一些查询分析了。
5.1、查询块Bolck
MATCH (block :block)<-[:inc]-(tx :tx)
WHERE block.hash='$blockhash'
RETURN block, tx
5.2、查询交易Transaction
MATCH (inputs)-[:in]->(tx:tx)-[:out]->(outputs)
WHERE tx.txid='$txid'
OPTIONAL MATCH (inputs)-[:locked]->(inputsaddresses)
OPTIONAL MATCH (outputs)-[:locked]->(outputsaddresses)
OPTIONAL MATCH (tx)-[:inc]->(block)
RETURN inputs, tx, outputs, block, inputsaddresses, outputsaddresses
5.3、查询地址Address
MATCH (address :address {address:'1PNXRAA3dYTzVRLwWG1j3ip9JKtmzvBjdY'})<-[:locked]-(output :output)
WHERE address.address='$address'
RETURN address, output
5.4、查寻路径
????查找交易和地址之间的路径可能是你可以用比特币区块链的图形数据库做的最有趣的事情,所以这里有一些Cypher查询的例子:
MATCH (start :output {index:'$txid:vout'}), (end :output {index:'$txid:out'})
MATCH path=shortestPath( (start)-[:in|:out*]-(end) )
RETURN path
MATCH (start :address {address:'$address1'}), (end :address {address:'$address2'})
MATCH path=shortestPath( (start)-[:in|:out|:locked*]-(end) )
RETURN path
六、总结
????本文是关于如何从blk.dat文件(区块链)获取块和交易数据并将它们导入到图数据库的简单指南。
????我认为,如果你想对区块链进行分析,这是值得的。图数据库是比特币区块链数据的最自然地表达,而使用SQL数据库来进行比特币交易数据分析,是非常困难甚至无法实现的。
为了尽量让这个指南简明扼要,所以没有涉及以下内容:
- 阅读区块链。读取blk.dat文件很容易。然而,关于这些文件的恼人之处在于,块不是按顺序写入这些文件的,这使得设置块的高度或计算交易的费用有点棘手(但您可以围绕它进行编码)。
- 解码块和交易。如果您想使用上面的Cypher查询,您将需要通过解码区块头和原始交易数据来获得所需的参数。您可以编写自己的解码器,或者尝试使用现有的比特币库。
- 还有另外一些特殊格式的处理,也需要特殊考虑。但是如果你了解数据是如何组织起来的,将它转换成不同的格式只是开发一些特定的程序而已。
|