前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何将Bitcoin比特币区块链数据导入关系数据库

如何将Bitcoin比特币区块链数据导入关系数据库

作者头像
深蓝studyzy
发布于 2022-06-16 07:56:53
发布于 2022-06-16 07:56:53
72700
代码可运行
举报
文章被收录于专栏:深蓝居深蓝居
运行总次数:0
代码可运行

在接触了比特币和区块链后,我一直有一个想法,就是把所有比特币的区块链数据放入到关系数据库(比如SQL Server)中,然后当成一个数据仓库,做做比特币交易数据的各种分析。想法已经很久了,但是一直没有实施。最近正好有点时间,于是写了一个比特币区块链的导出导入程序。

之前我的一篇博客:在区块链上表白——使用C#将一句话放入比特币的区块链上  介绍了怎么发起一笔比特币的交易,今天我们仍然是使用C#+NBitcoin,读取比特币钱包Bitcoin Core下载到本地的全量区块链数据,并将这些数据写入数据库。如果有和我一样想法的朋友,可以参考下面是我的操作过程:

一、准备

我们要解析的是存储在本地硬盘上的Bitcoin Core钱包的全量比特币数据,那么首先就是要下载并安装好Bitcoin Core,下载地址:https://bitcoin.org/en/download 然后就等着这个软件同步区块链数据吧。目前比特币的区块链数据大概130G,所以可能需要好几天,甚至一个星期才能将所有区块链数据同步到本地。当然如果你很早就安装了这个软件,那么就太好了,毕竟要等好几天甚至一个星期,真的很痛苦。

二、建立比特币区块链数据模型

要进行区块链数据的分析,那么必须得对区块链的数据模型了解才行。我大概研究了一下,可以总结出4个实体:区块、交易、输入、输出。而其中的关系是,一个区块对应多个交易,一个交易对应多个输入和多个输出。除了Coinbase的输入外,一笔输入对应另一笔交易中的输出。于是我们可以得出这样的数据模型:

需要特别说明几点的是:

1.TxId是自增的int,我没有用TxHash做Transaction的PK,那是因为TxHash根本就不唯一啊!有好几个不同区块里面的第一笔交易,也就是Coinbase交易是相同的。这其实应该是异常数据,因为相同的TxHash将导致只能花费一次,所以这个矿工杯具了。

2.对于一笔Coinbase 的Transaction,其输入的PreOutTxId是0000000000000000000000000000000000000000000000000000000000000000,而其PreOutIndex是-1,这是一条不存在的TxOutput,所以我并没有建立TXInput和TxOutput的外键关联。

3.对于Block,PreId就是上一个Block的ID,而创世区块的PreId是0000000000000000000000000000000000000000000000000000000000000000,也是一个不存在的BlockId,所以我没有建立Block的自引用外键。

4.有很多字段其实并不是区块链数据结构中的,这些字段是我添加为了接下来方便分析用的。在导入的时候并没有值,需要经过一定的SQL运算才能得到。比如Trans里面的TotalInAmount,TransFee等。

我用的是PowerDesigner,建模完成后,生成SQL语句,即可。这是我的建表SQL:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
create table Block ( 
   Height               int                  not null, 
   BlkId                char(64)             not null, 
   TxCount              int                  not null, 
   Size                 int                  not null, 
   PreId                char(64)             not null, 
   Timestamp            datetime             not null, 
   Nonce                bigint               not null, 
   Difficulty           double precision     not null, 
   Bits                 char(64)             not null, 
   Version              int                  not null, 
   TxMerkleRoot         char(64)             not null, 
   constraint PK_BLOCK primary key nonclustered (BlkId) 
) 
go

/*==============================================================*/ 
/* Index: Block_Height                                          */ 
/*==============================================================*/ 
create unique clustered index Block_Height on Block ( 
Height ASC 
) 
go

