/* * This file is part of the Flowee project * Copyright (C) 2017-2026 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 "Tx.h" #include "Block.h" #include "TxIterator_p.h" #include "transaction.h" #include #include #include #include Tx::Tx() { } Tx::Tx(const Streaming::ConstBuffer &rawTransaction) : m_data(rawTransaction) { } uint32_t Tx::txVersion() const { return static_cast(le32toh(*((uint32_t*)m_data.begin()))); } uint256 Tx::createHash() const { CHash256 ctx; ctx.write((const unsigned char*) m_data.begin(), m_data.size()); uint256 result; ctx.finalize((unsigned char*)&result); return result; } CTransaction Tx::createOldTransaction() const { CTransaction answer; CDataStream buf(m_data.begin(), m_data.end(), 0 , 0); answer.Unserialize(buf, 0, 0); return answer; } int64_t Tx::offsetInBlock(const Block &block) const { assert(m_data.isValid()); assert(block.data().isValid()); assert(block.data().internal_buffer() == m_data.internal_buffer()); return m_data.begin() - block.data().begin(); } Tx Tx::fromOldTransaction(const CTransaction &transaction, const std::shared_ptr &pool) { CSizeComputer sc(0, 0); sc << transaction; if (pool) { pool->reserve(sc.size()); transaction.Serialize(*pool, 0, 0); return Tx(pool->commit()); } Streaming::BufferPool pl(sc.size()); transaction.Serialize(pl, 0, 0); return Tx(pl.commit()); } // static std::list Tx::findInputs(Tx::Iterator &iter) { std::list inputs; Input curInput; auto content = iter.next(); while (content != Tx::End) { if (content == Tx::PrevTxHash) { // only read inputs for non-coinbase txs if (iter.byteData().size() != 32) throw std::runtime_error("Failed to understand PrevTxHash"); curInput.txid = iter.uint256Data(); content = iter.next(Tx::PrevTxIndex); if (content != Tx::PrevTxIndex) throw std::runtime_error("Failed to find PrevTxIndex"); curInput.index = iter.intData(); inputs.push_back(curInput); } else if (content == Tx::OutputValue) { break; } content = iter.next(); } return inputs; } // static Tx::Output Tx::nextOutput(Tx::Iterator &iter) { return iter.nextOutput(); } Tx::Output Tx::output(int index) const { assert(index >= 0); Iterator iter(*this); return iter.nextOutput(index); } //////////////////////////////////////////////////////////// bool static isConstBytes(Tx::Component tag) { return tag == Tx::TxVersion || tag == Tx::LockTime || tag == Tx::PrevTxIndex || tag == Tx::Sequence; } TxTokenizer::TxTokenizer(const Streaming::ConstBuffer &buffer) : m_data(buffer), m_txStart(m_data.begin()), m_currentTokenStart(m_txStart), m_currentTokenEnd(m_txStart), m_tag(Tx::Unset) { } TxTokenizer::TxTokenizer(const Block &block, int offsetInBlock) : m_data(block.data()), m_txStart(nullptr), m_tag(Tx::Unset) { assert(block.isFullBlock()); const char *pos; if (offsetInBlock == 0) { pos = m_data.begin() + 80; pos += readCompactSizeSize(pos); // num tx field } else { pos = m_data.begin() + offsetInBlock; } m_currentTokenStart = pos; m_currentTokenEnd = pos; } Tx::Component TxTokenizer::next() { if (m_currentTokenEnd + 1 >= m_data.end()) { m_tag = Tx::End; return tag(); } m_currentTokenStart = m_currentTokenEnd; if (m_tag == Tx::Unset || m_tag == Tx::End) { m_numInputsLeft = -1; m_numOutputsLeft = -1; m_txStart = m_currentTokenStart; m_currentTokenEnd += 4; m_tag = Tx::TxVersion; return checkSpaceForTag(); } if (m_tag == Tx::TxVersion) { uint64_t x = readCompactSize(&m_currentTokenEnd, m_data.end()); if (x > 0xFFFF) throw std::runtime_error("Tx invalid"); m_numInputsLeft = static_cast(x); // we immediately go to the next token m_currentTokenStart = m_currentTokenEnd; if (0 == m_numInputsLeft) m_tag = Tx::Sequence; // last tag of an input. So we jump to output processing } if (m_numInputsLeft > 0) { if (m_tag == Tx::TxVersion || m_tag == Tx::Sequence) { m_isCashToken = false; m_cashTokenHasCommitment = false; m_cashTokenHasAmount = false; m_currentTokenEnd += 32; m_tag = Tx::PrevTxHash; return checkSpaceForTag(); } if (m_tag == Tx::PrevTxHash) { m_currentTokenEnd += 4; m_tag = Tx::PrevTxIndex; return checkSpaceForTag(); } if (m_tag == Tx::PrevTxIndex) { uint64_t scriptLength = readCompactSize(&m_currentTokenEnd, m_data.end()); if (scriptLength > 0xFFFF) throw std::runtime_error("Tx invalid"); m_currentTokenStart = m_currentTokenEnd; m_currentTokenEnd += static_cast(scriptLength); m_tag = Tx::TxInScript; return checkSpaceForTag(); } if (m_tag == Tx::TxInScript) { m_numInputsLeft--; m_currentTokenEnd += 4; m_tag = Tx::Sequence; return checkSpaceForTag(); } } if (m_tag == Tx::Sequence) { uint64_t x = readCompactSize(&m_currentTokenEnd, m_data.end()); if (x > 0xFFFF) throw std::runtime_error("Tx invalid"); m_numOutputsLeft = static_cast(x); // we immediately go to the next token m_currentTokenStart = m_currentTokenEnd; if (m_numOutputsLeft == 0) // jump over outputs m_tag = Tx::OutputScript; } if (m_numOutputsLeft > 0) { if (m_tag == Tx::Sequence || m_tag == Tx::OutputScript) { m_currentTokenEnd += 8; m_outputScriptStart = nullptr; m_tag = Tx::OutputValue; return checkSpaceForTag(); } if (m_tag == Tx::OutputValue) { m_scriptTokenLength = readCompactSize(&m_currentTokenEnd, m_data.end()); if (m_scriptTokenLength > 0xFFFF) throw std::runtime_error("Tx invalid"); m_currentTokenStart = m_currentTokenEnd; m_outputScriptStart = m_currentTokenStart; m_outputScriptEnd = m_currentTokenStart + m_scriptTokenLength; unsigned char tokenPrefix = static_cast(*m_currentTokenStart); m_isCashToken = (tokenPrefix == 0xef); if (m_isCashToken) { m_scriptTokenLength--; // we immediately go to the next token m_currentTokenEnd++; m_currentTokenStart = m_currentTokenEnd; } } if (m_isCashToken) { if (m_tag == Tx::OutputValue) { m_scriptTokenLength -= 32; m_currentTokenEnd += 32; m_tag = Tx::CashTokenCategory; return checkSpaceForTag(); } if (m_tag == Tx::CashTokenCategory) { // Read the token_bitfield m_scriptTokenLength--; unsigned char bitfield = static_cast(*m_currentTokenStart); m_cashTokenHasCommitment = (bitfield & Tx::HasCommitment); m_cashTokenHasAmount = (bitfield & Tx::HasFtAmount); m_currentTokenEnd += 1; m_tag = Tx::CashTokenBitfield; return checkSpaceForTag(); } if (m_cashTokenHasCommitment && m_tag == Tx::CashTokenBitfield) { // Has a cash token amount (setting in bitfield), and previous tag was CashTokenBitfield uint64_t commitmentLength = readCompactSize(&m_currentTokenEnd, m_data.end()); if (commitmentLength > 0xFFFF) throw std::runtime_error("Commitment invalid"); m_scriptTokenLength -= (m_currentTokenEnd - m_currentTokenStart); m_currentTokenStart = m_currentTokenEnd; m_currentTokenEnd += static_cast(commitmentLength); m_scriptTokenLength -= commitmentLength; m_tag = Tx::CashTokenCommitment; return checkSpaceForTag(); } if (m_cashTokenHasAmount && (m_tag == Tx::CashTokenCommitment || (m_tag == Tx::CashTokenBitfield && !m_cashTokenHasCommitment))) { // Has a cash token amount (setting in bitfield), and, either: // 1) Previous tag was CashTokenCommitment // 2) Previous tag was CashTokenBitfield + it did not have a commitment m_currentTokenEnd += readCompactSizeSize(m_currentTokenStart); m_scriptTokenLength -= (m_currentTokenEnd - m_currentTokenStart); m_tag = Tx::CashTokenAmount; return checkSpaceForTag(); } } { // no if-statement, at this position only the OutputScript is allowed m_numOutputsLeft--; m_currentTokenStart = m_currentTokenEnd; m_currentTokenEnd += static_cast(m_scriptTokenLength); m_tag = Tx::OutputScript; m_isCashToken = false; return checkSpaceForTag(); } } if (m_tag == Tx::OutputScript) { m_currentTokenEnd += 4; m_tag = Tx::LockTime; return checkSpaceForTag(); } if (m_tag == Tx::LockTime) m_tag = Tx::End; else assert(false); return tag(); } Tx::Component TxTokenizer::checkSpaceForTag() { if (m_tag != Tx::End && m_currentTokenEnd > m_data.end()) throw std::runtime_error("Tx data missing"); return tag(); } Tx::Iterator::Iterator(const Tx &tx) : d(new TxTokenizer(tx.m_data)) { } Tx::Iterator::Iterator(const Block &block, int offsetInBlock) : d(new TxTokenizer(block, offsetInBlock)) { } Tx::Iterator::Iterator(const Tx::Iterator && other) { d = std::move(other.d); } Tx::Iterator::~Iterator() { delete d; } Tx::Component Tx::Iterator::next(int filter) { do { int tag = d->next(); if (filter == 0 || (tag & filter)) return static_cast(tag); } while (d->tag() != Tx::End); return Tx::End; } Tx::Component Tx::Iterator::tag() const { return d->tag(); } Tx Tx::Iterator::prevTx() const { assert(d->tag() == Tx::End); return Tx(Streaming::ConstBuffer(d->m_data.internal_buffer(), d->m_txStart, d->m_currentTokenEnd)); } Streaming::ConstBuffer Tx::Iterator::byteData() const { return Streaming::ConstBuffer(d->m_data.internal_buffer(), d->m_currentTokenStart, d->m_currentTokenEnd); } Streaming::ConstBuffer Tx::Iterator::rawOutputScript() const { if (d->m_outputScriptEnd <= d->m_outputScriptStart || d->m_outputScriptEnd > d->m_data.end()) return Streaming::ConstBuffer(); return Streaming::ConstBuffer(d->m_data.internal_buffer(), d->m_outputScriptStart, d->m_outputScriptEnd); } int Tx::Iterator::dataLength() const { return d->m_currentTokenEnd - d->m_currentTokenStart; } int32_t Tx::Iterator::intData() const { if (isConstBytes(d->tag())) return *((int32_t*)(d->m_currentTokenStart)); const char *tmp = d->m_currentTokenStart; return readCompactSize(&tmp, d->m_data.end()); } uint32_t Tx::Iterator::uintData() const { if (isConstBytes(d->tag())) return le32toh(*((uint32_t*)(d->m_currentTokenStart))); const char *tmp = d->m_currentTokenStart; return readCompactSize(&tmp, d->m_data.end()); } uint64_t Tx::Iterator::longData() const { if (d->tag() == OutputValue) return le64toh(*((uint64_t*)(d->m_currentTokenStart))); if (isConstBytes(d->tag())) return le32toh(*((uint32_t*)(d->m_currentTokenStart))); const char *tmp = d->m_currentTokenStart; return readCompactSize(&tmp, d->m_data.end()); } uint256 Tx::Iterator::uint256Data() const { assert (d->m_currentTokenEnd - d->m_currentTokenStart >= 32); uint256 answer(d->m_currentTokenStart); return answer; } void Tx::Iterator::hashByteData(uint256 &output) const { CSHA256 hasher; hasher.write(d->m_currentTokenStart, dataLength()); uint256 buf; hasher.finalize(output.begin()); } uint8_t Tx::Iterator::bitfieldData() const { if (d->m_tag != CashTokenBitfield) return 0; return *d->m_currentTokenStart; } size_t Tx::Iterator::position() const { return d->m_currentTokenStart - d->m_txStart; } Tx::Output Tx::Iterator::nextOutput(int skip) { Output answer; auto content = tag(); if (tag() != Tx::OutputValue) content = next(Tx::OutputValue); while (content != Tx::End) { if (--skip < 0) { // use this one! answer.outputValue = static_cast(longData()); content = next(); size_t start1 = position(); if (content == Tx::CashTokenCategory) { --start1; // since it auto-jumps over the marker. content = next(Tx::OutputScript); // for 'offset()' } if (content != Tx::OutputScript) throw std::runtime_error("Malformed transaction"); answer.m_rawOutputScript = rawOutputScript(); answer.m_outputStart = position() - start1; assert(answer.m_outputStart >= 0); break; } content = next(Tx::OutputValue); } return answer; } bool operator==(const Tx::Input &a, const Tx::Input &b) { return a.index == b.index && a.dataFile == b.dataFile && a.txid == b.txid; } // ------ Tx::Output::Output(const Streaming::ConstBuffer &b, int64_t v) : m_rawOutputScript(b), outputValue(v), m_outputStart(0) { // pre-calc m_outputStart if there is a token. if (hasToken()) { Tx::Token token; const char *t = m_rawOutputScript.begin(); assert(static_cast(*t) == 0xef); token.bitfield = static_cast(t[33]); t += 34; if (token.hasCommitment()) t += readCompactSize(&t, m_rawOutputScript.end()); if (token.hasAmount()) (void) readCompactSize(&t, m_rawOutputScript.end()); m_outputStart = t - m_rawOutputScript.begin(); } } Streaming::ConstBuffer Tx::Output::outputScript() const { assert(m_outputStart > -1); return m_rawOutputScript.mid(m_outputStart); } Streaming::ConstBuffer Tx::Output::rawOutputScript() const { return m_rawOutputScript; } bool Tx::Output::hasToken() const { if (m_rawOutputScript.size() < 34) return false; return static_cast(*m_rawOutputScript.begin()) == 0xef; } Tx::Token Tx::Output::token() const { if (!hasToken()) throw std::runtime_error("No token"); Tx::Token answer; const char *t = m_rawOutputScript.begin(); assert(static_cast(*t) == 0xef); answer.category = uint256(t + 1); answer.bitfield = static_cast(t[33]); t += 34; if (answer.hasCommitment()) { auto length = readCompactSize(&t, m_rawOutputScript.end()); answer.commitment = std::vector(t, t + length); t += length; } if (answer.hasAmount()) answer.amount = readCompactSize(&t, m_rawOutputScript.end()); return answer; }