/* * This file is part of the Flowee project * Copyright (C) 2021 Tom Zander * * 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. If not, see . */ #include "BlockMetaData.h" #include #include #include #include #include constexpr int TxRowWidth = 8; constexpr int32_t FEE_INVALID = 0xFFFFFF; // tags used to save our data file with. enum Tags { BlockID = 0, BlockHeight, LegacyIsCTOR, /// the legacy bool about ctor, no longer relevant. WideTransactionDataBlob, /// the legacy one that included the txid hash. DO NOT (re)USE. TransactionDataBlob }; bool BlockMetaData::hasFeesData() const { return first()->fees != FEE_INVALID; } BlockMetaData::BlockMetaData(const Streaming::ConstBuffer &buffer) : m_data(buffer) { Streaming::MessageParser parser(buffer); while (parser.next() != Streaming::EndOfDocument) { if (parser.tag() == BlockID) m_blockId = parser.uint256Data(); else if (parser.tag() == BlockHeight) m_blockHeight = parser.intData(); else if (parser.tag() == TransactionDataBlob) { assert(parser.isByteArray()); m_transactions = parser.bytesDataBuffer(); } } } BlockMetaData::BlockMetaData() { } BlockMetaData BlockMetaData::parseBlock(int blockHeight, const Block &block, const std::vector > > &perTxFees, Streaming::BufferPool &pool) { std::deque txs; Tx::Iterator iter(block); bool endFound = false; bool coinbase = true; TransactionData currentTx; currentTx.scriptTags = 0; uint256 txidBeforeThis; // the txid of the transaction placed before the current in the block uint256 prevTxHash; // a copy from the input size_t chunkIndex = 0; size_t feeIndex = 0; std::deque *chunk = nullptr; if (!perTxFees.empty()) chunk = perTxFees.at(chunkIndex).get(); while (iter.next()) { if (iter.tag() == Tx::End) { if (endFound) // 2nd End means done with entire block break; Tx tx = iter.prevTx(); currentTx.offsetInBlock = tx.offsetInBlock(block); currentTx.fees = 0; if (!coinbase && chunk) { if (chunk->size() <= feeIndex) { // next chunk feeIndex = 0; chunk = nullptr; if (perTxFees.size() > ++chunkIndex) { chunk = perTxFees.at(chunkIndex).get(); assert(!chunk->empty()); } } if (chunk) { int fees = chunk->at(feeIndex++); if (fees >= 0 && fees < FEE_INVALID) // fits in our field currentTx.fees = fees; else currentTx.fees = FEE_INVALID; // -1, fee too heigh for our cache } } else if (coinbase && chunk == nullptr) { currentTx.fees = FEE_INVALID; // -1, mark that we don't have fee info in this BMD } coinbase = false; txs.push_back(currentTx); currentTx.scriptTags = 0; endFound = true; continue; } endFound = false; if (iter.tag() == Tx::PrevTxHash) { prevTxHash = iter.uint256Data(); } else if (iter.tag() == Tx::OutputScript) { const CScript script(iter.byteData()); if (script.IsPayToScriptHash()) { currentTx.scriptTags |= Api::ScriptTag::P2SH; continue; } auto scriptIter = script.begin(); opcodetype type; while (script.GetOp(scriptIter, type)) { switch (type) { case OP_RETURN: currentTx.scriptTags |= Api::ScriptTag::OpReturn; break; case OP_CHECKDATASIG: case OP_CHECKDATASIGVERIFY: currentTx.scriptTags |= Api::ScriptTag::OpCheckDataSig; break; case OP_CHECKSIG: case OP_CHECKSIGVERIFY: currentTx.scriptTags |= Api::ScriptTag::OpChecksig; break; case OP_CHECKMULTISIG: case OP_CHECKMULTISIGVERIFY: currentTx.scriptTags |= Api::ScriptTag::OpCheckmultisig; break; case OP_CHECKLOCKTIMEVERIFY: currentTx.scriptTags |= Api::ScriptTag::OpCheckLockTimeverify; break; default: break; }; } } } pool.reserve(txs.size() * TxRowWidth); for (size_t i = 0; i < txs.size(); ++i) { static_assert(sizeof(TransactionData) == TxRowWidth, "Jump table row size"); memcpy(pool.begin() + i * TxRowWidth, &txs[i], TxRowWidth); } auto txData = pool.commit(txs.size() * TxRowWidth); pool.reserve(txData.size() + 55); Streaming::MessageBuilder builder(pool); builder.add(BlockID, block.createHash()); builder.add(BlockHeight, blockHeight); builder.add(TransactionDataBlob, txData); return BlockMetaData(pool.commit()); } const BlockMetaData::TransactionData *BlockMetaData::findTransaction(int offsetInBlock) const { assert(offsetInBlock > 0); uint32_t oib = static_cast(offsetInBlock); const char *currentTx = m_transactions.begin(); while (currentTx < m_transactions.end()) { auto tx = reinterpret_cast(currentTx); if (tx->offsetInBlock == oib) return tx; if (tx->offsetInBlock > oib) break; currentTx += TxRowWidth; } return nullptr; } const BlockMetaData::TransactionData *BlockMetaData::first() const { return reinterpret_cast(m_transactions.begin()); } int BlockMetaData::txCount() const { return m_transactions.size() / TxRowWidth; } const BlockMetaData::TransactionData *BlockMetaData::tx(int index) const { assert(index >= 0); auto *ptr = m_transactions.begin() + index * sizeof(TransactionData); if (ptr > m_transactions.end() - sizeof(TransactionData)) throw std::runtime_error("Index out of bounds"); return reinterpret_cast(ptr); } Streaming::ConstBuffer BlockMetaData::data() const { return m_data; } int BlockMetaData::blockHeight() const { return m_blockHeight; } uint256 BlockMetaData::blockId() const { return m_blockId; }