/* * 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 . */ #ifndef FLOWEE_PRIMITIVES_TX_H #define FLOWEE_PRIMITIVES_TX_H #include #include #include #include class CTransaction; class Block; class TxTokenizer; namespace Streaming { class BufferPool; } /** * @brief The Tx class is a Bitcoin transaction in canonical form. * The Tx object is a thin wrapper around a buffer of data which is known to be a Bitcoin transaction. * * @see CTransaction, Block */ class Tx { public: enum Component { Unset = 0, TxVersion = 1, ///< int PrevTxHash = 2 , ///< 32-bytes hash (uint256) PrevTxIndex = 4, ///< int or uint64_t TxInScript = 8, ///< var-length const-buffer Sequence = 0x10, /// uint32_t OutputValue = 0x20, ///< uint64_t CashTokenCategory = 0x200, ///< 32-bytes hash (uint256) CashTokenBitfield = 0x400, ///< See Tx::CashTokens CashTokenCommitment = 0x800, ///< var-length const-buffer CashTokenAmount = 0x1000, ///< Fungible-token-amount. compact size encoded number OutputScript = 0x40, ///< var-length const-buffer LockTime = 0x80, ///< uint32_t End = 0x100 }; /** * Cash Tokens can be added to a transaction-output, each will * have the following tokens 'flags' made available when * the tag is the CashTOkenBitfield. * * Note that 'batons' can be applied only if the utxo we spend * had it too. * * This is a bitfield, usage is by testing the value from * uint8_t Tx::Iterator::bitfieldData() * against these values. */ enum CashTokens { HasFtAmount = 0x10, ///< Holds Fungible-Tokens. See CashTokenAmount HasNft = 0x20, ///< Holds a single NFT. HasCommitment = 0x40, ///< Holds an NFT commitment. See CashTokenCommitment NftMutableBaton = 1, ///< Allows altering the commitment. NftMintBaton = 2, ///< Allows the creation of new NFTs. }; struct Token { uint256 category; uint8_t bitfield = 0; std::vector commitment; uint64_t amount = 0; bool hasAmount() const { return (bitfield & HasFtAmount) != 0; } bool hasNft() const { return (bitfield & HasNft) != 0; } bool hasCommitment() const { return (bitfield & HasCommitment) != 0; } bool isImmutableNft() const { return hasNft() && (bitfield & 0x0f) == 0; } bool isMutableNft() const { return hasNft() && (bitfield & 0x0f) == NftMutableBaton; } bool isMintingNft() const { return hasNft() && (bitfield & 0x0f) == NftMintBaton; } }; /// creates invalid transaction. Tx(); Tx(const Streaming::ConstBuffer &rawTransaction); Tx(const Tx &other) = default; /** * @brief isValid returns true if it has a known backing memory store. * Notice that this method doesn't do validation of the transaction data. */ inline bool isValid() const { return m_data.isValid(); } /** * Returns the version number of a transaction. */ uint32_t txVersion() const; /** * Hashes the transaction content and returns the sha256 double hash. * The hash is often also called the transaction-ID. */ uint256 createHash() const; /** * for backwards compatibility with existing code this loads the transaction into a CTransaction class. */ CTransaction createOldTransaction() const; /** * @brief offsetInBlock returns the amount of bytes into the block this transaction is positioned. * @param block the block it is supposed to be part of. * @return a simple subtraction of pointers, if the argument block doesn't contain the * tx, the result is unspecified (but typically bad) */ int64_t offsetInBlock(const Block &block) const; static Tx fromOldTransaction(const CTransaction &transaction, const std::shared_ptr &pool = nullptr); /** * @return the bytecount of this transaction. */ inline int size() const { return m_data.size(); } struct Input { uint256 txid; int index = -1; int dataFile = -1; // unused here, but useful for the UnspentOutputDatabase }; class Output { public: Output() = default; explicit Output(const Streaming::ConstBuffer &rawOutputScript, int64_t v); Output(const Output &other) = default; Streaming::ConstBuffer outputScript() const; Streaming::ConstBuffer rawOutputScript() const; int64_t outputValue = -1; bool hasToken() const; Token token() const; // throws if hasToken() returns false Output &operator=(const Output &other) = default; private: friend class Tx; Streaming::ConstBuffer m_rawOutputScript; // -1; uninitialized. 0: no tokens. > 0: we have tokens. int m_outputStart = -1; }; /** * @brief The Iterator class allows one to iterate over a ConstBuffer-backed transaction or block. * The Tx class doesn't have a random-access API for its contents because the class doesn't read * all the data into memory. This makes it significantly faster for many usecases and easier on * memory consumtion which is why Flowee followed this trade-off. * The correct way to find certain transaction data is to start an iterator and find it by 'walking' * over the transaction explicitly. * * Notice that little to no checks are done in the API for correct usage, which means that you could * request the LockTime variable as a uint256Data(), which is a bad idea (possible crash). So be * careful about the data-types you read actually matching the tag(). */ class Iterator { public: /// Constructor Iterator(const Tx &tx); /// This iterator skips the block-header and reads the first transaction. After a Tx::End /// it continues to the next transaction. At the end of the block Tx::Ends will continue /// repeatedly. Iterator(const Block &block, int offsetInBlock = 0); Iterator(const Iterator &other) = delete; Iterator(const Iterator && other); ~Iterator(); /** * @brief next seeks to find the next tag. * @param filter allows you to filter which tags you want to find. You can pass in multiple * enum values OR-ed together. Notice that Tx::End will always implicitly be included * in the filter. * @return The output of 'tag()' * Please be aware that this method can throw a runtime_error should the transaction encounter * partial or missing data. */ Component next(int filter = 0); /// Return the current tag found. Component tag() const; /** * @brief prevTx creates a transaction object should you have gotten to the Tx::End tag. * Its very important to realize this method returns the content from the start of the transaction * to the current location, as such the only way to get a proper full transaction is just after * next() returned Tx::End * * Notice that the returned Tx is a zero-copy instance pointing to the same ConstData as backed by * the original Block. */ Tx prevTx() const; /// Return the value of the current tag as a ConstBuffer. Streaming::ConstBuffer byteData() const; /// Return the output script raw data. /// If the iterator is looking at any token or output script tag, this /// will return the full raw data from the transaction's outputscript. /// This is useful to create a Tx::Token instance from. Streaming::ConstBuffer rawOutputScript() const; /// Return the amount of bytes would be included in the byteData() int dataLength() const; /// Return the value of the current tag as a 32-bit signed int int32_t intData() const; /// Return the value of the current tag as a 32-bit unsigned int uint32_t uintData() const; /// Return the value of the current tag as a 64-bit unsigned int uint64_t longData() const; /// Return the value of the current tag as a 256-bit unsigned 'int' uint256 uint256Data() const; /// (Single) hash the current byte-data with the sha256 algo and return the result. void hashByteData(uint256 &output) const; inline uint256 hashedByteData() const { uint256 b; hashByteData(b); return b; } /// see Tx::CashTokens /// returns the CashToken bitfield, or zero. uint8_t bitfieldData() const; // returns relative position in the current transaction. size_t position() const; /** * This calls next various times to get all the information of the first Ouput from the current position. * @param skip instead of the first output, skip a number of them. * * If there is no next output, the returned Output will have empty defaults like a negative outputValue. * * @code * auto myIter = Tx::Iter(block, offsetInBlock); * // fetch output num 3 * auto output = myIter.nextOutput(2); * @endcode */ Output nextOutput(int skip = 0); void operator=(const Iterator &other) = delete; private: TxTokenizer *d; }; /** * Takes an iterator and finds + returns all the inputs. * The iterator will halt at the first value of an output (Tx::OutputValue) or Tx::End */ static std::list findInputs(Tx::Iterator &iter); [[deprecated("Use Tx::Iterator::nextOutput() instead")]] static Output nextOutput(Tx::Iterator &iter); Output output(int index) const; /// \internal inline Streaming::ConstBuffer data() const { return m_data; } inline bool operator==(const Tx &other) const { return m_data.operator==(other.m_data); } inline bool operator!=(const Tx &other) const { return !operator==(other); } Tx &operator=(const Tx &other) = default; private: Streaming::ConstBuffer m_data; }; bool operator==(const Tx::Input &a, const Tx::Input &b); inline bool operator!=(const Tx::Input &a, const Tx::Input &b) { return !operator==(a, b); } namespace boost { template <> struct hash { std::size_t operator()(const Tx::Input& k) const { uint64_t x = k.index; x = x << 32; x += k.dataFile; return k.txid.GetCheapHash() ^ x; } }; } #endif