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

在接触了比特币和区块链后,我一直有一个想法,就是把所有比特币的区块链数据放入到关系数据库(比如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:

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对象的代码:

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语句:

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的时候我也怀疑能不能导入那么大批量的数据。

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

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

时间: 2024-10-25 21:01:42

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

Bitcoin Cash为什么要对比特币区块链进行分叉Hard fork?

区块链兄弟社区,区块链技术专业问答先行者,中国区块链技术爱好者聚集地 作者:吴寿鹤 来源:区块链兄弟 原文链接:http://www.blockchainbrother.com/article/30 著权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 经过长达几年的讨论之后,比特币扩容问题似乎终于要尘埃落定,但不管最终采取何种方案,总是会有一些用户对结果不甚满意.代码优化隔离见证(SegWit)经过优化发展到了如今的Segwit2x提案,而且距离该方案的激活也只剩几步之遥.但另外

比特币——区块链

区块链: 一种实时记录全部交易的去中心化公开数据库,在区块链上进行支付时全网计算机共同查询区块链数据,共同验证这笔支付交易是否有效.确认支付后将写入区块链并产生一条不可篡改记录. 区块:区块大小是1M,包含父哈希.Merkle根.时间戳.难度目标.随机数.程序上近似每10分钟增加一个BLOCK,block就是当前账本区块,如果矿工运算高于这个时间则下次下调难度,如果低于这个时间则增加难度,难度由当前算力确定. 区块链:每一个网络中的用户不需要完整的blockchain, 不过拥有完整blockc

[转帖]比特币区块链的数据结构解析

比特币区块链的数据结构解析 发布者: 无主之地 发表于: 2015-8-13 18:37 来自: 比巴克 16608 1 分享 http://8btc.com/article-1915-1.html 区块链(数据区块.数据块.数据块链)作为比特币的核心概念,对于理解比特币结构起着至关重要的作用.在<什么是数据区块>中,编者从数据区块分布式交易记录的角度简单的介绍了数据区块的概念.今天,编者从数据区块的程序结构上来详细的分析数据区块里面到底记录了什么信息. 数据区块文件的位置如果你用的是Bitc

第六章 比特币--区块链思想诞生的摇篮

一.比特币项目简介 特点: 去中心化:没有任何独立个体可以对交易进行破坏,任何交易请求都需要大多数参与者的共识 匿名性:账户地址是匿名的,无法从交易信息关联到具体的个体,这也意味着很难进行审计 通胀预防:发行量上限2100万个,无法超发,每四年减半. 1. 比特币大事记 2008.10.31,中本聪发布比特币白皮书 2009.1.3 18:15:05,中本聪挖出第一批50个币 2010.5.21 佛罗里达的程序员用1万个币买了价值25美元的披萨优惠券 2012.11.28 产量第一次减半 201

比特币区块链的局限

很多人说比特币是目前区块链最成功的应用,这么说有一定道理,但更贴合实际的说法是:由于在创造比特币时,并没有现成的.可以支持比特币系统运行的底层技术架构,所以中 本聪创造了区块链.也就是说,中本聪创造区块链的初衷是为了实现一个点对点的电子现金系统.因此,当我们对于区块链的用途有更高的期待时,它的一些局限就体现出来了. 首先,比特币区块链的设计只考虑了比特币的交易,本身并不支持定义其他资产,或是定义复杂的交易逻辑.如果要添加新功能,就要对系统进行升级,然而困难在于,对于比特币 这样的完全去中心化的系

区块链技术公司 看区块链数据如何实现安全共享

区块链技术公司的出现令个人数据掌控权从互联网公司转移到用户自己手中,通过它,用户个人数据可以与个人数字×××相关联,用户可以选择个人数字×××是匿名.或公开,还可以随时随地从任何设备访问区块链应用平台,掌握他们的区块链个人数据. 区块链技术公司以去中心化的节点信息公开.共享让人人掌控自己的个人数据成为可能.举例来说,某人的×××号码在区块链上的信息可能被转换为一串密文.他在房间办理入住时,仅需通过应用将×××号码密文发送给酒店,酒店将信息同区块链应用上的加密数据比对,不需要知道他的任何真实信息,

何玺:区块链的价值不止产比特币,在对未来商业逻辑的重构与创新

徐小平说"区块链革命已经到来.这是一场顺之者昌,逆之者亡的伟大技术革命.它对传统的颠覆,将会比互联网.移动互联网来得更加迅猛.彻底."作为从2012年就开始关注区块链技术爱好者,何玺今天也来聊聊火热的区块链. 一.区块链/比特币的关系 随着比特币的持续火爆,越来越多的人开始知道区块链.甚至有不少人以为比特币就是区块链,而区块链也就是比特币本身.何玺在这里做一下说明. 08年10月,中本聪发布<比特币:一种点对点的电子现金系统>(Bitcoin: A Peer-to-Peer

极比特在区块链503888高度分叉,打造更聪明更隐私比特币

来自极比特基金会的最新消息,UBT将于2018年1月中旬,在BTC区块链高度503888硬分叉.分叉前拥有bitcoin的用户在分叉后自动按1:1获得UBT糖果,3月1日依然持有UBT可获得MTC(糖果比例UBT:MTC=1:100).MTC是硅谷FANG公司及美国一流大学教授牵头的技术团队的颠覆性共享经济区块链底层技术平台MetrOS 项目代币.MetrOS项目将于2018年2月发布白皮书,3月进行ICO,4月开始在全球各大交易所上市交易.极比特UBT的发行总量为21.21百万,预挖21万为极

区块链快速入门(七)——比特币

区块链快速入门(七)--比特币 一.比特币简介 比特币(BitCoin,BTC)是基于区块链技术的一种数字货币实现,比特币网络是历史上首个经过大规模长时间检验的数字货币系统.自2009 年正式上线以来,比特币价格经历了数次的震荡,目前每枚比特币市场价格超过6000 美金,并曾经一度接近20000美金.比特币网络在功能上具有如下特点:A.去中心化没有任何独立个体可以对网络中交易进行破坏,任何交易请求都需要大多数参与者的共识.B.匿名性比特币网络中账户地址是匿名的,无法从交易信息关联到具体的个体,但