2019-12-01 22:36:19 +02:00
# include "BlockProc.h"
# include "BTC.h"
# include "Util.h"
# include "bitcoin/transaction.h"
# include <QTextStream>
# include <algorithm>
# include <set>
# include <unordered_map>
# include <unordered_set>
2019-12-04 23:53:45 +02:00
/* static */ const TxHash BlockProcBase : : nullhash ;
2019-12-01 22:36:19 +02:00
/// fill this struct's data with all the txdata, etc from a bitcoin CBlock. Alternative to using the second c'tor.
2019-12-04 20:02:14 +02:00
void PreProcessedBlock : : fill ( BlockHeight blockHeight , size_t blockSize , const bitcoin : : CBlock & b ) {
2019-12-01 22:36:19 +02:00
if ( ! header . IsNull ( ) | | ! txInfos . empty ( ) )
clear ( ) ;
height = blockHeight ;
2019-12-02 13:50:18 +02:00
sizeBytes = blockSize ;
2019-12-01 22:36:19 +02:00
header = b . GetBlockHeader ( ) ;
2019-12-03 21:53:32 +02:00
estimatedThisSizeBytes = sizeof ( * this ) + size_t ( BTC : : GetBlockHeaderSize ( ) ) ;
2019-12-01 22:36:19 +02:00
txInfos . reserve ( b . vtx . size ( ) ) ;
2019-12-04 20:02:14 +02:00
std : : unordered_map < TxHash , unsigned , HashHasher > txHashToIndex ;
2019-12-01 22:36:19 +02:00
std : : unordered_map < HashX , std : : vector < unsigned > , HashHasher > hashXOuts , hashXIns ;
std : : unordered_set < HashX , HashHasher > hashXsSeen ;
// run through all tx's, build inputs and outputs lists
size_t txIdx = 0 ;
for ( const auto & tx : b . vtx ) {
// copy tx hash data for the tx
2019-12-02 01:29:24 +02:00
TxInfo info ;
info . hash = BTC : : Hash2ByteArrayRev ( tx - > GetHash ( ) ) ;
2019-12-04 20:02:14 +02:00
info . nInputs = IONum ( tx - > vin . size ( ) ) ;
info . nOutputs = IONum ( tx - > vout . size ( ) ) ;
2019-12-01 22:36:19 +02:00
// remember the tx hash -> index association for use later in this function
txHashToIndex [ info . hash ] = unsigned ( txIdx ) ; // cheap copy + cheap hash func. should make this fast.
// process outputs for this tx
if ( ! tx - > vout . empty ( ) )
// remember output0 index for this txindex
2019-12-03 19:09:38 +02:00
info . output0Index . emplace ( unsigned ( outputs . size ( ) ) ) ;
2019-12-01 22:36:19 +02:00
2019-12-05 12:07:27 +02:00
IONum outN = 0 ;
2019-12-01 22:36:19 +02:00
for ( const auto & out : tx - > vout ) {
// save the outputs seen
outputs . emplace_back (
OutPt { unsigned ( txIdx ) , outN , out . nValue }
) ;
2019-12-02 13:50:18 +02:00
estimatedThisSizeBytes + = sizeof ( OutPt ) ;
2019-12-01 22:36:19 +02:00
const size_t outputIdx = outputs . size ( ) - 1 ;
if ( const auto cscript = out . scriptPubKey ;
2019-12-05 12:07:27 +02:00
! BTC : : IsOpReturn ( cscript ) ) ///< skip OP_RETURN
2019-12-01 22:36:19 +02:00
{
2019-12-05 12:07:27 +02:00
const HashX hashX = BTC : : HashXFromCScript ( cscript ) ;
2019-12-01 22:36:19 +02:00
// add this output to the hashX -> outputs association for later
hashXOuts [ hashX ] . emplace_back ( outputIdx ) ;
hashXsSeen . insert ( hashX ) ;
2019-12-05 12:07:27 +02:00
}
else {
+ + nOpReturns ;
} /*//use this clause if you want to actually save/process opreturn scripts:
else {
2019-12-01 22:36:19 +02:00
// OpReturn tracking...
opreturns . emplace_back ( OpReturn { unsigned ( outputIdx ) , cscript } ) ;
} */
+ + outN ;
}
// process inputs
if ( ! tx - > vin . empty ( ) )
// remember input0Index position for this tx
2019-12-03 19:09:38 +02:00
info . input0Index . emplace ( unsigned ( inputs . size ( ) ) ) ;
2019-12-01 22:36:19 +02:00
for ( const auto & in : tx - > vin ) {
2019-12-02 19:03:46 +02:00
// note we do place the coinbase tx here even though we ignore it later on -- we keep it to have accurate indices
2019-12-01 22:36:19 +02:00
inputs . emplace_back ( InputPt {
unsigned ( txIdx ) ,
BTC : : Hash2ByteArrayRev ( in . prevout . GetTxId ( ) ) , // .prevoutHash
2019-12-03 07:20:32 +02:00
uint16_t ( in . prevout . GetN ( ) ) , // .prevoutN
2019-12-02 01:29:24 +02:00
{ } , // .parentTxOutIdx (start out undefined)
2019-12-01 22:36:19 +02:00
} ) ;
2019-12-02 13:50:18 +02:00
estimatedThisSizeBytes + = sizeof ( InputPt ) ;
2019-12-01 22:36:19 +02:00
}
2019-12-02 13:50:18 +02:00
estimatedThisSizeBytes + = sizeof ( info ) + size_t ( info . hash . size ( ) ) ;
2019-12-01 22:36:19 +02:00
txInfos . emplace_back ( std : : move ( info ) ) ;
+ + txIdx ;
}
// shrink inputs/outputs to fit now to conserve memory
inputs . shrink_to_fit ( ) ;
outputs . shrink_to_fit ( ) ;
// at this point we have a partially constructed object. we must run through all the inputs again
// and figure out which if any refer to tx's in this block, and assign those to our hashXIns.
// Also: to save memory on txhash's for such inputs, we make sure the txhash refers to the same underlying
// QByteArray data.
size_t inIdx = 0 ;
for ( auto & inp : inputs ) {
if ( const auto it = txHashToIndex . find ( inp . prevoutHash ) ; it ! = txHashToIndex . end ( ) ) {
// this input refers to a tx in this block!
const auto txIdx = it - > second ;
assert ( txIdx < txInfos . size ( ) & & txIdx < b . vtx . size ( ) ) ;
const TxInfo & info = txInfos [ txIdx ] ;
inp . prevoutHash = info . hash ; //<--- ensure shallow copy that points to same underlying data (saves memory)
2019-12-02 01:29:24 +02:00
if ( info . output0Index . has_value ( ) )
2019-12-03 19:09:38 +02:00
inp . parentTxOutIdx . emplace ( info . output0Index . value ( ) + inp . prevoutN ) ; // save the index into the `outputs` array where the parent tx to this spend occurred
2019-12-02 01:29:24 +02:00
else { assert ( 0 ) ; }
2019-12-01 22:36:19 +02:00
const auto & tx = b . vtx [ txIdx ] ;
assert ( inp . prevoutN < tx - > vout . size ( ) ) ;
if ( const auto cscript = tx - > vout [ inp . prevoutN ] . scriptPubKey ;
! BTC : : IsOpReturn ( cscript ) )
{
// mark this input as touching this hashX
2019-12-05 12:07:27 +02:00
const HashX hashX = BTC : : HashXFromCScript ( cscript ) ;
2019-12-01 22:36:19 +02:00
hashXIns [ hashX ] . emplace_back ( inIdx ) ;
hashXsSeen . insert ( hashX ) ;
}
}
+ + inIdx ;
}
hashXAggregated . reserve ( hashXsSeen . size ( ) ) ;
for ( const auto & hashX : hashXsSeen ) {
HashXAggregated ag ;
ag . hashX = hashX ;
if ( auto it = hashXIns . find ( hashX ) ; it ! = hashXIns . end ( ) )
ag . ins . swap ( it - > second ) ;
if ( auto it = hashXOuts . find ( hashX ) ; it ! = hashXOuts . end ( ) )
ag . outs . swap ( it - > second ) ;
ag . ins . shrink_to_fit ( ) ;
ag . outs . shrink_to_fit ( ) ;
2019-12-02 13:50:18 +02:00
estimatedThisSizeBytes + = sizeof ( ag ) + size_t ( ag . hashX . size ( ) ) + ag . ins . size ( ) * sizeof ( decltype ( ag . ins ) : : value_type ) + ag . outs . size ( ) * sizeof ( decltype ( ag . outs ) : : value_type ) ;
2019-12-01 22:36:19 +02:00
hashXAggregated . emplace_back ( std : : move ( ag ) ) ;
}
2019-12-04 20:02:14 +02:00
sortHashXAggregated ( inputs ) ;
2019-12-01 22:36:19 +02:00
}
QString PreProcessedBlock : : toDebugString ( ) const
{
QString ret ;
{
QTextStream ts ( & ret , QIODevice : : ReadOnly | QIODevice : : Truncate | QIODevice : : Text ) ;
ts < < " <PreProcessedBlock -- "
2019-12-02 13:50:18 +02:00
< < " height: " < < height < < " " < < " size: " < < sizeBytes < < " header_nTime: " < < header . nTime < < " hash: " < < header . GetHash ( ) . ToString ( ) . c_str ( )
2019-12-01 22:36:19 +02:00
< < " nTx: " < < txInfos . size ( ) < < " nIns: " < < inputs . size ( ) < < " nOuts: " < < outputs . size ( ) < < " nScriptHash: " < < hashXAggregated . size ( ) ;
int i = 0 ;
for ( const auto & ag : hashXAggregated ) {
ts < < " (# " < < i < < " - " < < ag . hashX . toHex ( ) < < " - nIns: " < < ag . ins . size ( ) < < " nOuts: " < < ag . outs . size ( ) ;
for ( size_t j = 0 ; j < ag . ins . size ( ) ; + + j ) {
const auto idx = ag . ins [ j ] ;
const auto & theInput [[maybe_unused]] = inputs [ idx ] ;
2019-12-02 01:29:24 +02:00
assert ( theInput . parentTxOutIdx . has_value ( ) & & txHashForOutputIdx ( theInput . parentTxOutIdx . value ( ) ) = = theInput . prevoutHash ) ;
2019-12-01 22:36:19 +02:00
ts < < " {in# " < < j < < " - " < < inputs [ idx ] . prevoutHash . toHex ( ) < < " : " < < inputs [ idx ] . prevoutN
2019-12-04 20:02:14 +02:00
< < " , spent in " < < txHashForInputIdx ( inputs , idx ) . toHex ( ) < < " : " < < numForInputIdx ( inputs , idx ) . value_or ( 999999 ) < < " } " ;
2019-12-01 22:36:19 +02:00
}
for ( size_t j = 0 ; j < ag . outs . size ( ) ; + + j ) {
const auto idx = ag . outs [ j ] ;
ts < < " {out# " < < j < < " - " < < txHashForOutputIdx ( idx ) . toHex ( ) < < " : " < < outputs [ idx ] . outN < < " amt: " < < outputs [ idx ] . amount . ToString ( ) . c_str ( ) < < " } " ;
}
ts < < " ) " ;
+ + i ;
}
/*
ts < < " opreturns: " < < opreturns . size ( ) ;
i = 0 ;
for ( const auto & op : opreturns ) {
ts < < " (# " < < i < < " - " < < txInfos [ outputs [ op . outIdx ] . txIdx ] . hash . toHex ( ) < < " ) " ;
+ + i ;
} */
ts < < " > " ;
}
return ret ;
}
2019-12-02 10:00:22 +02:00
/// convenience factory static method: given a block, return a shard_ptr instance of this struct
2019-12-04 20:02:14 +02:00
/*static*/
PreProcessedBlockPtr PreProcessedBlock : : makeShared ( unsigned height_ , size_t size , const bitcoin : : CBlock & block )
2019-12-02 10:00:22 +02:00
{
2019-12-04 20:02:14 +02:00
return std : : make_shared < PreProcessedBlock > ( height_ , size , block ) ;
2019-12-02 10:00:22 +02:00
}
2019-12-04 16:54:40 +02:00
2019-12-04 20:02:14 +02:00
/* ---- ProcessedBlock ---- */
ProcessedBlock : : CannotResolveInputError : : ~ CannotResolveInputError ( ) { } // for weak vtable warning
/// convenience factory static method: given a block, return a shard_ptr instance of this struct
/*static*/
ProcessedBlockPtr ProcessedBlock : : makeShared ( TxNum txBaseNum , const PreProcessedBlock & ppb , const TxHash2NumResolver & resolverFunc , const UTXOSet & uset ) // may throw
{
auto rawPtr = new ProcessedBlock ( txBaseNum , ppb , resolverFunc , uset ) ; // may throw
return ProcessedBlockPtr ( rawPtr ) ; // didn't throw, return shared_ptr
}
ProcessedBlock : : ProcessedBlock ( TxNum txBaseNum , const PreProcessedBlock & ppb , const TxHash2NumResolver & resolverFunc , const UTXOSet & uset )
: BlockProcBase ( ppb ) , txNum0 ( txBaseNum )
{
inputs . reserve ( ppb . inputs . size ( ) ) ;
estimatedThisSizeBytes - = ppb . inputs . size ( ) * sizeof ( PreProcessedBlock : : InputPt ) ; // reduce the size estimate because we will recompute it below
std : : unordered_map < HashX , std : : optional < unsigned > , HashHasher > hashXRevMap ; // index HashX -> its HashXAggregated index
unsigned i = 0 ;
// build hashX -> ag quick lookup map
for ( auto & ag : hashXAggregated ) {
2019-12-04 23:53:45 +02:00
hashXRevMap [ ag . hashX ] = i + + ;
2019-12-04 20:02:14 +02:00
}
// run through all of the inputs and resolve them to a HashX by consuling the utxo set and the resolverFunc
i = 0 ;
for ( const auto & inp : ppb . inputs ) {
TXO txo ;
txo . u . prevout . n = inp . prevoutN ;
if ( inp . parentTxOutIdx . has_value ( ) ) {
assert ( inp . parentTxOutIdx . value ( ) < outputs . size ( ) ) ;
// prevout was in this block, grab txNum quickly
const auto & prevout = outputs [ inp . parentTxOutIdx . value ( ) ] ;
txo . u . prevout . txNum = txIdx2Num ( prevout . txIdx ) ;
// at this point we know the input in question was already pre-populated in the hashXAggregated structure,
2019-12-04 23:53:45 +02:00
} else if ( i = = 0 ) {
// coinbase input.. skip the scripthash stuff
txo = TXO ( ) ; // mark prevout as "invalid"
2019-12-04 20:02:14 +02:00
} else {
// prevout was not in this block, call the resolverFunc to figure out the 'txNum'
auto opt = resolverFunc ( inp . prevoutHash ) ;
if ( UNLIKELY ( ! opt . has_value ( ) ) ) {
throw CannotResolveInputError (
QString ( " Unable to resolve prevoutHash %1 for input# %2 of txid %3 " )
. arg ( QString ( inp . prevoutHash . toHex ( ) ) )
. arg ( ppb . numForInputIdx ( ppb . inputs , i ) . value_or ( 9999 ) )
. arg ( QString ( ppb . txHashForInputIdx ( ppb . inputs , i ) . toHex ( ) ) ) ) ;
}
txo . u . prevout . txNum = opt . value ( ) ;
// look for utxo in utxo set to find the HashX and Amount.
if ( auto it = uset . find ( txo ) ; UNLIKELY ( it = = uset . end ( ) ) ) {
throw CannotResolveInputError (
QString ( " Unable to resolve utxo %1:%2 in utxo set for input# %3 of txid %4 " )
. arg ( QString ( inp . prevoutHash . toHex ( ) ) )
. arg ( txo . u . prevout . n )
. arg ( ppb . numForInputIdx ( ppb . inputs , i ) . value_or ( 9999 ) )
. arg ( QString ( ppb . txHashForInputIdx ( ppb . inputs , i ) . toHex ( ) ) ) ) ;
} else {
// at this point we know the input in question was from a previous block so we need to populate the
// hashXAggregated structure now. Either by pushing an 'ins' to the end of an existing struct's
// vector, or creating a new
const auto & info = it - > second ;
auto & opt = hashXRevMap [ info . hashX ] ;
if ( ! opt . has_value ( ) ) {
// did not exist -- new hashX
hashXAggregated . emplace_back ( HashXAggregated {
info . hashX , //
{ } , // outs
{ i } , // ins
} ) ;
2019-12-04 23:53:45 +02:00
opt = hashXAggregated . size ( ) - 1 ; // mark new hashX in aggregated structure
2019-12-04 20:02:14 +02:00
estimatedThisSizeBytes + = sizeof ( HashXAggregated ) ;
} else {
hashXAggregated [ opt . value ( ) ] . ins . push_back ( i ) ;
}
estimatedThisSizeBytes + = sizeof ( i ) ;
}
}
// now, push the input into our concrete class's inputs array
inputs . emplace_back ( InputPt {
inp . txIdx , // .txIdx
txo , // .prevOut
} ) ;
estimatedThisSizeBytes + = sizeof ( InputPt ) ;
+ + i ;
}
// all inputs are pushed, we need to update the hashXAggregated structure by sorting it an making sure indices
// are unique
2019-12-04 23:53:45 +02:00
hashXAggregated . shrink_to_fit ( ) ; // shrink capacity -> size
2019-12-04 20:02:14 +02:00
for ( auto & ag : hashXAggregated ) {
// sort each ag structure again
std : : sort ( ag . ins . begin ( ) , ag . ins . end ( ) ) ;
// make sure inputs are unique
2019-12-04 23:53:45 +02:00
auto last = std : : unique ( ag . ins . begin ( ) , ag . ins . end ( ) ) ;
ag . ins . erase ( last , ag . ins . end ( ) ) ;
ag . ins . shrink_to_fit ( ) ; // just in case we grew its capacity too large
2019-12-04 20:02:14 +02:00
}
sortHashXAggregated ( inputs ) ; // sort by txid/output
}
2019-12-04 16:54:40 +02:00
// very much a work in progress. this needs to also consult the UTXO set to be complete. For now we just
// have this here for reference.
std : : vector < std : : unordered_set < HashX , HashHasher > >
ProcessedBlock : : hashXTouchedByTx ( ) const
{
std : : vector < std : : unordered_set < HashX , HashHasher > > ret ( txInfos . size ( ) ) ;
for ( const auto & ag : hashXAggregated ) {
// scan all outputs and add this hashX
for ( const auto outIdx : ag . outs ) {
ret [ outputs [ outIdx ] . txIdx ] . insert ( ag . hashX ) ; // cheap shallow copy
}
// scan all inputs and add this hashX
for ( const auto inIdx : ag . ins ) {
ret [ inputs [ inIdx ] . txIdx ] . insert ( ag . hashX ) ;
}
}
return ret ;
}
2019-12-04 20:02:14 +02:00
QString ProcessedBlock : : toDebugString ( const Num2TxHashResolver & resolver , const UTXOSet & uset ) const
{
// TODO: implement...
QString ret ;
{
QTextStream ts ( & ret , QIODevice : : ReadOnly | QIODevice : : Truncate | QIODevice : : Text ) ;
ts < < " <ProcessedBlock -- "
< < " height: " < < height < < " " < < " size: " < < sizeBytes < < " header_nTime: " < < header . nTime < < " hash: " < < header . GetHash ( ) . ToString ( ) . c_str ( )
< < " nTx: " < < txInfos . size ( ) < < " nIns: " < < inputs . size ( ) < < " nOuts: " < < outputs . size ( ) < < " nScriptHash: " < < hashXAggregated . size ( ) ;
int i = 0 ;
for ( const auto & ag : hashXAggregated ) {
ts < < " (# " < < i < < " - " < < ag . hashX . toHex ( ) < < " - nIns: " < < ag . ins . size ( ) < < " nOuts: " < < ag . outs . size ( ) ;
for ( size_t j = 0 ; j < ag . ins . size ( ) ; + + j ) {
const auto idx = ag . ins [ j ] ;
const auto & theInput [[maybe_unused]] = inputs [ idx ] ;
const auto & inp = inputs [ idx ] ;
QByteArray h = resolver ( inp . prevOut . txNum ( ) ) . value_or ( " " ) . toHex ( ) ;
QString amt = " prevOutAmount: ??? " ;
if ( h . isEmpty ( ) ) {
h = QString ( " <NOTFOUND TxNum: %1> " ) . arg ( inp . prevOut . txNum ( ) ) . toUtf8 ( ) ;
}
if ( auto it = uset . find ( inp . prevOut ) ; it ! = uset . end ( ) ) {
amt = QString ( " prevOutAmount: %1 height: %2 " ) . arg ( it - > second . amount . ToString ( ) . c_str ( ) ) . arg ( int ( it - > second . confirmedHeight . value_or ( 0 ) ) - 1 ) ;
}
ts < < " {in# " < < j < < " - " < < h < < " : " < < inp . prevOut . N ( )
< < " , spent in " < < txHashForInputIdx ( inputs , idx ) . toHex ( ) < < " : " < < numForInputIdx ( inputs , idx ) . value_or ( 999999 ) < < " } " ;
}
for ( size_t j = 0 ; j < ag . outs . size ( ) ; + + j ) {
const auto idx = ag . outs [ j ] ;
ts < < " {out# " < < j < < " - " < < txHashForOutputIdx ( idx ) . toHex ( ) < < " : " < < outputs [ idx ] . outN < < " amt: " < < outputs [ idx ] . amount . ToString ( ) . c_str ( ) < < " } " ;
}
ts < < " ) " ;
+ + i ;
}
/*
ts < < " opreturns: " < < opreturns . size ( ) ;
i = 0 ;
for ( const auto & op : opreturns ) {
ts < < " (# " < < i < < " - " < < txInfos [ outputs [ op . outIdx ] . txIdx ] . hash . toHex ( ) < < " ) " ;
+ + i ;
} */
ts < < " > " ;
}
return ret ;
}