mirror of
https://github.com/dashpay/dash.git
synced 2024-12-28 13:32:47 +01:00
515 lines
16 KiB
C++
515 lines
16 KiB
C++
// Copyright (c) 2014-2023 The Dash Core developers
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#ifndef BITCOIN_COINJOIN_COINJOIN_H
|
|
#define BITCOIN_COINJOIN_COINJOIN_H
|
|
|
|
#include <core_io.h>
|
|
#include <netaddress.h>
|
|
#include <primitives/block.h>
|
|
#include <primitives/transaction.h>
|
|
#include <sync.h>
|
|
#include <timedata.h>
|
|
#include <tinyformat.h>
|
|
#include <univalue.h>
|
|
#include <util/ranges.h>
|
|
#include <util/translation.h>
|
|
#include <version.h>
|
|
|
|
#include <utility>
|
|
|
|
class CCoinJoin;
|
|
class CConnman;
|
|
class CBLSPublicKey;
|
|
class CBlockIndex;
|
|
class CMasternodeSync;
|
|
|
|
namespace llmq {
|
|
class CChainLocksHandler;
|
|
} // namespace llmq
|
|
|
|
// timeouts
|
|
static constexpr int COINJOIN_AUTO_TIMEOUT_MIN = 5;
|
|
static constexpr int COINJOIN_AUTO_TIMEOUT_MAX = 15;
|
|
static constexpr int COINJOIN_QUEUE_TIMEOUT = 30;
|
|
static constexpr int COINJOIN_SIGNING_TIMEOUT = 15;
|
|
|
|
static constexpr size_t COINJOIN_ENTRY_MAX_SIZE = 9;
|
|
|
|
// pool responses
|
|
enum PoolMessage : int32_t {
|
|
ERR_ALREADY_HAVE,
|
|
ERR_DENOM,
|
|
ERR_ENTRIES_FULL,
|
|
ERR_EXISTING_TX,
|
|
ERR_FEES,
|
|
ERR_INVALID_COLLATERAL,
|
|
ERR_INVALID_INPUT,
|
|
ERR_INVALID_SCRIPT,
|
|
ERR_INVALID_TX,
|
|
ERR_MAXIMUM,
|
|
ERR_MN_LIST,
|
|
ERR_MODE,
|
|
ERR_NON_STANDARD_PUBKEY, // not used
|
|
ERR_NOT_A_MN, // not used
|
|
ERR_QUEUE_FULL,
|
|
ERR_RECENT,
|
|
ERR_SESSION,
|
|
ERR_MISSING_TX,
|
|
ERR_VERSION,
|
|
MSG_NOERR,
|
|
MSG_SUCCESS,
|
|
MSG_ENTRIES_ADDED,
|
|
ERR_SIZE_MISMATCH,
|
|
MSG_POOL_MIN = ERR_ALREADY_HAVE,
|
|
MSG_POOL_MAX = ERR_SIZE_MISMATCH
|
|
};
|
|
template<> struct is_serializable_enum<PoolMessage> : std::true_type {};
|
|
|
|
// pool states
|
|
enum PoolState : int32_t {
|
|
POOL_STATE_IDLE,
|
|
POOL_STATE_QUEUE,
|
|
POOL_STATE_ACCEPTING_ENTRIES,
|
|
POOL_STATE_SIGNING,
|
|
POOL_STATE_ERROR,
|
|
POOL_STATE_MIN = POOL_STATE_IDLE,
|
|
POOL_STATE_MAX = POOL_STATE_ERROR
|
|
};
|
|
template<> struct is_serializable_enum<PoolState> : std::true_type {};
|
|
|
|
// status update message constants
|
|
enum PoolStatusUpdate : int32_t {
|
|
STATUS_REJECTED,
|
|
STATUS_ACCEPTED
|
|
};
|
|
template<> struct is_serializable_enum<PoolStatusUpdate> : std::true_type {};
|
|
|
|
class CCoinJoinStatusUpdate
|
|
{
|
|
public:
|
|
int nSessionID{0};
|
|
PoolState nState{POOL_STATE_IDLE};
|
|
int nEntriesCount{0}; // deprecated, kept for backwards compatibility
|
|
PoolStatusUpdate nStatusUpdate{STATUS_ACCEPTED};
|
|
PoolMessage nMessageID{MSG_NOERR};
|
|
|
|
constexpr CCoinJoinStatusUpdate() = default;
|
|
|
|
constexpr CCoinJoinStatusUpdate(int nSessionID, PoolState nState, int nEntriesCount, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID) :
|
|
nSessionID(nSessionID),
|
|
nState(nState),
|
|
nEntriesCount(nEntriesCount),
|
|
nStatusUpdate(nStatusUpdate),
|
|
nMessageID(nMessageID) {};
|
|
|
|
SERIALIZE_METHODS(CCoinJoinStatusUpdate, obj)
|
|
{
|
|
READWRITE(obj.nSessionID, obj.nState);
|
|
if (s.GetVersion() <= COINJOIN_SU_PROTO_VERSION) {
|
|
READWRITE(obj.nEntriesCount);
|
|
}
|
|
READWRITE(obj.nStatusUpdate, obj.nMessageID);
|
|
}
|
|
};
|
|
|
|
/** Holds a mixing input
|
|
*/
|
|
class CTxDSIn : public CTxIn
|
|
{
|
|
public:
|
|
// memory only
|
|
CScript prevPubKey;
|
|
bool fHasSig{false}; // flag to indicate if signed
|
|
int nRounds{-10};
|
|
|
|
CTxDSIn(const CTxIn& txin, CScript script, int nRounds) :
|
|
CTxIn(txin),
|
|
prevPubKey(std::move(script)),
|
|
nRounds(nRounds)
|
|
{
|
|
}
|
|
|
|
CTxDSIn() = default;
|
|
};
|
|
|
|
class CCoinJoinAccept
|
|
{
|
|
public:
|
|
int nDenom{0};
|
|
CMutableTransaction txCollateral;
|
|
|
|
CCoinJoinAccept() = default;
|
|
|
|
CCoinJoinAccept(int nDenom, CMutableTransaction txCollateral) :
|
|
nDenom(nDenom),
|
|
txCollateral(std::move(txCollateral)){};
|
|
|
|
SERIALIZE_METHODS(CCoinJoinAccept, obj)
|
|
{
|
|
READWRITE(obj.nDenom, obj.txCollateral);
|
|
}
|
|
|
|
friend bool operator==(const CCoinJoinAccept& a, const CCoinJoinAccept& b)
|
|
{
|
|
return a.nDenom == b.nDenom && CTransaction(a.txCollateral) == CTransaction(b.txCollateral);
|
|
}
|
|
};
|
|
|
|
// A client's transaction in the mixing pool
|
|
class CCoinJoinEntry
|
|
{
|
|
public:
|
|
std::vector<CTxDSIn> vecTxDSIn;
|
|
std::vector<CTxOut> vecTxOut;
|
|
CTransactionRef txCollateral;
|
|
// memory only
|
|
CService addr;
|
|
|
|
CCoinJoinEntry() :
|
|
txCollateral(MakeTransactionRef())
|
|
{
|
|
}
|
|
|
|
CCoinJoinEntry(std::vector<CTxDSIn> vecTxDSIn, std::vector<CTxOut> vecTxOut, const CTransaction& txCollateral) :
|
|
vecTxDSIn(std::move(vecTxDSIn)),
|
|
vecTxOut(std::move(vecTxOut)),
|
|
txCollateral(MakeTransactionRef(txCollateral))
|
|
{
|
|
}
|
|
|
|
SERIALIZE_METHODS(CCoinJoinEntry, obj)
|
|
{
|
|
READWRITE(obj.vecTxDSIn, obj.txCollateral, obj.vecTxOut);
|
|
}
|
|
|
|
bool AddScriptSig(const CTxIn& txin);
|
|
};
|
|
|
|
|
|
/**
|
|
* A currently in progress mixing merge and denomination information
|
|
*/
|
|
class CCoinJoinQueue
|
|
{
|
|
public:
|
|
int nDenom{0};
|
|
COutPoint masternodeOutpoint;
|
|
uint256 m_protxHash;
|
|
int64_t nTime{0};
|
|
bool fReady{false}; //ready for submit
|
|
std::vector<unsigned char> vchSig;
|
|
// memory only
|
|
bool fTried{false};
|
|
|
|
CCoinJoinQueue() = default;
|
|
|
|
CCoinJoinQueue(int nDenom, const COutPoint& outpoint, const uint256& proTxHash, int64_t nTime, bool fReady) :
|
|
nDenom(nDenom),
|
|
masternodeOutpoint(outpoint),
|
|
m_protxHash(proTxHash),
|
|
nTime(nTime),
|
|
fReady(fReady)
|
|
{
|
|
}
|
|
|
|
SERIALIZE_METHODS(CCoinJoinQueue, obj)
|
|
{
|
|
READWRITE(obj.nDenom);
|
|
|
|
if (s.GetVersion() < COINJOIN_PROTX_HASH_PROTO_VERSION || (s.GetType() & SER_GETHASH)) {
|
|
READWRITE(obj.masternodeOutpoint);
|
|
} else {
|
|
READWRITE(obj.m_protxHash);
|
|
}
|
|
READWRITE(obj.nTime, obj.fReady);
|
|
if (!(s.GetType() & SER_GETHASH)) {
|
|
READWRITE(obj.vchSig);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] uint256 GetSignatureHash() const;
|
|
/** Sign this mixing transaction
|
|
* return true if all conditions are met:
|
|
* 1) we have an active Masternode,
|
|
* 2) we have a valid Masternode private key,
|
|
* 3) we signed the message successfully, and
|
|
* 4) we verified the message successfully
|
|
*/
|
|
bool Sign();
|
|
/// Check if we have a valid Masternode address
|
|
[[nodiscard]] bool CheckSignature(const CBLSPublicKey& blsPubKey) const;
|
|
|
|
bool Relay(CConnman& connman);
|
|
|
|
/// Check if a queue is too old or too far into the future
|
|
[[nodiscard]] bool IsTimeOutOfBounds(int64_t current_time = GetAdjustedTime()) const;
|
|
|
|
[[nodiscard]] std::string ToString() const
|
|
{
|
|
return strprintf("nDenom=%d, nTime=%lld, fReady=%s, fTried=%s, masternode=%s",
|
|
nDenom, nTime, fReady ? "true" : "false", fTried ? "true" : "false", masternodeOutpoint.ToStringShort());
|
|
}
|
|
|
|
friend bool operator==(const CCoinJoinQueue& a, const CCoinJoinQueue& b)
|
|
{
|
|
return a.nDenom == b.nDenom && a.masternodeOutpoint == b.masternodeOutpoint && a.nTime == b.nTime && a.fReady == b.fReady;
|
|
}
|
|
};
|
|
|
|
/** Helper class to store mixing transaction (tx) information.
|
|
*/
|
|
class CCoinJoinBroadcastTx
|
|
{
|
|
private:
|
|
// memory only
|
|
// when corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is -1
|
|
int nConfirmedHeight{-1};
|
|
|
|
public:
|
|
CTransactionRef tx;
|
|
COutPoint masternodeOutpoint;
|
|
uint256 m_protxHash;
|
|
std::vector<unsigned char> vchSig;
|
|
int64_t sigTime{0};
|
|
|
|
CCoinJoinBroadcastTx() :
|
|
tx(MakeTransactionRef())
|
|
{
|
|
}
|
|
|
|
CCoinJoinBroadcastTx(CTransactionRef _tx, const COutPoint& _outpoint, const uint256& proTxHash, int64_t _sigTime) :
|
|
tx(std::move(_tx)),
|
|
masternodeOutpoint(_outpoint),
|
|
m_protxHash(proTxHash),
|
|
sigTime(_sigTime)
|
|
{
|
|
}
|
|
|
|
SERIALIZE_METHODS(CCoinJoinBroadcastTx, obj)
|
|
{
|
|
READWRITE(obj.tx);
|
|
|
|
if (s.GetVersion() < COINJOIN_PROTX_HASH_PROTO_VERSION || (s.GetType() & SER_GETHASH)) {
|
|
READWRITE(obj.masternodeOutpoint);
|
|
} else {
|
|
READWRITE(obj.m_protxHash);
|
|
}
|
|
|
|
if (!(s.GetType() & SER_GETHASH)) {
|
|
READWRITE(obj.vchSig);
|
|
}
|
|
READWRITE(obj.sigTime);
|
|
}
|
|
|
|
friend bool operator==(const CCoinJoinBroadcastTx& a, const CCoinJoinBroadcastTx& b)
|
|
{
|
|
return *a.tx == *b.tx;
|
|
}
|
|
friend bool operator!=(const CCoinJoinBroadcastTx& a, const CCoinJoinBroadcastTx& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return *this != CCoinJoinBroadcastTx();
|
|
}
|
|
|
|
[[nodiscard]] uint256 GetSignatureHash() const;
|
|
|
|
bool Sign();
|
|
[[nodiscard]] bool CheckSignature(const CBLSPublicKey& blsPubKey) const;
|
|
|
|
void SetConfirmedHeight(int nConfirmedHeightIn) { nConfirmedHeight = nConfirmedHeightIn; }
|
|
bool IsExpired(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler) const;
|
|
[[nodiscard]] bool IsValidStructure() const;
|
|
};
|
|
|
|
// base class
|
|
class CCoinJoinBaseSession
|
|
{
|
|
protected:
|
|
mutable Mutex cs_coinjoin;
|
|
|
|
std::vector<CCoinJoinEntry> vecEntries GUARDED_BY(cs_coinjoin); // Masternode/clients entries
|
|
|
|
std::atomic<PoolState> nState{POOL_STATE_IDLE}; // should be one of the POOL_STATE_XXX values
|
|
std::atomic<int64_t> nTimeLastSuccessfulStep{0}; // the time when last successful mixing step was performed
|
|
|
|
std::atomic<int> nSessionID{0}; // 0 if no mixing session is active
|
|
|
|
CMutableTransaction finalMutableTransaction GUARDED_BY(cs_coinjoin); // the finalized transaction ready for signing
|
|
|
|
void SetNull() EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin);
|
|
|
|
bool IsValidInOuts(const std::vector<CTxIn>& vin, const std::vector<CTxOut>& vout, PoolMessage& nMessageIDRet, bool* fConsumeCollateralRet) const;
|
|
|
|
public:
|
|
int nSessionDenom{0}; // Users must submit a denom matching this
|
|
|
|
CCoinJoinBaseSession() = default;
|
|
|
|
int GetState() const { return nState; }
|
|
std::string GetStateString() const;
|
|
|
|
int GetEntriesCount() const LOCKS_EXCLUDED(cs_coinjoin) { LOCK(cs_coinjoin); return vecEntries.size(); }
|
|
int GetEntriesCountLocked() const EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin) { return vecEntries.size(); }
|
|
};
|
|
|
|
// base class
|
|
class CCoinJoinBaseManager
|
|
{
|
|
protected:
|
|
mutable Mutex cs_vecqueue;
|
|
|
|
// The current mixing sessions in progress on the network
|
|
std::vector<CCoinJoinQueue> vecCoinJoinQueue GUARDED_BY(cs_vecqueue);
|
|
|
|
void SetNull() LOCKS_EXCLUDED(cs_vecqueue);
|
|
void CheckQueue() LOCKS_EXCLUDED(cs_vecqueue);
|
|
|
|
public:
|
|
CCoinJoinBaseManager() = default;
|
|
|
|
int GetQueueSize() const LOCKS_EXCLUDED(cs_vecqueue) { LOCK(cs_vecqueue); return vecCoinJoinQueue.size(); }
|
|
bool GetQueueItemAndTry(CCoinJoinQueue& dsqRet) LOCKS_EXCLUDED(cs_vecqueue);
|
|
};
|
|
|
|
// helper class
|
|
class CCoinJoin
|
|
{
|
|
private:
|
|
// make constructor, destructor and copying not available
|
|
CCoinJoin() = default;
|
|
~CCoinJoin() = default;
|
|
CCoinJoin(CCoinJoin const&) = delete;
|
|
CCoinJoin& operator=(CCoinJoin const&) = delete;
|
|
|
|
// static members
|
|
static constexpr std::array<CAmount, 5> vecStandardDenominations{
|
|
(10 * COIN) + 10000,
|
|
(1 * COIN) + 1000,
|
|
(COIN / 10) + 100,
|
|
(COIN / 100) + 10,
|
|
(COIN / 1000) + 1,
|
|
};
|
|
|
|
static Mutex cs_mapdstx;
|
|
static std::map<uint256, CCoinJoinBroadcastTx> mapDSTX GUARDED_BY(cs_mapdstx);
|
|
|
|
static void CheckDSTXes(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler) LOCKS_EXCLUDED(cs_mapdstx);
|
|
|
|
public:
|
|
static constexpr std::array<CAmount, 5> GetStandardDenominations() { return vecStandardDenominations; }
|
|
static constexpr CAmount GetSmallestDenomination() { return vecStandardDenominations.back(); }
|
|
|
|
static constexpr bool IsDenominatedAmount(CAmount nInputAmount) { return AmountToDenomination(nInputAmount) > 0; }
|
|
static constexpr bool IsValidDenomination(int nDenom) { return DenominationToAmount(nDenom) > 0; }
|
|
|
|
/*
|
|
Return a bitshifted integer representing a denomination in vecStandardDenominations
|
|
or 0 if none was found
|
|
*/
|
|
static constexpr int AmountToDenomination(CAmount nInputAmount)
|
|
{
|
|
for (size_t i = 0; i < vecStandardDenominations.size(); ++i) {
|
|
if (nInputAmount == vecStandardDenominations[i]) {
|
|
return 1 << i;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
Returns:
|
|
- one of standard denominations from vecStandardDenominations based on the provided bitshifted integer
|
|
- 0 for non-initialized sessions (nDenom = 0)
|
|
- a value below 0 if an error occurred while converting from one to another
|
|
*/
|
|
static constexpr CAmount DenominationToAmount(int nDenom)
|
|
{
|
|
if (nDenom == 0) {
|
|
// not initialized
|
|
return 0;
|
|
}
|
|
|
|
size_t nMaxDenoms = vecStandardDenominations.size();
|
|
|
|
if (nDenom >= (1 << nMaxDenoms) || nDenom < 0) {
|
|
// out of bounds
|
|
return -1;
|
|
}
|
|
|
|
if ((nDenom & (nDenom - 1)) != 0) {
|
|
// non-denom
|
|
return -2;
|
|
}
|
|
|
|
CAmount nDenomAmount{-3};
|
|
|
|
for (size_t i = 0; i < nMaxDenoms; ++i) {
|
|
if (nDenom & (1 << i)) {
|
|
nDenomAmount = vecStandardDenominations[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return nDenomAmount;
|
|
}
|
|
|
|
/*
|
|
Same as DenominationToAmount but returns a string representation
|
|
*/
|
|
static std::string DenominationToString(int nDenom);
|
|
|
|
static bilingual_str GetMessageByID(PoolMessage nMessageID);
|
|
|
|
/// Get the minimum/maximum number of participants for the pool
|
|
static int GetMinPoolParticipants();
|
|
static int GetMaxPoolParticipants();
|
|
|
|
static constexpr CAmount GetMaxPoolAmount() { return COINJOIN_ENTRY_MAX_SIZE * vecStandardDenominations.front(); }
|
|
|
|
/// If the collateral is valid given by a client
|
|
static bool IsCollateralValid(const CTransaction& txCollateral);
|
|
static constexpr CAmount GetCollateralAmount() { return GetSmallestDenomination() / 10; }
|
|
static constexpr CAmount GetMaxCollateralAmount() { return GetCollateralAmount() * 4; }
|
|
|
|
static constexpr bool IsCollateralAmount(CAmount nInputAmount)
|
|
{
|
|
// collateral input can be anything between 1x and "max" (including both)
|
|
return (nInputAmount >= GetCollateralAmount() && nInputAmount <= GetMaxCollateralAmount());
|
|
}
|
|
|
|
static constexpr int CalculateAmountPriority(CAmount nInputAmount)
|
|
{
|
|
if (auto optDenom = ranges::find_if_opt(GetStandardDenominations(), [&nInputAmount](const auto& denom) {
|
|
return nInputAmount == denom;
|
|
})) {
|
|
return (float)COIN / *optDenom * 10000;
|
|
}
|
|
if (nInputAmount < COIN) {
|
|
return 20000;
|
|
}
|
|
|
|
//nondenom return largest first
|
|
return -1 * (nInputAmount / COIN);
|
|
}
|
|
|
|
|
|
static void AddDSTX(const CCoinJoinBroadcastTx& dstx) LOCKS_EXCLUDED(cs_mapdstx);
|
|
static CCoinJoinBroadcastTx GetDSTX(const uint256& hash) LOCKS_EXCLUDED(cs_mapdstx);
|
|
|
|
static void UpdatedBlockTip(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler, const std::unique_ptr<CMasternodeSync>& mn_sync);
|
|
static void NotifyChainLock(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler, const std::unique_ptr<CMasternodeSync>& mn_sync);
|
|
|
|
static void UpdateDSTXConfirmedHeight(const CTransactionRef& tx, int nHeight);
|
|
static void TransactionAddedToMempool(const CTransactionRef& tx) LOCKS_EXCLUDED(cs_mapdstx);
|
|
static void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex, const std::vector<CTransactionRef>& vtxConflicted) LOCKS_EXCLUDED(cs_mapdstx);
|
|
static void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex*) LOCKS_EXCLUDED(cs_mapdstx);
|
|
|
|
};
|
|
|
|
#endif // BITCOIN_COINJOIN_COINJOIN_H
|