65.9K
CodeProject 正在变化。 阅读更多。
Home

NBitcoin Indexer: 一个可扩展且容错的区块链索引器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (7投票s)

2014年9月16日

CC (BY-ND 3.0)

9分钟阅读

viewsIcon

47737

downloadIcon

84

利用 Azure、PowerShell 和 NBitcoin 构建一个容错且可伸缩的区块链索引器

引言

我之前在上一篇文章中,借助我称之为扫描状态 (Scan State) 的方法,介绍了一种跟踪区块链地址余额的方法。

扫描状态 (Scan State) 是一个灵活且可伸缩的想法,但难以使用。而且你需要提前确切知道要跟踪哪个地址。

因此,我决定基于 NBitcoin 创建我自己的比特币索引器。它将允许你通过简单的 API 来查询区块、交易和地址余额。

查询吞吐量高度分区,这使其有可能达到 Troy Hunt 基准测试的吞吐量。你可以在 MSDN 上找到官方数据。

换句话说:最坏情况下每秒 2000 次请求(受分区吞吐量限制),最好情况下每秒 20000 次请求(受存储账户吞吐量限制)。我设计的系统高度分区,因此在大多数情况下,你可以期望达到每秒 20000 次请求。

我做出的设计决策最大化了可伸缩性、幂等性和可靠性,而不是效率。换句话说,不要害怕无序地索引区块链,同时在多个机器上进行,重新索引已索引的内容,并重启崩溃的机器。

就可靠性而言,你可以让多台机器运行相同的索引器,并使用相同的表。由于幂等性,只要至少有一台机器在运行,区块链就会继续被索引。

但请注意:由于 Azure 和你的家庭之间存在高延迟(典型连接下为 30 毫秒),索引器应该直接在 Azure 中的虚拟机上运行(这样延迟降至 2 到 4 毫秒)。对于请求者没有此要求。

在本文中,我假设你对 Bitcoin 架构有很好的了解。你可以查阅我之前的文章以获得快速概述。

架构

NBitcoin Indexer 依赖于 **Bitcoin Core** 实例从网络下载区块。区块由 Bitcoin Core 以多个 *Blk*.dat 文件的形式保存在 block 文件夹中。然后,索引器处理这些文件,提取区块、交易和余额,并发送到 Azure 存储。

image

索引器会在内部文件中跟踪其工作进度,因此如果发生任何错误,你不必重试整个索引过程。

对于本地 Bitcoin Core 和 Azure 表之间的初始同步,索引器需要上传所有交易和区块(中型实例需要 3 小时),但上传所有余额可能需要一段时间(2 天)。

但是,使用 Azure,你可以轻松克隆虚拟机,并预先下载好区块目录,然后让每个本地索引器处理区块目录中的一部分文件。因此,使用 16 台机器,你可以预期(24 * 2)/ 16 = 3 小时,我们将看到实现这一目标的 Azure 细节。

image

原始同步完成后,你就可以丢弃大部分机器了。只要至少有 1 个实例在运行,索引就会继续正常处理。这是因为索引是一个幂等操作,所以多次索引同一个区块或交易都不会产生影响。

索引器客户端

客户端使用 IndexerClient 类,它是 Azure Storage 之上的顶层。客户端仅依赖于 Azure Storage 凭据。我打算稍后在其之上开发一个 JSON API 层。

让我们看看客户端可以找到的方法

image

你可以看到不同的结构。你可以查询 4 种结构:Block(区块)、Transactions(交易)、ChainChange(带有其高度的区块头)、AddressEntries(余额)。

ChainChange 仅仅是当前主链的所有区块头的列表。

image

一组 AddressEntries 表示一个余额上的所有操作。

image

但是,请注意,如果父交易尚未索引,AddressEntry.BalanceChange 可能为 nullAddressEntry.BalanceChange 在第一次客户端请求时惰性索引,前提是所有父交易都已索引。因此,请求余额可能需要超过一次 Azure 事务,但最终会趋近于 1

另外,调用 IndexerClient.GetEntries 后,AddressEntry.ConfirmedBlock 始终为 null。这是因为如果发生链重组,此信息可能会更改,因此我不会将确认交易 AddressEntry 的区块保存在 Azure 表中。

要获取已确认的区块,你需要一个本地的 Chain,然后调用 AddressEntry.FetchConfirmedBlock

因此,总而言之,要获取所有已确认的 AddressEntries,你需要以下代码

IndexerClient client = 
   IndexerConfiguration.FromConfiguration().CreateIndexerClient();     //Get the indexer client 
                                                                       //from configuration
