#include "BlockProc.h" #include "BTC.h" #include "Util.h" #include "bitcoin/transaction.h" #include #include #include #include #include /*static*/ const QByteArray PreProcessedBlock::staticnull; /// fill this struct's data with all the txdata, etc from a bitcoin CBlock. Alternative to using the second c'tor. void PreProcessedBlock::fill(unsigned blockHeight, const bitcoin::CBlock &b) { if (!header.IsNull() || !txInfos.empty()) clear(); height = blockHeight; header = b.GetBlockHeader(); txInfos.reserve(b.vtx.size()); using HashHasher = BTC::QByteArrayHashHasher; std::unordered_map txHashToIndex; std::unordered_map, HashHasher> hashXOuts, hashXIns; std::unordered_set 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 TxInfo info; info.hash = BTC::Hash2ByteArrayRev(tx->GetHash()); info.nInputs = unsigned(tx->vin.size()); info.nOutputs = unsigned(tx->vout.size()); // 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 info.output0Index = unsigned(outputs.size()); unsigned outN = 0; for (const auto & out : tx->vout) { // save the outputs seen outputs.emplace_back( OutPt{ unsigned(txIdx), outN, out.nValue } ); const size_t outputIdx = outputs.size()-1; if (const auto cscript = out.scriptPubKey; !BTC::IsOpReturn(cscript)) ///< skip OP_RETURN { const HashX hashX = cscript; // add this output to the hashX -> outputs association for later hashXOuts[ hashX ].emplace_back( outputIdx ); hashXsSeen.insert(hashX); } /*else { // OpReturn tracking... opreturns.emplace_back(OpReturn{unsigned(outputIdx), cscript}); }*/ ++outN; } // process inputs if (!tx->vin.empty()) // remember input0Index position for this tx info.input0Index = unsigned(inputs.size()); unsigned inN = 0; for (const auto & in : tx->vin) { inputs.emplace_back(InputPt{ unsigned(txIdx), BTC::Hash2ByteArrayRev(in.prevout.GetTxId()), // .prevoutHash unsigned(in.prevout.GetN()), // .prevoutN {}, // .parentTxOutIdx (start out undefined) }); ++inN; } 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) if (info.output0Index.has_value()) inp.parentTxOutIdx = info.output0Index.value() + inp.prevoutN; // save the index into the `outputs` array where the parent tx to this spend occurred else { assert(0); } 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 const HashX hashX = cscript; 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(); hashXAggregated.emplace_back(std::move(ag)); } // sort hashXAggregated by outputIdx,inputIdx std::sort(hashXAggregated.begin(), hashXAggregated.end(), [](const HashXAggregated &a, const HashXAggregated &b) -> bool { return std::make_pair(a.outs.empty() ? 0 : a.outs.front()+1, a.ins.empty() ? 0 : a.ins.front()+1 ) < std::make_pair(b.outs.empty() ? 0 : b.outs.front()+1, b.ins.empty() ? 0 : b.ins.front()+1 ); }); } QString PreProcessedBlock::toDebugString() const { QString ret; { QTextStream ts(&ret, QIODevice::ReadOnly|QIODevice::Truncate|QIODevice::Text); ts << ""; } return ret; } /// convenience factory static method: given a block, return a shard_ptr instance of this struct PreProcessedBlockPtr /*static*/ PreProcessedBlock::makeShared(unsigned height, const bitcoin::CBlock &block) { return std::make_shared(height, block); }