Wie rufe ich alle Transaktionen von Bitcoin ab?

Ich arbeite an einem Projekt, das alle Transaktionen von Bitcoin analysieren muss. Ich habe Bitcoin mit der Option txindex=1 verwendet und Transaktionen mit dem RPC-Aufruf getRawTransaction abgerufen. Es funktioniert, ist aber sehr langsam.

Gibt es eine schnelle Möglichkeit, alle Transaktionen von Bitcoin abzurufen? (Ich brauche sie nicht in der richtigen Reihenfolge zu ziehen).

Ich habe versucht, dann direkt aus der Bitcoin-DB zu lesen, aber laut Wofür ist die Datenbank? Transaktionen werden nicht in levelDB gespeichert, es gibt sie nur bei blockXXX.dat im Netzwerkformat, also müsste ich den Block parsen, um sie zu extrahieren, was nicht in Ordnung zu sein scheint (ich schätze, dass es auch verwaiste Blöcke gibt, was zu Problemen führen wird ).

Antworten (2)

Das Durchführen eines RPC-Aufrufs pro Transaktion fügt viel Overhead hinzu.

Es ist nicht genau klar, was Sie mit "alle Transaktionen" meinen, aber ich gehe davon aus, dass Sie eine Möglichkeit haben, "alle Blöcke" zu identifizieren, für die Sie die Transaktionen wünschen.

Sie können den Overhead dann auf einen RPC-Aufruf pro Block reduzieren, indem Sie getblock mit der zweiten Option (verbose) auf false (getblock blockhash false) verwenden und dann die rohen Blockdaten direkt selbst parsen.

Es ist ein wenig Arbeit, die rohen Blockdaten selbst zu analysieren, aber wenn Sie bereits mit rohen Transaktionsdaten arbeiten, haben Sie wahrscheinlich bereits den Code, um die entsprechende Dekodierung durchzuführen. Wenn nicht, ist es auf jeden Fall nicht allzu schwer, dies einzurichten.

Beachten Sie als Nebeneffekt, dass bei dieser Methode nicht mehr der vollständige Transaktionsindex (txindex=1) benötigt wird.

Ich habe in diesem Blogbeitrag ausführlicher über dieses spezielle Problem geschrieben , also überprüfen Sie diesen Beitrag auf weitere Details und einige Beispielcodes.

Ich würde empfehlen, den Rohblock von bitcoind zu erhalten und dann bitcoinj zu verwenden, um diesen Block zu analysieren (bei Verwendung von JVM).

Ergänzend zu meinem obigen Kommentar ist unten der Scala-Code zum Parsen der gesamten Bitcoin-Blockchain und zum Extrahieren von Rohblöcken. Es verwendet die Bitcoinj.jar-Bibliothek, um den Rohblock weiter zu analysieren.

Die Blöcke werden in Dateien gespeichert blkxxxxx.dat. Der Aufbau der Datei ist wie folgt :

4 | 4 | 80 | TxData | 4 | 4 | 80 | TxData | 4 | 4 | 80 | TxData | ...
  • Die ersten 4 Bytes: magische Bytes (identifizieren, in welchem ​​Netzwerk Sie sich befinden)

  • Zweite 4 Bytes: die Anzahl der Bytes des verbleibenden Blocks

  • Nächste 80 Bytes: Blockheader selbst

  • Next NumBlockBytes - 80 Bytes: Transaktionsdaten in diesem Block [numTx | Tx1 | Tx2 | Tx3 | ... ]

In meinem System konnte ich alle Dateien (1000+) innerhalb von 4 Stunden durchlaufen (keine Überprüfung oder Verarbeitung von Blockbytes, nur der Dummy-Code unten). Damals befanden sich rund 140 GB Daten auf der Blockchain. Vielleicht können einige Scala-Gurus es schneller machen.

Interessanterweise war die Synchronisierung von Bitcoin zum ersten Mal innerhalb von 6 Stunden abgeschlossen, einschließlich des Herunterladens und Verifizierens der Blöcke. Dies wird also in C++ schneller sein.

Außerdem müssen Sie sich mit Waisenkindern auseinandersetzen.

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)
}