AddressEntry[] entries = client.GetEntries(new BitcoinAddress("...")); //Fetch all balance changes
Chain mainChain = new Chain(Network.Main);                             //Create an empty chain
client.GetChainChangesUntilFork(mainChain.Tip, false)                  //Fetch the changes
        .UpdateChain(chain);                                           //Update the chain
var confirmedEntries =
    entries
    .Where(e => e.BalanceChange != null)
    .Select(e => e.FetchConfirmedBlock(chain))
    .Where(e => e.ConfirmedBlock != null)
    .ToList();                                      //Filter only completed and confirmed entries

其中配置文件包含 Azure 的连接信息。

<appSettings>
    <add key="Azure.AccountName" value="…"/>
    <add key="Azure.Key" value="…"/>

Chain 类属于 NBitcoin。第一次调用 GetChainChangesUntilFork 可能需要几分钟,因为它会获取所有区块头(320,000 个)。之后几乎不需要时间,因为只要本地链和 Azure 表中的链发生分叉,枚举就会停止。

你可以将本地链保存到文件中,Chain 类会自动并增量地保存(因此不需要 Chain.Save())。

Chain mainChain = new Chain
(Network.Main, new StreamObjectStream<ChainChange>(File.Open("LocalChain.dat", FileMode.OpenOrCreate)));

最后但同样重要的是,让我们看一下调用 IndexerClient.GetTransaction(id) 时获取的 TransactionEntry 类。

image

AddressEntry 类似,如果父交易尚未索引,SpentTxOuts 可能为 nullSpentTxOuts 在第一次请求时惰性加载,因此第一次请求将花费与父交易数量相同的 Azure 事务次数,但之后每次仅需 1 次。

索引器控制台应用程序

索引器由 NBitcoin.Indexer nuget 包中的 AzureIndexer 类实现。

但是,你很可能会在其控制台应用程序中运行索引器,你可以在此下载。你将找到我们在上一部分讨论过的索引比特币结构的选项:区块、交易、主链和地址(余额)。

对于将索引分散到多台机器上的有趣之处在于 FromBlkBlkCount 选项,它们指定了该实例将处理的 *blk* 文件。

NBitcoin.Indexer 1.0.0.0
Nicolas Dorier c AO-IS 2014
LGPL v3
This tool will export blocks in a blk directory filled by bitcoinq, and index 
blocks, transactions, or accounts into Azure
If you want to show your appreciation, vote with your wallet at 
15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe ;)

  -b, --IndexBlocks          (Default: False) Index blocks into azure blob 
                             container

  --NoSave                   (Default: False) Do not save progress in a 
                             checkpoint file

  -c, --CountBlkFiles        (Default: False) Count the number of blk file 
                             downloaded by bitcoinq

  --FromBlk                  (Default: 0) The blk file where processing will 
                             start

  --CountBlk                 (Default: 999999) The number of blk file that must
                             be processed

  -t, --IndexTransactions    (Default: False) Index transactions into azure 
                             table

  -a, --IndexAddresses       (Default: False) Index bitcoin addresses into 
                             azure table

  -m, --IndexMainChain       (Default: False) Index the main chain into azure 
                             table

  -u, --UploadThreadCount    (Default: -1) Number of simultaneous uploads 
                             (default value is 15 for blocks upload, 30 for 
                             transactions upload)

  -?, --help                 Display this help screen.


NBitcoin.Indexer 1.0.0.22

在运行索引器之前,你需要配置 LocalSettings.config 文件(*blk* 文件夹目录、Azure 凭据以及与本地节点的连接,如下一部分所示),所有机器都将使用相同的配置文件。

请注意,控制台应用程序在索引完所有区块后会退出,因此你需要通过 Windows 任务计划程序将其设置为每分钟左右运行一次。

在 Azure 中安装控制台应用程序

现在,我将向你展示如何在多台机器上运行索引器。以及如何分摊初始同步的负载。第一步是创建 Azure 映像,然后我们将对其进行复制。

你可以通过三种方式完成:使用 Azure 门户 (manage.windowsazure.com)、PowerShell,或者使用一些第三方工具(我用过的 Sourire)。 由于我不擅长解释如何在用户界面中点击,所以我将使用 PowerShell 来完成,这样你就可以根据需要进行脚本化。

首先,直接在此地址下载并安装 PowerShell Azure cmdlet: https://github.com/Azure/azure-sdk-tools/releases

然后启动 PowerShell。并通过运行以下命令下载你的订阅的登录信息

