Files
tomFlowee ced4f98beb Add token support to the Tx object
We add a Token class to Tx, which adds an easy to use API when compared
to the plain iterator. The main point of the API chosen is to make sure
we don't do unneeded parsing or copying for tokens unless needed.

Additionally refactor the ValidationPrivate::UnspentOutput class to
inherit from the Tx::Output class in order to avoid lots of duplicated
logic on handling the data.
2026-05-13 17:20:32 +02:00

308 lines
11 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (C) 2017-2026 Tom Zander <tom@flowee.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef FLOWEE_PRIMITIVES_TX_H
#define FLOWEE_PRIMITIVES_TX_H
#include <streaming/ConstBuffer.h>
#include <uint256.h>
#include <memory>
#include <vector>
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<uint8_t> 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<Streaming::BufferPool> &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<Input> 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<Tx::Input> {
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