MWEB: Primitives (CTransaction, CBlock, serialization)

This commit is contained in:
David Burkett 2022-01-29 13:01:42 -05:00 committed by Loshan T
parent 9417feaab4
commit 9d1f530a5f
7 changed files with 559 additions and 9 deletions

View File

@ -203,6 +203,7 @@ BITCOIN_CORE_H = \
merkleblock.h \
miner.h \
mweb/mweb_db.h \
mweb/mweb_models.h \
net.h \
net_permissions.h \
net_processing.h \

214
src/mweb/mweb_models.h Normal file
View File

@ -0,0 +1,214 @@
// Copyright(C) 2011 - 2020 The Litecoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef LITECOIN_MWEB_MODELS_H
#define LITECOIN_MWEB_MODELS_H
#include <amount.h>
#include <mw/models/block/Block.h>
#include <mw/models/tx/Transaction.h>
#include <serialize.h>
#include <tinyformat.h>
#include <memory>
#include <vector>
namespace MWEB {
/// <summary>
/// A convenience wrapper around a possibly-null extension block.
/// </summary>
struct Block {
mw::Block::CPtr m_block;
Block() = default;
Block(const mw::Block::CPtr& block)
: m_block(block) {}
CAmount GetTotalFee() const noexcept
{
return IsNull() ? 0 : m_block->GetTotalFee();
}
CAmount GetSupplyChange() const noexcept
{
return IsNull() ? 0 : m_block->GetSupplyChange();
}
mw::Header::CPtr GetMWEBHeader() const noexcept
{
return IsNull() ? mw::Header::CPtr{nullptr} : m_block->GetHeader();
}
int32_t GetHeight() const noexcept
{
return IsNull() ? -1 : m_block->GetHeight();
}
std::vector<mw::Hash> GetSpentIDs() const
{
if (IsNull()) {
return std::vector<mw::Hash>{};
}
std::vector<mw::Hash> spent_ids;
for (const Input& input : m_block->GetInputs()) {
spent_ids.push_back(input.GetOutputID());
}
return spent_ids;
}
std::vector<mw::Hash> GetOutputIDs() const
{
if (IsNull()) {
return std::vector<mw::Hash>{};
}
std::vector<mw::Hash> output_ids;
for (const Output& output : m_block->GetOutputs()) {
output_ids.push_back(output.GetOutputID());
}
return output_ids;
}
std::set<mw::Hash> GetKernelIDs() const
{
if (IsNull()) {
return std::set<mw::Hash>{};
}
std::set<mw::Hash> kernel_ids;
for (const Kernel& kernel : m_block->GetKernels()) {
kernel_ids.insert(kernel.GetKernelID());
}
return kernel_ids;
}
SERIALIZE_METHODS(Block, obj)
{
READWRITE(WrapOptionalPtr(obj.m_block));
}
bool IsNull() const noexcept { return m_block == nullptr; }
void SetNull() noexcept { m_block.reset(); }
};
/// <summary>
/// A convenience wrapper around a possibly-null MWEB transcation.
/// </summary>
struct Tx {
mw::Transaction::CPtr m_transaction;
Tx() = default;
Tx(const mw::Transaction::CPtr& tx)
: m_transaction(tx) {}
std::set<mw::Hash> GetSpentIDs() const noexcept
{
if (IsNull()) {
return std::set<mw::Hash>{};
}
std::set<mw::Hash> spent_ids;
for (const Input& input : m_transaction->GetInputs()) {
spent_ids.insert(input.GetOutputID());
}
return spent_ids;
}
std::set<mw::Hash> GetKernelIDs() const noexcept
{
if (IsNull()) {
return std::set<mw::Hash>{};
}
std::set<mw::Hash> kernel_ids;
for (const Kernel& kernel : m_transaction->GetKernels()) {
kernel_ids.insert(kernel.GetKernelID());
}
return kernel_ids;
}
std::set<mw::Hash> GetOutputIDs() const noexcept
{
if (IsNull()) {
return std::set<mw::Hash>{};
}
std::set<mw::Hash> output_ids;
for (const Output& output : m_transaction->GetOutputs()) {
output_ids.insert(output.GetOutputID());
}
return output_ids;
}
std::vector<PegInCoin> GetPegIns() const noexcept
{
if (IsNull()) {
return std::vector<PegInCoin>{};
}
return m_transaction->GetPegIns();
}
bool HasPegOut() const noexcept
{
if (IsNull()) {
return false;
}
const std::vector<Kernel>& kernels = m_transaction->GetKernels();
return std::any_of(
kernels.cbegin(), kernels.cend(),
[](const Kernel& kernel) { return kernel.HasPegOut(); }
);
}
std::vector<PegOutCoin> GetPegOuts() const noexcept
{
if (IsNull()) {
return std::vector<PegOutCoin>{};
}
return m_transaction->GetPegOuts();
}
uint64_t GetMWEBWeight() const noexcept
{
return IsNull() ? 0 : m_transaction->CalcWeight();
}
CAmount GetFee() const noexcept
{
return IsNull() ? 0 : CAmount(m_transaction->GetTotalFee());
}
int32_t GetLockHeight() const noexcept
{
return IsNull() ? 0 : m_transaction->GetLockHeight();
}
SERIALIZE_METHODS(Tx, obj)
{
READWRITE(WrapOptionalPtr(obj.m_transaction));
}
bool IsNull() const noexcept { return m_transaction == nullptr; }
void SetNull() noexcept { m_transaction.reset(); }
std::string ToString() const
{
return IsNull() ? "" : m_transaction->Print();
}
};
} // namespace MWEB
#endif // LITECOIN_MWEB_MODELS_H