Get-AzurePublishSettingsFile

然后使用以下命令导入

Import-AzurePublishSettingsFile "pathToSettings.publishsettings"

然后,我将保存创建机器所需的所有配置设置

$serviceName = "nbitcoinservice"	#Name of the machine
$storageAccount = "nbitcoinindexer"	#Where to save
$machineLogin = "BitcoinAdmin"
$machinePassword = "vdspok9_EO"
$cloneCount = 16

现在,我们需要创建一个新的存储账户和容器,它将保存所有磁盘驱动器和索引数据。(本地冗余存储 (Locally Redundant Storage) 是虚拟机的首选)

$subscriptionName = (Get-AzureSubscription)[0].SubscriptionName
New-AzureStorageAccount -StorageAccountName $storageAccount -Location "West Europe" -Type "Standard_LRS"
Set-AzureSubscription -SubscriptionName $subscriptionName -CurrentStorageAccountName $storageAccount
New-AzureStorageContainer -Container vhds

现在,我们需要创建虚拟机的配置,快速查看可用映像后,我找到了一个有趣的映像名称。

Get-AzureVMImage | Out-GridView 

我选择了 a699494373c04fc0bc8f2bb1389d6106__Windows-Server-2012-R2-201408.01-en.us-127GB.vhd

$computerName = $serviceName.Substring(0,[System.Math]::Min(15,$serviceName.Length)) 
#trunk computer name
New-AzureVMConfig -Name $computerName -InstanceSize "Basic_A2" -MediaLocation 
("https://"+ $storageAccount +".blob.core.windows.net/vhds/"+ $serviceName +"-system.vhd") 
-ImageName a699494373c04fc0bc8f2bb1389d6106__Windows-Server-2012-R2-201408.01-en.us-127GB.vhd | 
#What image, what config, where to save 
Add-AzureProvisioningConfig -Windows -AdminUsername $machineLogin -Password $machinePassword 
-EnableWinRMHttp | #What log/pass and allow powershell
Add-AzureDataDisk -CreateNew -DiskSizeInGB 500 -MediaLocation ("https://"+ $storageAccount 
+".blob.core.windows.net/vhds/"+ $serviceName +"-data.vhd") -DiskLabel bitcoindata -LUN 0 | 
#attach a data disk (we will save the blockchain on this one)
New-AzureVM -ServiceName $serviceName -Location "West Europe" #Make it so !
Get-AzureRemoteDesktopFile -ServiceName $serviceName -Name $computerName 
-LocalPath ($serviceName + ".rdp")
explorer ($serviceName + ".rdp") #Lazy wait to open folder where the rdp file is saved

虚拟机启动后,通过 RDP 文件连接到它。使用 diskmgmt 格式化你的数据磁盘。下载并安装 Bitcoin Core。然后创建一个 ps1(或批处理)文件来运行它(其中 E: 是我的数据驱动器)

& "C:\Program Files (x86)\Bitcoin\daemon\bitcoind.exe"  -conf=E:\bitcoin.conf

我的 bitcoind 配置文件如下

server=1
rpcuser=bitcoinrpc
rpcpassword=7fJ486SgNrajREUEtrhjYqhtzdHvf5L81LmgaDJEA7z
datadir=E:\Bitcoin

别忘了在 E: 中创建 E:\Bitcoin(如果 E: 是附加驱动器的盘符)。运行 bitcoin qt 并耐心等待区块链完全同步(可能需要几天)。

然后下载 NBitcoin.Indexer.Console,解压缩并修改 LocalSettings.config

<?xml version="1.0" encoding="utf-8" ?>
<appSettings>
    <add key="BlockDirectory" value="E:\Bitcoin\blocks"/>
    <add key="Azure.AccountName" value="nbitcoinindexer"/>
    <add key="Azure.Key" value="accountkey"/>
    
    <!--Prefix used before container and azure table (alpha num, optional, ex : prod)-->
    <add key="StorageNamespace" value=""/>
    
    <!--Directory where the indexer keep track of its work (optional)-->
    <add key="MainDirectory" value=""/>
    <!--Connection to local node, only for mempool and current chain indexation (ex : localhost:8333)-->
    <add key="Node" value="localhost:8333"/>
</appSettings>

你可以使用以下 PowerShell 命令从剪贴板中获取 accountkey

(Get-AzureStorageKey nbitcoinindexer).Primary | Out-Clipboard

你现在可以使用 NBitcoin.Indexer.Console 了,这里我索引了区块、交易、地址和主链。

