简介:utxo-dump 是一款用于快照比特币 UTXO 集合的实用工具,面向区块链开发、节点数据分析与链上研究方向的 Python 开发者。它通过 dump.py 脚本读取 Bitcoin Core 的链状态数据,支持指定区块高度、reindex 重建索引、verbose 详细输出等参数,可导出 vmcp.csv 等快照结果,适用于主网与 Testnet 的 UTXO 集分析场景。资源包共 13 个文件,以 8 个 Python 脚本为核心,辅以 txt 依赖说明、md 使用文档、csv 数据样例、gitignore 与 license 等配置文件,整体约 344KB,结构紧凑、便于直接运行与二次开发。其中 chainstate、script、b128 等模块分别承担链状态解析、脚本处理与编码转换职责,配合 README 可快速理解调用方式。目前已有 181 人学习关注,适合需要复现 UTXO 快照流程、研究链状态存储结构或搭建链上数据管道的开发者参考使用。
1. 快照 UTXO 的实用程序:为什么链上分析老手都在找这把“手术刀”
如果你正在做链上数据分析、钱包余额快照或者空投快照,大概率遇到过这种场景:想查某个地址在某个区块高度到底持有哪些 UTXO,翻遍区块浏览器只能一个个点,写 RPC 调用又慢又容易漏。utxo-dump 就是冲着这个痛点来的——它把全节点或本地索引里的 UTXO 集合按指定高度“冻”下来,导成结构化文件,后续想怎么查、怎么算、怎么对账都行。这个工具用 Python 写成,适合做链上数据清洗、余额快照验证、空投名单生成,以及需要“动态决策快照”做回溯分析的场景。它不是给炒币的人看的行情工具,而是给需要把链上状态落成可复现数据集的人用的。下面我从它怎么跑起来、参数怎么调、哪些地方容易翻车,一步步拆开讲。
2. utxo-dump 的取数逻辑:从链状态到结构化快照
2.1 它到底从哪拿数据
utxo-dump 不自己维护全量链数据,它依赖一个已经同步好的节点或者本地索引数据库。常见做法是接 Bitcoin Core 的 RPC 接口,通过gettxoutsetinfo和scantxoutset这类调用把 UTXO 集合拉出来。但直接调 RPC 有个问题:scantxoutset是遍历式的,大链上跑一次可能几十分钟,而且中途断了没有断点续传。utxo-dump 的思路是在这层之上做封装,把扫描结果按地址、按脚本类型、按金额区间做二次归集,最后落成 JSON 或 CSV。
我一般会先确认节点版本和 RPC 端口,因为不同版本的scantxoutset返回字段有差异。比如早期版本返回的unspents里没有height字段,只有txid和vout,那就没法直接按高度过滤,得自己再查一笔交易确认高度。这个坑后面避坑章节会细说。
2.2 快照的粒度选择:全量还是按条件过滤
utxo-dump 支持几种取数模式,选哪种取决于你要解决什么问题:
| 模式 | 触发参数 | 适用场景 | 数据量级 |
|---|---|---|---|
| 全量快照 | --mode full | 做全链 UTXO 集审计 | 数 GB 起 |
| 按地址列表 | --addresses-file | 空投名单核对 | 取决于列表长度 |
| 按金额区间 | --min-value/--max-value | 找“灰尘”或大额持仓 | 中等 |
| 按脚本类型 | --script-type | 只取 P2PKH 或 P2WPKH | 中等 |
全量模式最慢,但一次跑完后续所有分析都不用再碰节点。按地址列表模式最快,适合已知目标地址集合的场景。我通常先用按地址列表跑一遍验证脚本逻辑,确认输出格式没问题,再决定要不要上全量。
2.3 输出格式与字段含义
默认输出是 JSON Lines,每行一个 UTXO 记录。字段包括txid、vout、address、scriptPubKey、amount、height、confirmations。其中height是这笔 UTXO 所在区块高度,confirmations是当前链尖到该高度的确认数。如果你要做“某高度快照”,得用--snapshot-height参数把高于该高度的 UTXO 过滤掉,否则拿到的是当前状态而不是历史状态。
这里有个容易混淆的点:scantxoutset本身扫的是当前 UTXO 集,它不保留历史。你要历史快照,要么节点开了txindex并且你有每个高度的 UTXO 集备份,要么用第三方索引服务。utxo-dump 在文档里写得很清楚,它不创造数据,只是搬运和整理。所以如果你的节点没开txindex,按高度过滤会失败,报TXINDEX_NOT_ENABLED之类的错误。
3. 跑通第一个快照:环境、命令与参数调优
3.1 环境准备与依赖安装
Python 版本建议 3.9 以上,因为用到了dataclasses和asyncio的一些新特性。依赖不多,主要是python-bitcoinrpc或者requests做 RPC 调用,pandas做后续分析可选装。
# 创建虚拟环境,避免污染系统 Python python3 -m venv venv source venv/bin/activate # 安装核心依赖 pip install requests pandas # 如果要从源码跑,克隆后安装 git clone <repo-url> utxo-dump cd utxo-dump pip install -r requirements.txt注意requirements.txt里可能锁定了requests的版本,如果你环境里已经有高版本,建议用虚拟环境隔离,否则容易出现urllib3版本冲突导致 RPC 连接报 SSL 错误。这个坑我踩过两次,后来一律用 venv。
3.2 配置文件与 RPC 连接
utxo-dump 支持命令行传参,也支持配置文件。我习惯用配置文件,因为参数多的时候命令行容易写错。配置文件是 TOML 格式:
# config.toml [rpc] host = "127.0.0.1" port = 8332 user = "your_rpc_user" password = "your_rpc_password" timeout = 120 [snapshot] mode = "addresses" addresses_file = "addresses.txt" snapshot_height = 800000 output = "snapshot.jsonl" batch_size = 500batch_size控制每次 RPC 请求带多少个地址,太大容易超时,太小则请求次数多。我一般设 500,在本地节点上跑比较稳。timeout设 120 秒是给scantxoutset留足时间,如果你节点性能一般,可以调到 300。
3.3 执行快照与进度观察
跑起来就一条命令:
python utxo_dump.py --config config.toml如果不用配置文件,等价命令是:
python utxo_dump.py \ --rpc-host 127.0.0.1 \ --rpc-port 8332 \ --rpc-user your_user \ --rpc-password your_pass \ --mode addresses \ --addresses-file addresses.txt \ --snapshot-height 800000 \ --output snapshot.jsonl \ --batch-size 500跑的时候终端会打印进度,类似Processed 500/12000 addresses, 3421 UTXOs found。如果卡住不动,先看节点是不是在同步,或者 RPC 端口是不是被防火墙拦了。我遇到过节点在同步时scantxoutset直接返回空结果的情况,因为 UTXO 集还没建好。等节点同步完再跑就正常了。
3.4 输出验证与快速统计
跑完后别急着拿数据去用,先做一轮基本校验:
import json from collections import Counter utxos = [] with open("snapshot.jsonl") as f: for line in f: utxos.append(json.loads(line)) print(f"Total UTXOs: {len(utxos)}") print(f"Unique addresses: {len(set(u['address'] for u in utxos))}") print(f"Total amount: {sum(u['amount'] for u in utxos):.8f} BTC") # 按脚本类型分布 types = Counter(u['scriptPubKey']['type'] for u in utxos) for t, c in types.most_common(): print(f"{t}: {c}")这段代码做三件事:统计 UTXO 总数、去重地址数、总金额,以及按脚本类型看分布。如果总金额和你预期差很多,大概率是snapshot_height没生效,或者地址列表里有无效地址被静默跳过了。utxo-dump 对无效地址的处理是记 warning 然后跳过,不会中断整个任务,所以跑完一定要看日志里的 warning 数量。
4. 避坑与排查:那些让你白跑一晚上的细节
4.1 现象:跑完发现 UTXO 数量远少于预期
原因:最常见的是snapshot_height设得比当前链尖还高,或者节点还没同步到那个高度。另一种可能是地址列表里有大量地址在目标高度上确实没有 UTXO,但你以为有。
解决:先用getblockcount确认节点当前高度,确保snapshot_height小于等于它。然后拿几个已知有余额的地址单独跑一遍,确认脚本逻辑没问题。如果地址列表是从别的工具导出的,检查一下有没有多余的空格或换行符导致地址解析失败。
4.2 现象:RPC 报错Work queue depth exceeded
原因:batch_size设太大了,节点处理不过来。Bitcoin Core 对scantxoutset的并发有限制,一次塞太多地址会触发队列深度超限。
解决:把batch_size降到 100 或 200,同时把timeout调大。如果还不行,就在代码里加个time.sleep(0.5)在每次请求之间,牺牲一点速度换稳定性。我一般设 200 加 0.3 秒间隔,跑一晚上能出几百万条 UTXO。
4.3 现象:输出文件里height字段全是 0 或 null
原因:节点没开txindex,或者 utxo-dump 用的 RPC 方法不返回高度信息。scantxoutset在某些版本里返回的unspents确实不带height,需要额外调getrawtransaction去查。
解决:确认bitcoin.conf里有txindex=1,然后重启节点等索引建完。如果不想开txindex,可以在 utxo-dump 配置里加--resolve-height参数,让它对每条 UTXO 单独查高度,但速度会慢很多。我一般建议直接开txindex,一劳永逸。
4.4 现象:内存占用飙升,跑一半被 OOM 杀掉
原因:全量模式下 utxo-dump 会把所有结果先攒在内存里再写文件,UTXO 集大了之后内存扛不住。
解决:用--stream参数开启流式写入,每处理完一批就 flush 到磁盘。另外可以把--output设成按高度分片,比如snapshot_{height}.jsonl,这样单文件不会太大。我跑全量的时候还会用--max-memory 2G限制一下,超了自动触发 GC。
4.5 现象:地址列表里有重复地址,结果里出现重复 UTXO
原因:utxo-dump 默认不去重地址列表,你传了重复地址它就重复查,结果里同一个 UTXO 可能出现多次。
解决:跑之前先用sort -u addresses.txt -o addresses.txt去重。或者在配置里加--dedupe-addresses,让工具自己处理。我习惯在生成地址列表的环节就去重,省得后面再补。
5. 进阶用法:把快照变成可复现的数据集
5.1 按高度分片与增量快照
如果你需要多个高度的快照做对比分析,比如看某地址持仓随时间的变化,可以写个循环脚本:
#!/bin/bash # 对多个高度分别跑快照 for height in 700000 750000 800000 850000; do python utxo_dump.py \ --config config.toml \ --snapshot-height $height \ --output "snapshot_${height}.jsonl" echo "Done height $height" done每个高度一个文件,后续用 pandas 读进来做 diff 很方便。注意每个高度都要重新扫一遍 UTXO 集,如果节点性能一般,建议在低峰期跑,或者用--cache-dir把中间结果缓存下来,避免重复扫描。
5.2 与 pandas 对接做余额聚合
快照文件是 JSON Lines,pandas 可以直接读:
import pandas as pd df = pd.read_json("snapshot_800000.jsonl", lines=True) # 按地址聚合余额 balance = df.groupby("address")["amount"].sum().reset_index() balance.columns = ["address", "balance"] # 筛选余额大于 0.01 的地址 whales = balance[balance["balance"] > 0.01] print(f"Addresses with >0.01 BTC: {len(whales)}") # 导出 CSV 给下游用 whales.to_csv("whales_800000.csv", index=False)这段代码把 UTXO 级别的数据聚合成地址级别的余额,然后筛出大额持仓。如果你要做空投快照,这一步就是核心逻辑。注意amount字段单位是 BTC,不是 satoshi,如果下游系统要 satoshi,记得乘 1e8。
5.3 验证快照完整性的三个检查点
跑完快照后,我一般会做三个检查来确认数据可信:
| 检查项 | 方法 | 预期结果 |
|---|---|---|
| 总金额对账 | 对比gettxoutsetinfo的total_amount | 差异小于 0.001% |
| 地址数对账 | 对比节点gettxoutsetinfo的txouts | 数量级一致 |
| 抽样验证 | 随机抽 10 个地址调getbalance | 余额一致 |
如果总金额差异大,先看是不是snapshot_height没生效,或者地址列表不完整。如果地址数差很多,可能是脚本类型过滤把某些地址排除了。抽样验证最直接,拿几个地址去区块浏览器对一下,心里就有底了。
5.4 一个让我长记性的教训
有次跑一个空投快照,地址列表是从社区收集的,大概 8 万个地址。我图快,batch_size设了 2000,结果跑到一半节点直接无响应,重启后发现有 3000 多个地址没处理到,但日志里只记了 warning,没中断任务。后来我养成了一个习惯:每次跑完快照,先统计输出行数和地址列表行数的比例,如果低于某个阈值(比如 90%),就强制重跑缺失部分。现在我的脚本里都会加一段自动对账逻辑,跑完自动比对输入地址数和输出地址数,不一致就报警。希望这个习惯也能帮到你,少熬一个通宵。
本文还有配套的精品资源,点击获取