Transactions

如何從 bitcoind 中檢索所有交易?

  • December 13, 2017

我正在研究一個需要解析來自 bitcoind 的所有交易的項目。我一直在使用選項 txindex=1 的 bitcoind,並使用 RPC 呼叫 getRawTransaction 提取交易。它工作,但很慢。

有沒有什麼快速的方法可以從 bitcoind 中提取所有交易?(我不需要按順序拉它們)。

然後我嘗試直接從 bitcoind DB 中讀取,但根據What is the database for? 交易不儲存在levelDB中,只有blockXXX.dat,網路格式,所以我需要解析塊來提取它們,這似乎不太好(我猜孤塊也在那裡,所以會產生問題)。

每個事務進行一次 RPC 呼叫會增加很多成本。

目前尚不清楚“所有交易”的確切含義,但我假設您有一種方法可以辨識您想要交易的“所有區塊”。

然後,您可以將成本減少到每個塊一次 RPC 呼叫,方法是使用 getblock 並將第二個選項 (verbose) 設置為 false (getblock blockhash false),然後自己直接解析原始塊數據。

自己解析原始塊數據需要做一些工作,但如果您已經在處理原始交易數據,那麼我猜您可能已經有了程式碼來進行相關解碼。如果沒有,無論如何設置它並不太難。

作為副作用,請注意,此方法不再需要完整的交易索引 (txindex=1)。

我已經在這篇博文中更詳細地介紹了這個特定問題,因此請查看這篇文章了解更多詳細資訊以及一些範常式式碼。

添加到我上面的評論中,下面是解析整個比特幣區塊鏈並提取原始塊的 Scala 程式碼。它使用 Bitcoinj.jar 庫來進一步解析原始塊。

塊儲存在文件中blkxxxxx.dat。該文件的結構如下

4 | 4 | 80 | TxData | 4 | 4 | 80 | TxData | 4 | 4 | 80 | TxData | ...
  • 前 4 個字節:魔術字節(辨識您所在的網路)
  • 第二個4字節:剩餘塊的字節數
  • 接下來的 80 個字節:塊頭本身
  • Next NumBlockBytes - 80 字節:此塊中的交易數據 [ numTx | TX1 | TX2 | TX3 | … ]

在我的系統中,我能夠在 4 小時內遍歷所有文件(1000+)(沒有驗證或處理塊字節,只是下面的虛擬程式碼)。當時區塊鏈上有大約 140 GB 的數據。也許一些 Scala 大師可以讓它更快。

有趣的是,當我第一次同步 bitcoind 時,它在 6 小時內完成,包括下載和驗證區塊。所以這在 C++ 中會更快。

您還必須處理孤兒。

import java.io._
import java.nio._
import scala.collection.mutable.ArrayBuffer
import org.apache.commons.io.FileUtils
import org.bitcoinj.core._
import org.bitcoinj.params._
import scala.collection.JavaConversions._

object Utils {

 // Used for closing files implicitly
 def using[A <: {def close(): Unit}, B](param: A)(f: A => B): B = try { f(param) } finally { param.close() }

 // this is the method that actually parses the file
 def parseFile(name:String) = { 
   System.gc // large files (around 140 MB each, need to clear memory)
   using(new FileInputStream(name)) {fis =>
     using(new BufferedInputStream(fis)) {bis =>
       var currBlkByte = -1 // which byte of raw block are we reading?
       var currBlk = 0 // which block is currently being read?
       var currBlkSize = -1L // what is the size of block (in bytes)
       var endBlkByte = -1 // which is the ending byte of current block?

       val blkSizeBytes = new ArrayBuffer[Byte] // stores bytes containing data about block size
       val blkBytes = new ArrayBuffer[Byte] // stores bytes of block

       Stream.continually(bis.read).takeWhile(-1 !=).foreach{int =>
         currBlkByte += 1  
         val byte = int.toByte 
         // ignore first 4 bytes (magic bytes), next 4 bytes stores upcoming block's size in little endian
         if (currBlkByte >= 4 && currBlkByte < 8) blkSizeBytes += byte
         if (currBlkByte == 7) { // this byte is the last one encoding block's size
           currBlkSize = ByteBuffer.wrap(blkSizeBytes.toArray).order(ByteOrder.LITTLE_ENDIAN).getInt & 0xFFFFFFFFL;            
           endBlkByte = currBlkSize.toInt + 7 // first 8 bytes for info, remaining encoding block
           blkSizeBytes.clear // clear for next block
         }
         if (currBlkByte > 7) blkBytes += byte  // block data 
         if (currBlkByte == endBlkByte) { // we have reached end of block
           // last block byte
           currBlk += 1 // increment block count
           currBlkByte = -1 // reset
           endBlkByte = -1 // reset
           parseBlk(blkBytes.toArray) // we have block in bytes, lets parse it
           blkBytes.clear // reset
         } 
       }
     }
   }
 }

 val context = new Context(MainNetParams.get) // needed for Bitcoinj v 0.13 and above

 def parseBlk(bytes:Array[Byte]) = { // uses Bitcoinj    
   new Block(MainNetParams.get, bytes).getTransactions.foreach {tx =>
     val hash = tx.getHashAsString
     val inputs = tx.getInputs
     val outputs = tx.getOutputs
     // do something with above
   }
 }
 def getAllFiles(dir:String, extensions:Array[String], recursive:Boolean) = 
   FileUtils.listFiles(new File(dir), extensions, recursive).toArray.map(_.toString)

}
import Utils._

object BlockParser {
 val dir = "/home/user/.bitcoin/blocks"
 //files have names like blk00000.dat, ..., blk01096.dat (last one at time of writing)
 val files = getAllFiles(dir, Array("dat"), false).collect {
   case name if name.contains("blk") => // collect only those file with names like "blkxxxxx.dat"
     val num = name.drop(s"$dir/blk".size).take(5).toInt // (take 5 is based on actual file names)
       (name, num)      
 }.sortBy(_._2).unzip._1 // sort by file number 

 files.foreach(parseFile)
}

引用自:https://bitcoin.stackexchange.com/questions/21375