NBitcoin.Indexer.Console.exe -b -t -a -m

可伸缩性和容错性

容错很简单,只需在多个实例上运行相同的命令行和相同的配置文件即可。

但要扩展初始索引,你必须运行几乎相同的命令,除了你将指定每个实例需要处理的 *blk* 文件,如架构部分所述。

请注意,你可以通过以下 PowerShell 脚本连接到之前的实例(警告端口可能不同)

$port = (Get-AzureVM -ServiceName $serviceName | Get-AzureEndpoint PowerShell).Port
$password = ConvertTo-SecureString $machinePassword -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential ($machineLogin, $password)
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck
Enter-PSSession -ConnectionUri ("https://" + $serviceName + ".cloudapp.net:"+$port+"/wsman") 
-Credential $creds -SessionOption $sessionOptions

我们的目标是克隆已同步 Bitcoin Core 的虚拟机 $cloneCount 次。然后,我们将编写一个脚本,在每个克隆的机器上运行索引器,处理 blk 文件夹的不同文件。当然,你可以手动完成,也可以通过脚本完成,我们将通过脚本完成。

首先,我们需要捕获我们机器的映像。

Save-AzureVMImage -ServiceName $serviceName -Name $computerName 
-ImageName $serviceName -OSState Specialized

然后创建克隆(提示:运行命令行,然后去喝杯茶)。

$endpoints =  Get-AzureVM -ServiceName $serviceName | Get-AzureEndpoint
For ($i=0; $i -lt $cloneCount; $i++)
{
$baseNameLen = [System.Math]::Min
(15 - $i.ToString().Length, $computerName.Length + $i.ToString().Length)
$cloneName = $computerName.SubString(0,$baseNameLen) + $i
$vmconfig = New-AzureVMConfig -Name $cloneName -InstanceSize "Basic_A2" -ImageName $serviceName
Foreach ($endpoint in $endpoints)
{
 $vmconfig | Add-AzureEndpoint -Name $endpoint.Name 
-LocalPort $endpoint.LocalPort -PublicPort $endpoint.Port -Protocol $endpoint.Protocol
}
$vmconfig | New-AzureVM -ServiceName ($serviceName + $i) -Location "West Europe"
}

现在,假设文件夹中有 160 个 blk 文件需要索引。那么,机器 i 将从 blk 文件 i 开始索引,并索引 10 个 blk 文件。换句话说,以下命令行

$jobs = @()
$blkCount = 160
$blkPerMachine = [System.Math]::Floor($blkCount / $cloneCount)
For ($i=0; $i -lt $cloneCount; $i++)
{
$password = ConvertTo-SecureString $machinePassword -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential ($machineLogin, $password)
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck
$session = New-PSSession -ConnectionUri 
("https://" + $serviceName + $i + ".cloudapp.net:"+$port+"/wsman") 
-Credential $creds -SessionOption $sessionOptions
$job = Invoke-Command -Session $session -AsJob -ArgumentList $i -Scriptblock {
param($locali)
cd "E:/Indexer.Console" #if you save the NBitcoin Indexer here
NBitcoin.Indexer.Console.exe -b -t -a -m -FromBlk ($locali * $blkPerMachine) -CountBlk $blkPerMachine
}
$jobs = $jobs + $job

}

然后,我们通过将输出写入名为 C:/output $i.txt 的文件来监视这一切。

while($TRUE)
{
$i = 0;

foreach($job in $jobs){
    #this get output of each jobs in log file
    Receive-Job -Job $job 2>&1 >> ("c:\output"+$i+".txt")
    $i++
}

Start-Sleep -s 5
}

当然,这一切都可以手动完成(做 16 次相同的事情并不算太长),而且你只需要在初始索引时做一次。但我自私的原因是我想做一些 Azure 和 PowerShell 的事情,因为它很酷。Sourire

多长时间可以索引完整个区块链?这取决于你准备启动多少台机器。但我估计 16 台机器可以在 3 小时内完成所有索引。

最后一点建议,如果出现任何问题,你很可能需要一个工具来管理你的机器。因此,我建议使用我的第三方工具IaaS Management Studio,它可以更轻松地暂停、连接和删除克隆的磁盘。

结论

我计划改进索引器,支持隐身地址(如果已知扫描密钥)、彩色币支持,然后我将考虑一个解决方案,让你使用自己的扫描器来扩展它。我还会添加一个 JSON API,以便轻松创建像 blockchain.info 这样的 Web 门户。如果你想加速开发,请用你的钱包投票:15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe。Sourire

© . All rights reserved.