/*==============================================================*/ 
/* Table: Trans                                                 */ 
/*==============================================================*/ 
create table Trans ( 
   TxId                 int                  not null, 
   BlkId                char(64)             not null, 
   TxHash               char(64)             not null, 
   Version              int                  not null, 
   InputCount           int                  not null, 
   OutputCount          int                  not null, 
   TotalOutAmount       bigint               not null, 
   TotalInAmount        bigint               not null, 
   TransFee             bigint               not null, 
   IsCoinbase           bit                  not null, 
   IsHeightLock         bit                  not null, 
   IsTimeLock           bit                  not null, 
   LockTimeValue        int                  not null, 
   Size                 int                  not null, 
   TransTime            datetime             not null, 
   constraint PK_TRANS primary key (TxId) 
) 
go

/*==============================================================*/ 
/* Index: Relationship_1_FK                                     */ 
/*==============================================================*/ 
create index Relationship_1_FK on Trans ( 
BlkId ASC 
) 
go

/*==============================================================*/ 
/* Index: Trans_Hash                                            */ 
/*==============================================================*/ 
create index Trans_Hash on Trans ( 
TxHash ASC 
) 
go

/*==============================================================*/ 
/* Table: TxInput                                               */ 
/*==============================================================*/ 
create table TxInput ( 
   TxId                 int                  not null, 
   Idx                  int                  not null, 
   Amount               bigint               not null, 
   PrevOutTxId          char(64)             not null, 
   PrevOutIndex         int                  not null, 
   PaymentScriptLen     int                  not null, 
   PaymentScript        varchar(8000)        not null, 
   Address              char(58)             null, 
   constraint PK_TXINPUT primary key (TxId, Idx) 
) 
go

/*==============================================================*/ 
/* Index: Relationship_2_FK                                     */ 
/*==============================================================*/ 
create index Relationship_2_FK on TxInput ( 
TxId ASC 
) 
go

/*==============================================================*/ 
/* Table: TxOutput                                              */ 
/*==============================================================*/ 
create table TxOutput ( 
   TxId                 int                  not null, 
   Idx                  int                  not null, 
   Amount               bigint               not null, 
   ScriptPubKeyLen      int                  not null, 
   ScriptPubKey         varchar(8000)        not null, 
   Address              char(58)             null, 
   IsUnspendable        bit                  not null, 
   IsPayToScriptHash    bit                  not null, 
   IsValid              bit                  not null, 
   IsSpent              bit                  not null, 
   constraint PK_TXOUTPUT primary key (TxId, Idx) 
) 
go

/*==============================================================*/ 
/* Index: Relationship_3_FK                                     */ 
/*==============================================================*/ 
create index Relationship_3_FK on TxOutput ( 
TxId ASC 
) 
go

alter table Trans 
   add constraint FK_TRANS_RELATIONS_BLOCK foreign key (BlkId) 
      references Block (BlkId) 
go

alter table TxInput 
   add constraint FK_TXINPUT_RELATIONS_TRANS foreign key (TxId) 
      references Trans (TxId) 
go

alter table TxOutput 
   add constraint FK_TXOUTPUT_RELATIONS_TRANS foreign key (TxId) 
      references Trans (TxId) 
go

三、导出区块链数据为CSV

数据模型有了,接下来我们就是建立对应的表,然后写程序将比特币的Block写入到数据库中。我本来用的是EntityFramework来实现插入数据库的操作。但是后来发现实在太慢,插入一个Block甚至要等10多20秒,这要等到何年何月才能插入完啊!我试了各种方案,比如写原生的SQL,用事务,用LINQToSQL等,性能都很不理想。最后终于找到了一个好办法,那就是直接导出为文本文件(比如CSV格式),然后用SQL Server的Bulk Insert命令来实现批量导入,这是我已知的最快的写入数据库的方法。

解析Bitcoin Core下载下来的所有比特币区块链数据用的还是NBitcoin这个开源库。只需要用到其中的BlockStore 类,即可轻松实现区块链数据的解析。

