2019-12-28 23:46:05 +02:00
//
2019-12-28 23:48:24 +02:00
// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash
2019-12-28 23:46:05 +02:00
// Copyright (C) 2019-2020 Calin A. Culianu <calin.culianu@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program (see LICENSE.txt). If not, see
// <https://www.gnu.org/licenses/>.
//
2019-12-01 22:36:19 +02:00
# include "BlockProc.h"
# include "BTC.h"
# include "Util.h"
# include "bitcoin/transaction.h"
2019-12-05 13:02:43 +02:00
# include "robin_hood/robin_hood.h"
2019-12-01 22:36:19 +02:00
# include <QTextStream>
# include <algorithm>
# include <set>
# include <unordered_set>
2019-12-07 15:04:45 +02:00
/* static */ const TxHash PreProcessedBlock : : 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 ( ) ) ;
2020-01-03 00:34:37 +02:00
robin_hood : : unordered_flat_map < TxHash , unsigned , HashHasher , std : : equal_to < TxHash > , 99 > txHashToIndex ; // since we know the size ahead of time here, we can set max_load_factor to 99% and avoid over-allocating the hash table
txHashToIndex . reserve ( b . vtx . size ( ) ) ;
2019-12-01 22:36:19 +02:00
// 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 (
2019-12-11 19:58:52 +02:00
OutPt { unsigned ( txIdx ) , outN , out . nValue , { } }
2019-12-01 22:36:19 +02:00
) ;
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
2019-12-12 00:07:29 +02:00
auto & ag = hashXAggregated [ hashX ] ;
ag . outs . emplace_back ( outputIdx ) ;
2019-12-12 13:15:45 +02:00
if ( auto & vec = ag . txNumsInvolvingHashX ; vec . empty ( ) | | vec . back ( ) ! = txIdx )
2019-12-12 00:07:29 +02:00
vec . emplace_back ( txIdx ) ;
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!
2019-12-12 12:07:18 +02:00
const auto prevTxIdx = it - > second ;
assert ( prevTxIdx < txInfos . size ( ) & & prevTxIdx < b . vtx . size ( ) ) ;
const TxInfo & prevInfo = txInfos [ prevTxIdx ] ;
inp . prevoutHash = prevInfo . hash ; //<--- ensure shallow copy that points to same underlying data (saves memory)
if ( prevInfo . output0Index . has_value ( ) )
inp . parentTxOutIdx . emplace ( prevInfo . 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-11 17:02:42 +02:00
auto & outp = outputs [ inp . parentTxOutIdx . value ( ) ] ;
outp . spentInInputIndex . emplace ( inIdx ) ; // mark the output as spent by this index
2019-12-12 12:07:18 +02:00
const auto & prevTx = b . vtx [ prevTxIdx ] ;
assert ( inp . prevoutN < prevTx - > vout . size ( ) ) ;
if ( const auto cscript = prevTx - > vout [ inp . prevoutN ] . scriptPubKey ; // grab prevOut address
2019-12-01 22:36:19 +02:00
! BTC : : IsOpReturn ( cscript ) )
{
2019-12-12 13:15:45 +02:00
// mark this input as involving this hashX
2019-12-05 12:07:27 +02:00
const HashX hashX = BTC : : HashXFromCScript ( cscript ) ;
2019-12-12 00:07:29 +02:00
auto & ag = hashXAggregated [ hashX ] ;
ag . ins . emplace_back ( inIdx ) ;
2019-12-12 13:15:45 +02:00
if ( auto & vec = ag . txNumsInvolvingHashX ; vec . empty ( ) | | vec . back ( ) ! = inp . txIdx )
2019-12-12 12:07:18 +02:00
vec . emplace_back ( inp . txIdx ) ; // now that we resolved the input's spending address, mark this input's txIdx as having touched this hashX
2019-12-01 22:36:19 +02:00
}
}
+ + inIdx ;
}
2019-12-05 18:19:55 +02:00
for ( auto & [ hashX , ag ] : hashXAggregated ) {
std : : sort ( ag . ins . begin ( ) , ag . ins . end ( ) ) ;
std : : sort ( ag . outs . begin ( ) , ag . outs . end ( ) ) ;
2019-12-12 13:15:45 +02:00
std : : sort ( ag . txNumsInvolvingHashX . begin ( ) , ag . txNumsInvolvingHashX . end ( ) ) ;
auto last = std : : unique ( ag . txNumsInvolvingHashX . begin ( ) , ag . txNumsInvolvingHashX . end ( ) ) ;
ag . txNumsInvolvingHashX . erase ( last , ag . txNumsInvolvingHashX . end ( ) ) ;
2019-12-01 22:36:19 +02:00
ag . ins . shrink_to_fit ( ) ;
ag . outs . shrink_to_fit ( ) ;
2019-12-12 13:15:45 +02:00
ag . txNumsInvolvingHashX . shrink_to_fit ( ) ;
2019-12-05 18:19:55 +02:00
// tally up space usage
2019-12-12 00:07:29 +02:00
estimatedThisSizeBytes + =
sizeof ( ag ) + size_t ( hashX . size ( ) ) + ag . ins . size ( ) * sizeof ( decltype ( ag . ins ) : : value_type )
+ ag . outs . size ( ) * sizeof ( decltype ( ag . outs ) : : value_type )
2019-12-12 13:15:45 +02:00
+ ag . txNumsInvolvingHashX . size ( ) * sizeof ( decltype ( ag . txNumsInvolvingHashX ) : : value_type ) ;
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 ;
2019-12-05 18:19:55 +02:00
for ( const auto & [ hashX , ag ] : hashXAggregated ) {
ts < < " (# " < < i < < " - " < < hashX . toHex ( ) < < " - nIns: " < < ag . ins . size ( ) < < " nOuts: " < < ag . outs . size ( ) ;
2019-12-01 22:36:19 +02:00
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-07 15:04:45 +02:00
< < " , spent in " < < txHashForInputIdx ( idx ) . toHex ( ) < < " : " < < numForInputIdx ( 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
// 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 > >
2019-12-12 13:15:45 +02:00
PreProcessedBlock : : hashXsByTx ( ) const
2019-12-04 16:54:40 +02:00
{
std : : vector < std : : unordered_set < HashX , HashHasher > > ret ( txInfos . size ( ) ) ;
2019-12-05 18:19:55 +02:00
for ( const auto & [ hashX , ag ] : hashXAggregated ) {
2019-12-04 16:54:40 +02:00
// scan all outputs and add this hashX
for ( const auto outIdx : ag . outs ) {
2019-12-05 18:19:55 +02:00
ret [ outputs [ outIdx ] . txIdx ] . insert ( hashX ) ; // cheap shallow copy
2019-12-04 16:54:40 +02:00
}
// scan all inputs and add this hashX
for ( const auto inIdx : ag . ins ) {
2019-12-05 18:19:55 +02:00
ret [ inputs [ inIdx ] . txIdx ] . insert ( hashX ) ;
2019-12-04 16:54:40 +02:00
}
}
return ret ;
}