Как получить все транзакции из биткойн?

Я работаю над проектом, который должен анализировать все транзакции из биткойнов. Я использую биткойн, у которого опция txindex=1 и вытягивание транзакций с использованием RPC-вызова getRawTransaction. Работает, но очень медленно.

Есть ли какой-нибудь быстрый способ вывести все транзакции из биткойна? (Мне не нужно тянуть их по порядку).

Я пытался тогда читать напрямую из БД bitcoind, но в соответствии с тем, для чего нужна база данных? транзакции не хранятся в levelDB, они есть только в blockXXX.dat в сетевом формате, поэтому мне нужно будет разобрать блок, чтобы извлечь их, что не кажется нормальным (я предполагаю, что потерянные блоки также там, так что это вызовет проблемы ).

Ответы (2)

Выполнение вызова RPC для каждой транзакции добавляет много накладных расходов.

Не совсем понятно, что вы подразумеваете под «всеми транзакциями», но я предполагаю, что у вас есть способ определить «все блоки», для которых вам нужны транзакции.

Таким образом, вы можете уменьшить накладные расходы до одного вызова RPC на блок, используя getblock со второй опцией (verbose), установленной в false (getblock blockhash false), а затем самостоятельно анализируя необработанные данные блока.

Самостоятельный анализ необработанных данных блока требует некоторой работы, но если вы уже работаете с необработанными данными транзакций, то, я думаю, у вас уже может быть код для соответствующего декодирования. Если нет, в любом случае это не так уж сложно настроить.

В качестве побочного эффекта обратите внимание, что для этого метода больше не требуется полный индекс транзакции (txindex=1).

Я написал об этой конкретной проблеме более подробно в этом сообщении в блоге , так что проверьте этот пост для получения более подробной информации и некоторых примеров кода.

Я бы рекомендовал получить необработанный блок из bitcoind, а затем использовать bitcoinj для анализа этого блока (при использовании JVM).

В дополнение к моему комментарию выше, ниже приведен код 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 | Тх2 | Тх3 | ... ]

В моей системе я смог перебрать все файлы (более 1000) за 4 часа (без проверки или обработки блочных байтов, только фиктивный код ниже). В то время в блокчейне было около 140 ГБ данных. Возможно, некоторые гуру Scala сделают это быстрее.

Интересно, что когда я впервые синхронизировал биткойн, он завершился в течение 6 часов, включая загрузку и проверку блоков. Так что это будет быстрее на С++.

Также вам придется иметь дело с сиротами.

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