以下是我将区块链数据解析为我们的Block对象的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static void LoadBlock2DB(string localPath, int start) 
{ 
    var store = new BlockStore(localPath, Network.Main); 
    int i = -1; 
    BlockToCsvHelper helper = new BlockToCsvHelper(height);

    foreach (var block in store.Enumerate(false)) 
    { 
        i++; 
        if (i < start) 
        { 
            continue; 
        }

        try 
         { 
            log.Debug("Start load Block " + i + ": " + block.Item.Header + " from file:" +  block.BlockPosition.ToString()); 
            var blk = LoadBlock(block, i);//将NBitcoin的Block转换为我们建模的Block对象 
            helper.WriteBitcoin2Csv(blk);//将我们的Block对象转换为CSV保存 
        } 
        catch (Exception ex) 
         { 
            log.Error("保存Block到数据库时异常,请手动载入,i=" + i, ex); 
        }

    } 
    Console.WriteLine("--------End-----------"); 
    Console.ReadLine(); 
}


private static Block LoadBlock(StoredBlock block, int i) 
{ 
    var blk = new Block() 
    { 
         BlkId = block.Item.Header.ToString(), 
        Difficulty = block.Item.Header.Bits.Difficulty, 
        Bits = block.Item.Header.Bits.ToString(), 
        Height = i, 
        Nonce = block.Item.Header.Nonce, 
        PreId = block.Item.Header.HashPrevBlock.ToString(), 
        TxMerkleRoot = block.Item.GetMerkleRoot().ToString(), 
        Size = block.Item.GetSerializedSize(), 
        Version = block.Item.Header.Version, 
        Timestamp = block.Item.Header.BlockTime.UtcDateTime, 
        TxCount = block.Item.Transactions.Count 
    }; 
    log.Debug("Transaction Count=" + block.Item.Transactions.Count); 
    foreach (var transaction in block.Item.Transactions) 
    { 
        var tx = new Trans() 
         { 
            BlkId = blk.BlkId, 
            TxHash = transaction.GetHash().ToString(), 
            Version = (int)transaction.Version, 
            InputCount = transaction.Inputs.Count, 
             OutputCount = transaction.Outputs.Count, 
            TotalOutAmount = transaction.TotalOut.Satoshi, 
            TransTime = blk.Timestamp, 
            IsCoinbase = transaction.IsCoinBase, 
            IsHeightLock = transaction.LockTime.IsHeightLock, 
            IsTimeLock = transaction.LockTime.IsTimeLock, 
             LockTimeValue = (int)transaction.LockTime.Value, 
             Size = transaction.GetSerializedSize() 
        }; 
        blk.Trans.Add(tx); 
        for (var idx = 0; idx < transaction.Inputs.Count; idx++) 
        { 
            var input = transaction.Inputs[idx]; 
            var txInput = new TxInput() 
             { 
                PaymentScript = input.ScriptSig.ToString(), 
                PaymentScriptLen = input.ScriptSig.Length, 
                 PrevOutTxId = input.PrevOut.Hash.ToString(), 
                 PrevOutIndex = (int)input.PrevOut.N, 
                Trans = tx, 
                Idx = idx 
            };

            if (!tx.IsCoinbase) 
            { 
                var addr = input.ScriptSig.GetSignerAddress(Network.Main); 
                if (addr != null) 
                { 
                    txInput.Address = addr.ToString(); 
                } 
            } 
             if (txInput.PaymentScript.Length > 8000) 
            { 
                 log.Error("Transaction Input PaymentScript异常,将被截断,TxHash: " + tx.TxHash); 
                txInput.PaymentScript = txInput.PaymentScript.Substring(0, 7999); 
            } 
            tx.TxInput.Add(txInput); 
        } 
        for (var idx = 0; idx < transaction.Outputs.Count; idx++) 
        { 
            var output = transaction.Outputs[idx]; 
            var txOutput = new TxOutput() 
            { 
                Amount = output.Value.Satoshi, 
                ScriptPubKey = output.ScriptPubKey.ToString(), 
                ScriptPubKeyLen = output.ScriptPubKey.Length, 
                Trans = tx, 
                IsUnspendable = output.ScriptPubKey.IsUnspendable, 
                IsPayToScriptHash = output.ScriptPubKey.IsPayToScriptHash, 
                IsValid = output.ScriptPubKey.IsValid, 
                Idx = idx

            }; 
            if (txOutput.ScriptPubKey.Length > 8000) 
            { 
                log.Error("Transaction Output ScriptPubKey异常,将被截断,TxHash: " + tx.TxHash); 
                txOutput.ScriptPubKey = txOutput.ScriptPubKey.Substring(0, 7999); 
            } 
            if (!output.ScriptPubKey.IsUnspendable) 
            { 
                if (output.ScriptPubKey.IsPayToScriptHash) 
                { 
                    txOutput.Address = output.ScriptPubKey.GetScriptAddress(Network.Main).ToString(); 
                } 
                else 
                { 
                     var addr = output.ScriptPubKey.GetDestinationAddress(Network.Main); 
                    if (addr == null) 
                     { 
                        var keys = output.ScriptPubKey.GetDestinationPublicKeys(); 
                        if (keys.Length == 0) 
                        { 
                             //异常 
                            log.Warn("Transaction Output异常,TxHash: " + tx.TxHash); 
                        } 
                         else 
                        { 
                             addr = keys[0].GetAddress(Network.Main); 
                         } 
                    } 
                    if (addr != null) 
                    { 
                        txOutput.Address = addr.ToString(); 
                    } 
                 } 
            } 
            tx.TxOutput.Add(txOutput); 
         } 
    } 
    return blk; 
}

