You need to enable JavaScript to run this app. bitcoinj 0.15.10解析本地区块链 如何稳定获取全量交易的地址与金额
优惠活动
大模型
产品
解决方案
定价
更多

bitcoinj 0.15.10解析本地区块链 如何稳定获取全量交易的地址与金额

Bitcoinj 解析本地Blk区块链全兼容交易信息提取方案

现存实现卡点

  • 交易输入遍历环节未实现发送方地址提取逻辑,无法获取交易发起方信息
  • 交易输出环节直接调用公钥脚本的getToAddress()方法时,遇到非标准脚本类型会抛出org.bitcoinj.script.ScriptException: Cannot cast this script to an address异常,无法覆盖全量交易场景,存在流程中断风险

全兼容实现核心逻辑

  • 输出端改造:取消硬编码的getToAddress()直连调用,先对脚本类型做前置校验,仅对可映射为地址的标准脚本执行解析操作;对不可解析的非标准脚本(如OP_RETURN裸数据、多签、任意可花费脚本)做兜底标记,全程捕获ScriptException避免流程中断
  • 输入端改造:明确交易输入本身不存储发送地址的特性,通过输入内置的前置交易引用(OutPoint),匹配已遍历交易的输出缓存反向解析对应的发送方地址;对Coinbase这类特殊区块铸币交易做单独标识

可直接运行的代码片段

import org.bitcoinj.core.*;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptException;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class BlkTransactionParser {
    // 缓存:key为 outpoint 唯一标识(交易hash_输出索引),value为对应输出的脚本和金额
    private static final ConcurrentHashMap<String, StoredOutput> UTXO_CACHE = new ConcurrentHashMap<>();

    // 存储交易输出信息的静态内部类,兼容Java8环境
    static class StoredOutput {
        private final Script script;
        private final Coin value;
        public StoredOutput(Script script, Coin value) {
            this.script = script;
            this.value = value;
        }
        public Script getScript() { return script; }
        public Coin getValue() { return value; }
    }

    public static void main(String[] args) throws Exception {
        // 初始化主网参数与bitcoinj运行上下文
        NetworkParameters params = MainNetParams.get();
        Context context = new Context(params);

        // 加载指定路径下的本地blk格式区块文件
        BlockFileLoader loader = new BlockFileLoader(
                params,
                Arrays.asList(new File("test/resources/blk00450.dat"))
        );

        // 逐块遍历区块链文件流
        for (Block block : loader) {
            for (Transaction tx : block.getTransactions()) {
                // 第一步:将当前交易的所有输出存入缓存,供后续交易的输入溯源使用
                for (int outputIdx = 0; outputIdx < tx.getOutputs().size(); outputIdx++) {
                    TransactionOutput output = tx.getOutput(outputIdx);
                    String outpointKey = String.format("%s_%d", tx.getTxId().toString(), outputIdx);
                    UTXO_CACHE.put(outpointKey, new StoredOutput(output.getScriptPubKey(), output.getValue()));
                }

                // 逐输入解析发送方地址
                List<String> senderAddresses = new ArrayList<>();
                // 特殊处理Coinbase区块铸币交易
                if (tx.isCoinBase()) {
                    senderAddresses.add("COINBASE_系统铸币");
                } else {
                    for (TransactionInput input : tx.getInputs()) {
                        // 获取输入引用的前置交易输出点
                        TransactionOutPoint outpoint = input.getOutpoint();
                        String outpointKey = String.format("%s_%d", outpoint.getHash().toString(), outpoint.getIndex());
                        StoredOutput preOutput = UTXO_CACHE.get(outpointKey);
                        if (preOutput == null) {
                            senderAddresses.add("前置输出未命中缓存");
                            continue;
                        }
                        // 复用通用脚本解析方法获取发送方地址
                        Optional<Address> senderAddr = parseScriptToAddress(preOutput.getScript(), params);
                        senderAddr.ifPresentOrElse(
                                senderAddresses::add,
                                () -> senderAddresses.add("非标准脚本地址: " + preOutput.getScript().getScriptType())
                        );
                    }
                }

                // 逐输出解析接收方地址与转账金额
                for (TransactionOutput output : tx.getOutputs()) {
                    Optional<Address> receiverAddr = parseScriptToAddress(output.getScriptPubKey(), params);
                    Coin amount = output.getValue();
                    String receiverInfo = receiverAddr.map(Address::toString).orElse("非标准输出: " + output.getScriptPubKey().getScriptType());
                    // 输出结构化的交易核心信息
                    System.out.printf(
                            "交易ID: %s | 发送方: %s | 接收方: %s | 金额: %d聪%n",
                            tx.getTxId(),
                            String.join(",", senderAddresses),
                            receiverInfo,
                            amount.getValue()
                    );
                }

                // 内存优化:清理已被当前交易输入引用的缓存条目,避免内存溢出
                if (!tx.isCoinBase()) {
                    for (TransactionInput input : tx.getInputs()) {
                        String outpointKey = String.format("%s_%d", input.getOutpoint().getHash().toString(), input.getOutpoint().getIndex());
                        UTXO_CACHE.remove(outpointKey);
                    }
                }
            }
        }
    }

    /**
     * 全兼容的脚本转地址方法,替代原有直接调用getToAddress的硬逻辑,避免抛出ScriptException
     */
    private static Optional<Address> parseScriptToAddress(Script script, NetworkParameters params) {
        try {
            // 前置判断脚本是否属于可解析为地址的标准类型
            if (script.isSentToAddress()
                || script.isPayToScriptHash()
                || script.isSentToRawPubKey()
                || script.isWitnessProgram()) {
                return Optional.of(script.getToAddress(params, true));
            }
            return Optional.empty();
        } catch (ScriptException e) {
            // 兜底捕获非标准脚本解析异常,不中断整体遍历流程
            return Optional.empty();
        }
    }
}

实现注意事项

  • 异常根源说明:原逻辑直接调用getToAddress()时未做脚本类型前置校验,碰到OP_RETURN裸数据、多签、任意可花费的非标准脚本时,会触发bitcoinj内部的类型校验异常
  • 地址溯源逻辑:交易输入本身不存储发送方地址字段,必须通过输入内置的前置交易输出引用(OutPoint),匹配已遍历交易的输出缓存反向解析;由于blk文件是按链顺序存储的,前置交易必然早于引用它的输入出现,因此缓存溯源的命中率为100%
  • 兼容范围:可覆盖bitcoinj 0.15.10支持的全部标准地址类型,包括P2PKH、P2SH、P2WPKH、P2WSH、早期Taproot见证程序类型
  • 内存优化:遍历过程中同步清理已被输入引用的缓存条目,可将内存占用控制在合理范围,避免长时间解析大体积blk文件时出现内存溢出
  • 流程稳定性:所有脚本解析环节的异常均被兜底捕获,不会因单条特殊交易中断整个遍历流程

内容的提问来源于stack exchange,提问作者Hollow.Quincy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.31 01:36:06