View File

@ -38,3 +38,13 @@ std::string CBlock::ToString() const
}
return s.str();
}
CTransactionRef CBlock::GetHogEx() const noexcept
{
if (vtx.size() >= 2 && vtx.back()->IsHogEx()) {
assert(!vtx.back()->vout.empty());
return vtx.back();
}
return nullptr;
}

View File

@ -9,6 +9,7 @@
#include <primitives/transaction.h>
#include <serialize.h>
#include <uint256.h>
#include <mweb/mweb_models.h>
/** Nodes collect new transactions into a block, hash them into a hash tree,
* and scan through nonce values to make the block's hash satisfy proof-of-work
@ -70,6 +71,8 @@ public:
// memory only
mutable bool fChecked;
MWEB::Block mweb_block;
CBlock()
{
SetNull();
@ -85,6 +88,11 @@ public:
{
READWRITEAS(CBlockHeader, obj);
READWRITE(obj.vtx);
if (!(s.GetVersion() & SERIALIZE_NO_MWEB)) {
if (obj.vtx.size() >= 2 && obj.vtx.back()->IsHogEx()) {
READWRITE(obj.mweb_block);
}
}
}
void SetNull()
@ -92,6 +100,7 @@ public:
CBlockHeader::SetNull();
vtx.clear();
fChecked = false;
mweb_block.SetNull();
}
CBlockHeader GetBlockHeader() const
@ -107,6 +116,9 @@ public:
}
std::string ToString() const;
// Returns the hogex (integrating) transaction, if it exists.
CTransactionRef GetHogEx() const noexcept;
};
/** Describes a place in the block chain to another node such that if the

View File

@ -56,17 +56,31 @@ std::string CTxOut::ToString() const
return strprintf("CTxOut(nValue=%d.%08d, scriptPubKey=%s)", nValue / COIN, nValue % COIN, HexStr(scriptPubKey).substr(0, 30));
}
CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), nLockTime(0) {}
CMutableTransaction::CMutableTransaction(const CTransaction& tx) : vin(tx.vin), vout(tx.vout), nVersion(tx.nVersion), nLockTime(tx.nLockTime) {}
CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), nLockTime(0), m_hogEx(false) {}
CMutableTransaction::CMutableTransaction(const CTransaction& tx) : vin(tx.vin), vout(tx.vout), nVersion(tx.nVersion), nLockTime(tx.nLockTime), mweb_tx(tx.mweb_tx), m_hogEx(tx.m_hogEx) {}
uint256 CMutableTransaction::GetHash() const
{
return SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS);
if (IsMWEBOnly()) {
const auto& kernels = mweb_tx.m_transaction->GetKernels();
if (!kernels.empty()) {
return uint256(kernels.front().GetHash().vec());
}
}
return SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS | SERIALIZE_NO_MWEB);
}
uint256 CTransaction::ComputeHash() const
{
return SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS);
if (IsMWEBOnly()) {
const auto& kernels = mweb_tx.m_transaction->GetKernels();
if (!kernels.empty()) {
return uint256(kernels.front().GetHash().vec());
}
}
return SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS | SERIALIZE_NO_MWEB);
}
uint256 CTransaction::ComputeWitnessHash() const
@ -74,13 +88,14 @@ uint256 CTransaction::ComputeWitnessHash() const
if (!HasWitness()) {
return hash;
}
return SerializeHash(*this, SER_GETHASH, 0);
return SerializeHash(*this, SER_GETHASH, SERIALIZE_NO_MWEB);
}
/* For backward compatibility, the hash is initialized to 0. TODO: remove the need for this default constructor entirely. */
CTransaction::CTransaction() : vin(), vout(), nVersion(CTransaction::CURRENT_VERSION), nLockTime(0), hash{}, m_witness_hash{} {}
CTransaction::CTransaction(const CMutableTransaction& tx) : vin(tx.vin), vout(tx.vout), nVersion(tx.nVersion), nLockTime(tx.nLockTime), hash{ComputeHash()}, m_witness_hash{ComputeWitnessHash()} {}
CTransaction::CTransaction(CMutableTransaction&& tx) : vin(std::move(tx.vin)), vout(std::move(tx.vout)), nVersion(tx.nVersion), nLockTime(tx.nLockTime), hash{ComputeHash()}, m_witness_hash{ComputeWitnessHash()} {}
CTransaction::CTransaction() : vin(), vout(), nVersion(CTransaction::CURRENT_VERSION), nLockTime(0), m_hogEx(false), hash{}, m_witness_hash{} {}
CTransaction::CTransaction(const CMutableTransaction& tx) : vin(tx.vin), vout(tx.vout), nVersion(tx.nVersion), nLockTime(tx.nLockTime), mweb_tx(tx.mweb_tx), m_hogEx(tx.m_hogEx), hash{ComputeHash()}, m_witness_hash{ComputeWitnessHash()} {}
CTransaction::CTransaction(CMutableTransaction&& tx) : vin(std::move(tx.vin)), vout(std::move(tx.vout)), nVersion(tx.nVersion), nLockTime(tx.nLockTime), mweb_tx(tx.mweb_tx), m_hogEx(tx.m_hogEx), hash{ComputeHash()}, m_witness_hash{ComputeWitnessHash()} {}
CAmount CTransaction::GetValueOut() const
{
@ -114,5 +129,57 @@ std::string CTransaction::ToString() const
str += " " + tx_in.scriptWitness.ToString() + "\n";
for (const auto& tx_out : vout)
str += " " + tx_out.ToString() + "\n";
if (!mweb_tx.IsNull()) {
str += " " + mweb_tx.ToString() + "\n";
}
return str;
}
std::vector<CTxInput> CTransaction::GetInputs() const noexcept
{
std::vector<CTxInput> inputs;
for (const CTxIn& txin : vin) {
inputs.push_back(txin);
}
for (const mw::Hash& spent_id : mweb_tx.GetSpentIDs()) {
inputs.push_back(spent_id);
}
return inputs;
}
CTxOutput CTransaction::GetOutput(const size_t index) const noexcept
{
assert(vout.size() > index);
return CTxOutput{COutPoint(GetHash(), index), vout[index]};
}
CTxOutput CTransaction::GetOutput(const OutputIndex& idx) const noexcept
{
if (idx.type() == typeid(mw::Hash)) {
return CTxOutput{boost::get<mw::Hash>(idx)};
} else {
const COutPoint& outpoint = boost::get<COutPoint>(idx);
assert(vout.size() > outpoint.n);
return CTxOutput{outpoint, vout[outpoint.n]};
}
}
std::vector<CTxOutput> CTransaction::GetOutputs() const noexcept
{
std::vector<CTxOutput> outputs;
for (size_t n = 0; n < vout.size(); n++) {
outputs.push_back(CTxOutput{COutPoint(GetHash(), n), vout[n]});
}
for (const mw::Hash& output_id : mweb_tx.GetOutputIDs()) {
outputs.push_back(CTxOutput{output_id});
}
return outputs;
}

