Files

308 lines
11 KiB
C++
Raw Permalink Normal View History

/*
2017-11-09 19:34:51 +01:00
* This file is part of the Flowee project
2026-05-13 16:43:23 +02:00
* 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>
2023-12-21 15:13:56 +01:00
#include <memory>
2026-05-13 16:43:23 +02:00
#include <vector>
class CTransaction;
2021-11-02 10:18:24 +01:00
class Block;
class TxTokenizer;
2018-01-15 15:26:12 +00:00
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 {
2024-09-01 20:02:21 +02:00
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
2024-09-01 21:50:51 +02:00
CashTokenCategory = 0x200, ///< 32-bytes hash (uint256)
2024-09-06 22:48:19 +02:00
CashTokenBitfield = 0x400, ///< See Tx::CashTokens
2024-09-01 21:50:51 +02:00
CashTokenCommitment = 0x800, ///< var-length const-buffer
2024-09-06 22:48:19 +02:00
CashTokenAmount = 0x1000, ///< Fungible-token-amount. compact size encoded number
OutputScript = 0x40, ///< var-length const-buffer
LockTime = 0x80, ///< uint32_t
End = 0x100
};
2024-09-06 22:48:19 +02:00
/**
* 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.
};
2026-05-13 16:43:23 +02:00
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;
2018-05-09 10:36:39 +02:00
/**
* @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)
*/
2021-11-02 10:18:24 +01:00
int64_t offsetInBlock(const Block &block) const;
2018-05-09 10:36:39 +02:00
2025-01-06 23:30:46 +01:00
static Tx fromOldTransaction(const CTransaction &transaction, const std::shared_ptr<Streaming::BufferPool> &pool = nullptr);
2018-01-15 15:26:12 +00:00
/**
* @return the bytecount of this transaction.
*/
inline int size() const {
return m_data.size();
}
2026-05-13 16:43:23 +02:00
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.
2021-11-02 10:18:24 +01:00
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;
2026-05-13 16:43:23 +02:00
/// 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;
2019-03-17 22:03:42 +01:00
/// 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;
2019-03-17 22:03:42 +01:00
/// Return the value of the current tag as a 32-bit unsigned int
uint32_t uintData() const;
2019-03-17 22:03:42 +01:00
/// Return the value of the current tag as a 64-bit unsigned int
uint64_t longData() const;
2019-03-17 22:03:42 +01:00
/// 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;
}
2024-09-06 22:48:19 +02:00
/// see Tx::CashTokens
/// returns the CashToken bitfield, or zero.
uint8_t bitfieldData() const;
2026-05-13 16:43:23 +02:00
// 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);
2026-05-13 16:43:23 +02:00
[[deprecated("Use Tx::Iterator::nextOutput() instead")]]
static Output nextOutput(Tx::Iterator &iter);
Output output(int index) const;
2026-05-13 16:43:23 +02:00
2019-03-12 22:46:58 +01:00
/// \internal
inline Streaming::ConstBuffer data() const {
return m_data;
}
2020-01-04 21:08:38 +01:00
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;
2020-01-04 21:08:38 +01:00
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 {
2020-05-27 11:36:28 +02:00
uint64_t x = k.index;
x = x << 32;
x += k.dataFile;
return k.txid.GetCheapHash() ^ x;
}
};
}
#endif