至于WriteBitcoin2Csv方法,就是以一定的格式,把Block、Trans、TxInput、TxOutput这4个对象分别写入4个文本文件中即可。

四、将CSV导入SQL Server

在完成了CSV文件的导出后,接下来就是怎么将CSV文件导入到SQL Server中。这个很简单,只需要执行BULK INSERT命令。比如这是我在测试的时候用到的SQL语句:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bulk insert [Block] from 'F:\temp\blk205867.csv'; 
bulk insert Trans from 'F:\temp\trans205867.csv'; 
bulk insert TxInput from 'F:\temp\input205867.csv'; 
bulk insert TxOutput from 'F:\temp\output205867.csv';

当然在实际的情况中,我并不是这么做的。我是每1000个Block就生成4个csv文件,然后使用C#连接到数据库,执行bulk insert命令。执行完成后再把这生成的4个csv文件删除,然后再循环继续导出下一批1000个Block。因为比特币的区块链数据实在太大了,如果我不分批,那么我的PC机硬盘就不够用了,而且在导入SQL Server的时候我也怀疑能不能导入那么大批量的数据。

最后,附上一张我正在导入中的进程图,已经导了一天了,还没有完成,估计还得再花一、两天时间吧。

所有区块链数据都进入数据库以后,就要发挥一下我的想象力,看能够分析出什么有意思的结果了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-06-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
基于Java语言构建区块链(五)—— 地址(钱包)
在 上一篇 文章当中,我们开始了交易机制的实现。你已经了解到交易的一些非个人特征:没有用户账户,您的个人数据(例如:姓名、护照号码以及SSN(美国社会安全卡(Social Security Card)上的9 位数字))不是必需的,并且不存储在比特币的任何地方。但仍然必须有一些东西能够识别你是这些交易输出的所有者(例如:锁定在这些输出上的币的所有者)。这就是比特币地址的作用所在。到目前为止,我们只是使用了任意的用户定义的字符串当做地址,现在是时候来实现真正的地址了,就像它们在比特币中实现的一样。
王维
2018/04/12
4.5K3
基于Java语言构建区块链(五)—— 地址(钱包)
在区块链上表白——使用C#将一句话放入比特币的区块链上
最近在看区块链和比特币的知识,顺便简单研究了一下BitCoin的脚本语言,发现OP_RETURN这个命令可以在后面放入自己想说的内容,很多侧链啊,公证之类就是利用了这个特性,可以把一句话,或者一个哈希值放在这个命令后面,于是我也想试一试,看看能不能成功。
深蓝studyzy
2022/06/16
8250
在区块链上表白——使用C#将一句话放入比特币的区块链上
基于Java语言构建区块链(六)—— 交易(Merkle Tree)
在这一系列文章的最开始部分,我们提到过区块链是一个分布式的数据库。那时候,我们决定跳过"分布式"这一环节,并且聚焦于"数据存储"这一环节。到目前为止,我们几乎实现了区块链的所有组成部分。在本篇文章中,我们将会涉及一些在前面的文章中所忽略的一些机制,并且在下一篇文章中我们将开始研究区块链的分布式特性。
王维
2018/04/16
1.5K4
基于Java语言构建区块链(六)—— 交易(Merkle Tree)
用NBitcoin进行区块链开发(5)
BTC的区块链(blockchain)存储着许多交易(transaction),transaction简单来讲是指BTC从某个地址到某个地址的转移记录。与我们平常的交易记录方式不太一样,一个交易主要由输入(value input)和输出(value output)构成。
申龙斌
2019/03/12
1.3K0
用 Go 构建一个区块链 -- Part 4: 交易(1)
翻译的系列文章我已经放到了 GitHub 上:blockchain-tutorial,后续如有更新都会在 GitHub 上,可能就不在这里同步了。如果想直接运行代码,也可以 clone GitHub 上的教程仓库,进入 src 目录执行 make 即可。
用户1558438
2018/08/23
4470
使用图数据分析比特币区块链
@TOC[1] Here's the table of contents: •一、比特币是如何运作的,区块链是什么 •1.1、比特币可以用来做什么? •1.2、在哪里可以找到区块链
马超的博客
2022/09/02
1.2K0
使用图数据分析比特币区块链
区块链系统探索之路:比特币核心的编译和探索
前几节我们研究了椭圆曲线,有限域等比特币和区块链所依赖的底层算法。问题在于这些算法不是独立存在,而是作为模块嵌入到整个区块链的体系之中。因此不了解区块链的体系组成或应用场景,那么我们就很难理解这些算法衍生出来的概念或者基于他们的作用,所以本节我们把区块链最原始的模态,也就是比特币核心编译和运行起来,先获得初步感性体验,然后在后面的章节中,我们能更好的明白椭圆曲线,有限域,如何组成钱包地址,为何数据在区块链系统中传输还需要各种奇奇怪怪的数据压缩等问题。
望月从良
2023/09/02
4960
区块链系统探索之路:比特币核心的编译和探索
Demo:第一章:Java实现比特币系统
创建一个maven项目 pom.xml: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org
Java廖志伟
2021/09/22
4200
基于Java语言构建区块链(四)—— 交易(UTXO)
上一篇 文章,我们实现了区块数据的持久化,本篇开始交易环节的实现。交易这一环节是整个比特币系统当中最为关键的一环,并且区块链唯一的目的就是通过安全的、可信的方式来存储交易信息,防止它们创建之后被人恶意篡改。今天我们开始实现交易这一环节,但由于这是一个很大的话题,所以我们分为两部分:第一部分我们将实现区块链交易的基本机制,到第二部分,我们再来研究它的细节。
王维
2018/04/12
2.3K2
基于Java语言构建区块链(四)—— 交易(UTXO)
用 Go 构建一个区块链 -- Part 5: 地址
翻译的系列文章我已经放到了 GitHub 上:blockchain-tutorial,后续如有更新都会在 GitHub 上,可能就不在这里同步了。如果想直接运行代码,也可以 clone GitHub 上的教程仓库,进入 src 目录执行 make 即可。
用户1558438
2018/08/23
9360
java工程师用spring boot和web3j构建以太坊区块链应用
区块链最近IT世界的流行语之一。这项有关数字加密货币的技术,并与比特币一起构成了这个热门的流行趋势。它是去中心化的,不可变的分块数据结构,这是可以安全连接和使用的密码算法。在这种结构中的每一区块通常包含前一个区块的加密哈希,一个时间戳,和交易数据。区块链是点对点管理网络的,并在加入每一个新的块之前进行节点间通信的验证。这是关于区块链的部分理论。简而言之,这是一种技术,它允许我们使用一个去中心化的方式管理双方的交易。现在,问题是我们如何在我们的系统中实现它。
笔阁
2018/09/04
1.8K0
java工程师用spring boot和web3j构建以太坊区块链应用
OP_RETURN誓言墙
我们可以在比特币交易中附加上一个OP_RETURN输出,OP_RETURN可以填入任意字符,可以是誓言,可以是证据,也可以是情人节表白,一经写入,就会永久保存在区块链上,不可删除,不可篡改。
申龙斌
2019/03/07
1.5K0
OP_RETURN誓言墙
一文看懂怎样用 Python 创建比特币交易
比特币价格的上上下下,始终撩动着每一个人无比关切的小心脏。从去年初的 800 美元左右,飞涨到去年底到 19783.21 美元最高点,不到1年,便有将近 25 倍的升值速度。尽管眼下又掉回 8000 多美元的价格,但价格差不多能搞出去年同期一个数量级,币圈人士“过去一年比以往 10 年挣的都多”,已经是不争的事实。 而对区块链开发者来说,据说也已经有拿到年新 500 万的天价。所以“跑步进入区块链”,已经成为不少程序员的共识。但是看过很多远离,我们如何才能迅速上手呢?国外网友 Ken Shirriff 在博
区块链大本营
2018/05/10
4.2K0
区块链系列教程之:比特币的困境
比特币网络是中本聪作为一个实验性的网络提出来并运行的。没想到的是这一个实验性质的网络,居然成了现在虚拟货币世界的龙头老大。这个结局估计是中本聪本人都没有想到过的。
程序那些事
2020/07/08
5550
区块链系列教程之:比特币的困境
比特币核心技术解读
在上一篇文章《区块链基础知识与关键技术》里对区块链的基础知识和关键技术进行了梳理,而比特币是区块链最典型的应用,本文将对比特币核心技术进行解读,如有错漏,欢迎交流指正。
pseudoyu
2023/04/11
1K0
比特币核心技术解读
交易Transaction【区块链生存训练】
日常生活中,我们每天都会与他人进行各种交易,对于“交易”这个概念感觉再熟悉不过了。比如:今天我去吃凉皮,支付给商家5元钱,非常简单吧,通常的交易记录可以是这样的: 付款方收款方金额申龙斌凉皮店老板5 然而,在比特币的区块链里,为了避免双重支付、支持去中心化、挖矿发行货币等,采用了一种完全不同的格式来记录这些交易,通过矿工把这些交易打包并发布在区块链上,它是按币的来源vin和去处vout(或使用条件)来记录的,大概是这样的(注意这是极度简化的常规交易): 输入vin输出vout来源于以前的某笔交易的某项输出支
申龙斌
2018/03/06
1.8K0
交易Transaction【区块链生存训练】
《以太坊白皮书》笔记(1)——比特币介绍
#1 State Transaction System State 在 Bitcoin 当中,"state" 代表了所有被挖出且没有被消费的货币的集合 (UTXO - "unspent transaction outputs"),并且每一个货币都有面值和持有者。Bitcoin 交易:有 1 个或多个输入,包含现有的 UTXO 和由持有者地址相关的私钥签名有 1 个或多个输出, 包含一个新的 UTXO 并更新状态State Transaction Function输入当前状态和一笔交易,输出新的状态或
企鹅号小编
2018/01/30
1K0
《以太坊白皮书》笔记(1)——比特币介绍
用 Go 构建一个区块链 -- Part 7: 网络
翻译的系列文章我已经放到了 GitHub 上:blockchain-tutorial,后续如有更新都会在 GitHub 上,可能就不在这里同步了。如果想直接运行代码,也可以 clone GitHub 上的教程仓库,进入 src 目录执行 make 即可。
用户1558438
2018/08/23
5430
用NBitcoin进行区块链开发(4) : 交易
有些网站还给程序员提供更为方便的Restful的API接口,比如blockchain.info提供的查询交易信息的API接口:
申龙斌
2018/12/29
1.7K0
用NBitcoin进行区块链开发(4) : 交易
1-区块链基础概述
在加密货币应用中,区块链结构的作用就是用作账本,每一个区块都是一页账册,它们相互之间通过哈希值进行连接形成一条完整有序的链表,每个区块的头部哈希是它们的唯一标识。
Ywrby
2022/10/27
1.7K0
1-区块链基础概述
相关推荐
基于Java语言构建区块链(五)—— 地址(钱包)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档