View File

@ -11,6 +11,9 @@
#include <script/script.h>
#include <serialize.h>
#include <uint256.h>
#include <mweb/mweb_models.h>
#include <boost/optional.hpp>
#include <boost/variant.hpp>
#include <tuple>
@ -21,6 +24,7 @@
* or with `ADDRV2_FORMAT`.
*/
static const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000;
static const int SERIALIZE_NO_MWEB = 0x20000000;
/** An outpoint - a combination of a transaction hash and an index n into its vout */
class COutPoint
@ -122,6 +126,46 @@ public:
std::string ToString() const;
};
typedef boost::variant<COutPoint, mw::Hash> OutputIndex;
/// <summary>
/// A generic transaction input that could either be an MWEB input hash or a canonical CTxIn.
/// </summary>
class CTxInput
{
public:
CTxInput(mw::Hash output_id)
: m_input(std::move(output_id)) {}
CTxInput(CTxIn txin)
: m_input(std::move(txin)) {}
bool IsMWEB() const noexcept { return m_input.type() == typeid(mw::Hash); }
OutputIndex GetIndex() const noexcept
{
return IsMWEB() ? OutputIndex{ToMWEB()} : OutputIndex{GetTxIn().prevout};
}
std::string ToString() const
{
return IsMWEB() ? ToMWEB().ToHex() : GetTxIn().ToString();
}
const mw::Hash& ToMWEB() const noexcept
{
assert(IsMWEB());
return boost::get<mw::Hash>(m_input);
}
const CTxIn& GetTxIn() const noexcept
{
assert(!IsMWEB());
return boost::get<CTxIn>(m_input);
}
private:
boost::variant<CTxIn, mw::Hash> m_input;
};
/** An output of a transaction. It contains the public key that the next input
* must be able to sign with to claim it.
*/
@ -165,6 +209,52 @@ public:
std::string ToString() const;
};
class CTransaction;
/// <summary>
/// A generic transaction output that could either be an MWEB output ID or a canonical CTxOut.
/// </summary>
class CTxOutput
{
public:
CTxOutput() = default;
CTxOutput(mw::Hash output_id)
: m_idx(std::move(output_id)), m_txout(boost::none) {}
CTxOutput(OutputIndex idx, CTxOut txout)
: m_idx(std::move(idx)), m_txout(std::move(txout)) {}
bool IsMWEB() const noexcept { return m_idx.type() == typeid(mw::Hash); }
const OutputIndex& GetIndex() const noexcept { return m_idx; }
std::string ToString() const
{
return IsMWEB() ? ToMWEB().ToHex() : GetTxOut().ToString();
}
const mw::Hash& ToMWEB() const noexcept
{
assert(IsMWEB());
return boost::get<mw::Hash>(m_idx);
}
const CTxOut& GetTxOut() const noexcept
{
assert(!IsMWEB() && !!m_txout);
return *m_txout;
}
const CScript& GetScriptPubKey() const noexcept
{
assert(!IsMWEB());
return GetTxOut().scriptPubKey;
}
private:
OutputIndex m_idx;
boost::optional<CTxOut> m_txout;
};
struct CMutableTransaction;
/**
@ -187,6 +277,7 @@ struct CMutableTransaction;
template<typename Stream, typename TxType>
inline void UnserializeTransaction(TxType& tx, Stream& s) {
const bool fAllowWitness = !(s.GetVersion() & SERIALIZE_TRANSACTION_NO_WITNESS);
const bool fAllowMWEB = !(s.GetVersion() & SERIALIZE_NO_MWEB);
s >> tx.nVersion;
unsigned char flags = 0;
@ -216,6 +307,21 @@ inline void UnserializeTransaction(TxType& tx, Stream& s) {
throw std::ios_base::failure("Superfluous witness record");
}
}
if ((flags & 8) && fAllowMWEB) {
/* The MWEB flag is present, and we support MWEB. */
flags ^= 8;
s >> tx.mweb_tx;
if (tx.mweb_tx.IsNull()) {
if (tx.vout.empty()) {
/* It's illegal to include a HogEx with no outputs. */
throw std::ios_base::failure("Missing HogEx output");
}
/* If the MWEB flag is set, but there are no MWEB txs, assume HogEx txn. */
tx.m_hogEx = true;
}
}
if (flags) {
/* Unknown flag in the serialization */
throw std::ios_base::failure("Unknown transaction optional data");
@ -226,6 +332,7 @@ inline void UnserializeTransaction(TxType& tx, Stream& s) {
template<typename Stream, typename TxType>
inline void SerializeTransaction(const TxType& tx, Stream& s) {
const bool fAllowWitness = !(s.GetVersion() & SERIALIZE_TRANSACTION_NO_WITNESS);
const bool fAllowMWEB = !(s.GetVersion() & SERIALIZE_NO_MWEB);
s << tx.nVersion;
unsigned char flags = 0;
@ -236,6 +343,12 @@ inline void SerializeTransaction(const TxType& tx, Stream& s) {
flags |= 1;
}
}
if (fAllowMWEB) {
if (tx.m_hogEx || !tx.mweb_tx.IsNull()) {
flags |= 8;
}
}
if (flags) {
/* Use extended format in case witnesses are to be serialized. */
std::vector<CTxIn> vinDummy;
@ -249,6 +362,9 @@ inline void SerializeTransaction(const TxType& tx, Stream& s) {
s << tx.vin[i].scriptWitness.stack;
}
}
if (flags & 8) {
s << tx.mweb_tx;
}
s << tx.nLockTime;
}
@ -277,6 +393,10 @@ public:
const std::vector<CTxOut> vout;
const int32_t nVersion;
const uint32_t nLockTime;
const MWEB::Tx mweb_tx;
/** Memory only. */
const bool m_hogEx;
private:
/** Memory only. */
@ -347,6 +467,41 @@ public:
}
return false;
}
bool HasMWEBTx() const noexcept { return !mweb_tx.IsNull(); }
bool IsHogEx() const noexcept { return m_hogEx; }
/// <summary>
/// Determines whether the transaction is strictly MWEB-to-MWEB, with no canonical transaction data.
/// </summary>
/// <returns>True if the tx is MWEB-to-MWEB only.</returns>
bool IsMWEBOnly() const noexcept { return HasMWEBTx() && vin.empty() && vout.empty(); }
/// <summary>
/// Builds a vector of CTxInputs, starting with the canoncial inputs (CTxIn), followed by the MWEB input hashes.
/// </summary>
/// <returns>A vector of all of the transaction's inputs.</returns>
std::vector<CTxInput> GetInputs() const noexcept;
/// <summary>
/// Constructs a CTxOutput for the specified canonical output.
/// </summary>
/// <param name="index">The index of the CTxOut. This must be a valid index.</param>
/// <returns>The CTxOutput object.</returns>
CTxOutput GetOutput(const size_t index) const noexcept;
/// <summary>
/// Constructs a CTxOutput for the specified output.
/// </summary>
/// <param name="idx">The index of the output. This could either be an output ID or a valid canonical output index.</param>
/// <returns>The CTxOutput object.</returns>
CTxOutput GetOutput(const OutputIndex& idx) const noexcept;
/// <summary>
/// Builds a vector of CTxOutputs, starting with the canoncial outputs (CTxOut), followed by the MWEB output IDs.
/// </summary>
/// <returns>A vector of all of the transaction's outputs.</returns>
std::vector<CTxOutput> GetOutputs() const noexcept;
};
/** A mutable version of CTransaction. */
@ -356,6 +511,10 @@ struct CMutableTransaction
std::vector<CTxOut> vout;
int32_t nVersion;
uint32_t nLockTime;
MWEB::Tx mweb_tx;
/** Memory only. */
bool m_hogEx = false;
CMutableTransaction();
explicit CMutableTransaction(const CTransaction& tx);
@ -390,12 +549,21 @@ struct CMutableTransaction
}
return false;
}
bool HasMWEBTx() const noexcept { return !mweb_tx.IsNull(); }
bool IsMWEBOnly() const noexcept { return HasMWEBTx() && vin.empty() && vout.empty(); }
};
typedef std::shared_ptr<const CTransaction> CTransactionRef;
static inline CTransactionRef MakeTransactionRef() { return std::make_shared<const CTransaction>(); }
template <typename Tx> static inline CTransactionRef MakeTransactionRef(Tx&& txIn) { return std::make_shared<const CTransaction>(std::forward<Tx>(txIn)); }
template <typename Stream>
void Unserialize(Stream& is, std::shared_ptr<const CTransaction>& p)
{
p = std::make_shared<const CTransaction>(deserialize, is);
}
/** A generic txid reference (txid or wtxid). */
class GenTxid
{

View File

@ -23,6 +23,7 @@
#include <prevector.h>
#include <span.h>
#include <boost/optional.hpp>
/**
* The maximum size of a serialized object in bytes or number of elements
@ -689,12 +690,59 @@ template<typename Stream, typename K, typename Pred, typename A> void Unserializ
template<typename Stream, typename T> void Serialize(Stream& os, const std::shared_ptr<const T>& p);
template<typename Stream, typename T> void Unserialize(Stream& os, std::shared_ptr<const T>& p);
class CTransaction;
template<typename Stream> void Unserialize(Stream& is, std::shared_ptr<const CTransaction>& item);
/**
* unique_ptr
*/
template<typename Stream, typename T> void Serialize(Stream& os, const std::unique_ptr<const T>& p);
template<typename Stream, typename T> void Unserialize(Stream& os, std::unique_ptr<const T>& p);
/**
* optional
*/
template<typename Stream, typename T> void Serialize(Stream& os, const boost::optional<T>& item);
template<typename Stream, typename T> void Unserialize(Stream& is, boost::optional<T>& item);
template <class T>
class OptionalPtr
{
protected:
T& obj;
public:
explicit OptionalPtr(T& _obj) : obj(_obj) {}
template <typename Stream>
void Serialize(Stream& os) const
{
uint8_t is_set = obj != nullptr ? 1 : 0;
os << is_set;
if (is_set == 1) {
::Serialize(os, obj);
}
}
template <typename Stream>
void Unserialize(Stream& is)
{
uint8_t is_set = 0;
is >> is_set;
if (is_set == 1) {
::Unserialize(is, obj);
}
}
};
template <typename I>
OptionalPtr<I> WrapOptionalPtr(I& n)
{
return OptionalPtr<I>(n);
}
/**
@ -974,10 +1022,40 @@ Serialize(Stream& os, const std::shared_ptr<const T>& p)
template<typename Stream, typename T>
void Unserialize(Stream& is, std::shared_ptr<const T>& p)
{
p = std::make_shared<const T>(deserialize, is);
T obj;
is >> obj;
p = std::make_shared<const T>(std::move(obj));
}
/**
* optional
*/
template <typename Stream, typename T>
void Serialize(Stream& os, const boost::optional<T>& p)
{
uint8_t is_set = !!p ? 1 : 0;
Serialize(os, is_set);
if (is_set == 1) {
Serialize(os, (*p));
}
}
template <typename Stream, typename T>
void Unserialize(Stream& is, boost::optional<T>& p)
{
uint8_t is_set = 0;
Unserialize(is, is_set);
if (is_set == 1) {
T val;
Unserialize(is, val);
p = boost::make_optional(std::move(val));
}
}
/**
* Support for SERIALIZE_METHODS and READWRITE macro.