2021-04-20 21:33:02 +02:00
// Copyright (c) 2014-2021 The Dash Core developers
2017-05-05 13:26:27 +02:00
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
2018-11-05 10:29:33 +01:00
2021-03-17 23:36:11 +01:00
# include <coinjoin/coinjoin.h>
2017-05-05 13:26:27 +02:00
2021-10-25 15:55:34 +02:00
# include <bls/bls.h>
2021-04-16 05:41:16 +02:00
# include <chain.h>
2021-10-25 15:55:34 +02:00
# include <chainparams.h>
# include <consensus/validation.h>
# include <core_io.h>
# include <llmq/chainlocks.h>
# include <llmq/instantsend.h>
# include <masternode/node.h>
# include <masternode/sync.h>
2020-03-19 23:46:56 +01:00
# include <messagesigner.h>
# include <netmessagemaker.h>
# include <txmempool.h>
2021-06-27 08:33:13 +02:00
# include <util/moneystr.h>
2021-10-25 15:55:34 +02:00
# include <util/system.h>
2020-03-19 23:46:56 +01:00
# include <validation.h>
2019-04-29 10:32:08 +02:00
2018-07-12 11:02:20 +02:00
# include <string>
2017-05-05 13:26:27 +02:00
2021-03-17 23:36:11 +01:00
bool CCoinJoinEntry : : AddScriptSig ( const CTxIn & txin )
2017-05-05 13:26:27 +02:00
{
2018-02-06 12:09:33 +01:00
for ( auto & txdsin : vecTxDSIn ) {
2018-11-05 10:29:07 +01:00
if ( txdsin . prevout = = txin . prevout & & txdsin . nSequence = = txin . nSequence ) {
if ( txdsin . fHasSig ) return false ;
2017-05-05 13:26:27 +02:00
txdsin . scriptSig = txin . scriptSig ;
txdsin . fHasSig = true ;
return true ;
}
}
return false ;
}
2021-03-17 23:36:11 +01:00
uint256 CCoinJoinQueue : : GetSignatureHash ( ) const
2018-02-16 15:54:53 +01:00
{
2019-01-03 10:17:43 +01:00
return SerializeHash ( * this ) ;
2018-02-16 15:54:53 +01:00
}
2021-03-17 23:36:11 +01:00
bool CCoinJoinQueue : : Sign ( )
2017-05-05 13:26:27 +02:00
{
2018-11-05 10:29:07 +01:00
if ( ! fMasternodeMode ) return false ;
2017-05-05 13:26:27 +02:00
2018-02-16 15:54:53 +01:00
2018-12-17 15:00:10 +01:00
uint256 hash = GetSignatureHash ( ) ;
2021-07-26 17:52:52 +02:00
CBLSSignature sig = WITH_LOCK ( activeMasternodeInfoCs , return activeMasternodeInfo . blsKeyOperator - > Sign ( hash ) ) ;
2018-12-17 15:00:10 +01:00
if ( ! sig . IsValid ( ) ) {
return false ;
2017-06-30 20:30:16 +02:00
}
2021-02-27 08:36:00 +01:00
vchSig = sig . ToByteVector ( ) ;
2017-06-30 20:30:16 +02:00
2018-02-16 15:54:53 +01:00
return true ;
2017-06-30 20:30:16 +02:00
}
2021-03-17 23:36:11 +01:00
bool CCoinJoinQueue : : CheckSignature ( const CBLSPublicKey & blsPubKey ) const
2017-06-30 20:30:16 +02:00
{
2020-12-15 00:26:30 +01:00
if ( ! CBLSSignature ( vchSig ) . VerifyInsecure ( blsPubKey , GetSignatureHash ( ) ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinQueue::CheckSignature -- VerifyInsecure() failed \n " ) ;
2018-12-17 15:00:10 +01:00
return false ;
2017-06-30 20:30:16 +02:00
}
return true ;
}
2021-03-17 23:36:11 +01:00
bool CCoinJoinQueue : : Relay ( CConnman & connman )
2017-06-30 20:30:16 +02:00
{
2018-02-06 12:09:33 +01:00
connman . ForEachNode ( [ & connman , this ] ( CNode * pnode ) {
2016-11-25 20:01:56 +01:00
CNetMsgMaker msgMaker ( pnode - > GetSendVersion ( ) ) ;
2021-03-17 23:36:11 +01:00
if ( pnode - > nVersion > = MIN_COINJOIN_PEER_PROTO_VERSION & & pnode - > fSendDSQueue ) {
2016-11-25 20:01:56 +01:00
connman . PushMessage ( pnode , msgMaker . Make ( NetMsgType : : DSQUEUE , ( * this ) ) ) ;
2019-06-18 13:33:05 +02:00
}
2018-02-06 12:09:33 +01:00
} ) ;
2017-06-30 20:30:16 +02:00
return true ;
2017-05-05 13:26:27 +02:00
}
2021-03-17 23:36:11 +01:00
bool CCoinJoinQueue : : IsTimeOutOfBounds ( ) const
2019-10-09 18:48:32 +02:00
{
2021-03-17 23:36:11 +01:00
return GetAdjustedTime ( ) - nTime > COINJOIN_QUEUE_TIMEOUT | | nTime - GetAdjustedTime ( ) > COINJOIN_QUEUE_TIMEOUT ;
2019-10-09 18:48:32 +02:00
}
2021-03-17 23:36:11 +01:00
uint256 CCoinJoinBroadcastTx : : GetSignatureHash ( ) const
2018-02-16 15:54:53 +01:00
{
return SerializeHash ( * this ) ;
}
2021-03-17 23:36:11 +01:00
bool CCoinJoinBroadcastTx : : Sign ( )
2017-06-30 20:30:16 +02:00
{
2018-11-05 10:29:07 +01:00
if ( ! fMasternodeMode ) return false ;
2017-06-30 20:30:16 +02:00
2018-12-17 13:40:29 +01:00
uint256 hash = GetSignatureHash ( ) ;
2017-06-30 20:30:16 +02:00
2021-07-26 17:52:52 +02:00
CBLSSignature sig = WITH_LOCK ( activeMasternodeInfoCs , return activeMasternodeInfo . blsKeyOperator - > Sign ( hash ) ) ;
2018-12-17 13:40:29 +01:00
if ( ! sig . IsValid ( ) ) {
return false ;
2017-06-30 20:30:16 +02:00
}
2021-02-27 08:36:00 +01:00
vchSig = sig . ToByteVector ( ) ;
2017-06-30 20:30:16 +02:00
2018-02-16 15:54:53 +01:00
return true ;
2017-06-30 20:30:16 +02:00
}
2021-03-17 23:36:11 +01:00
bool CCoinJoinBroadcastTx : : CheckSignature ( const CBLSPublicKey & blsPubKey ) const
2017-06-30 20:30:16 +02:00
{
2020-12-15 00:26:30 +01:00
if ( ! CBLSSignature ( vchSig ) . VerifyInsecure ( blsPubKey , GetSignatureHash ( ) ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBroadcastTx::CheckSignature -- VerifyInsecure() failed \n " ) ;
2018-12-17 13:40:29 +01:00
return false ;
2017-06-30 20:30:16 +02:00
}
return true ;
}
2021-03-17 23:36:11 +01:00
bool CCoinJoinBroadcastTx : : IsExpired ( const CBlockIndex * pindex ) const
2017-07-10 16:42:09 +02:00
{
2019-07-07 00:07:03 +02:00
// expire confirmed DSTXes after ~1h since confirmation or chainlocked confirmation
if ( nConfirmedHeight = = - 1 | | pindex - > nHeight < nConfirmedHeight ) return false ; // not mined yet
2021-07-30 02:01:02 +02:00
if ( pindex - > nHeight - nConfirmedHeight > 24 ) return true ; // mined more than an hour ago
2019-07-07 00:07:03 +02:00
return llmq : : chainLocksHandler - > HasChainLock ( pindex - > nHeight , * pindex - > phashBlock ) ;
2017-07-10 16:42:09 +02:00
}
2021-06-26 15:10:53 +02:00
bool CCoinJoinBroadcastTx : : IsValidStructure ( ) const
2019-05-23 11:13:34 +02:00
{
// some trivial checks only
if ( tx - > vin . size ( ) ! = tx - > vout . size ( ) ) {
return false ;
}
2021-03-17 23:36:11 +01:00
if ( tx - > vin . size ( ) < CCoinJoin : : GetMinPoolParticipants ( ) ) {
2019-05-23 11:13:34 +02:00
return false ;
}
2021-03-17 23:36:11 +01:00
if ( tx - > vin . size ( ) > CCoinJoin : : GetMaxPoolParticipants ( ) * COINJOIN_ENTRY_MAX_SIZE ) {
2019-05-23 11:13:34 +02:00
return false ;
}
2021-10-01 13:44:40 +02:00
return std : : all_of ( tx - > vout . cbegin ( ) , tx - > vout . cend ( ) , [ ] ( const auto & txOut ) {
return CCoinJoin : : IsDenominatedAmount ( txOut . nValue ) & & txOut . scriptPubKey . IsPayToPublicKeyHash ( ) ;
} ) ;
2019-05-23 11:13:34 +02:00
}
2021-03-17 23:36:11 +01:00
void CCoinJoinBaseSession : : SetNull ( )
2017-05-05 13:26:27 +02:00
{
// Both sides
2021-03-17 23:36:11 +01:00
LOCK ( cs_coinjoin ) ;
2017-05-05 13:26:27 +02:00
nState = POOL_STATE_IDLE ;
nSessionID = 0 ;
nSessionDenom = 0 ;
vecEntries . clear ( ) ;
finalMutableTransaction . vin . clear ( ) ;
finalMutableTransaction . vout . clear ( ) ;
2018-02-12 13:47:53 +01:00
nTimeLastSuccessfulStep = GetTime ( ) ;
2017-05-05 13:26:27 +02:00
}
2021-03-17 23:36:11 +01:00
void CCoinJoinBaseManager : : SetNull ( )
2017-12-04 07:06:07 +01:00
{
2018-09-04 12:54:59 +02:00
LOCK ( cs_vecqueue ) ;
2021-03-17 23:36:11 +01:00
vecCoinJoinQueue . clear ( ) ;
2018-09-04 12:54:59 +02:00
}
2021-03-17 23:36:11 +01:00
void CCoinJoinBaseManager : : CheckQueue ( )
2018-09-04 12:54:59 +02:00
{
TRY_LOCK ( cs_vecqueue , lockDS ) ;
2018-11-05 10:29:07 +01:00
if ( ! lockDS ) return ; // it's ok to fail here, we run this quite frequently
2017-12-04 07:06:07 +01:00
// check mixing queue objects for timeouts
2021-03-17 23:36:11 +01:00
auto it = vecCoinJoinQueue . begin ( ) ;
while ( it ! = vecCoinJoinQueue . end ( ) ) {
2019-10-09 18:48:32 +02:00
if ( ( * it ) . IsTimeOutOfBounds ( ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBaseManager::%s -- Removing a queue (%s) \n " , __func__ , ( * it ) . ToString ( ) ) ;
it = vecCoinJoinQueue . erase ( it ) ;
2019-06-18 13:33:05 +02:00
} else {
2018-11-05 10:29:07 +01:00
+ + it ;
2019-06-18 13:33:05 +02:00
}
2017-12-04 07:06:07 +01:00
}
}
2021-03-17 23:36:11 +01:00
bool CCoinJoinBaseManager : : GetQueueItemAndTry ( CCoinJoinQueue & dsqRet )
2018-09-04 12:54:59 +02:00
{
TRY_LOCK ( cs_vecqueue , lockDS ) ;
2018-11-05 10:29:07 +01:00
if ( ! lockDS ) return false ; // it's ok to fail here, we run this quite frequently
2018-09-04 12:54:59 +02:00
2021-03-17 23:36:11 +01:00
for ( auto & dsq : vecCoinJoinQueue ) {
2018-09-04 12:54:59 +02:00
// only try each queue once
2019-10-09 18:48:32 +02:00
if ( dsq . fTried | | dsq . IsTimeOutOfBounds ( ) ) continue ;
2018-09-04 12:54:59 +02:00
dsq . fTried = true ;
dsqRet = dsq ;
return true ;
}
return false ;
}
2021-03-17 23:36:11 +01:00
std : : string CCoinJoinBaseSession : : GetStateString ( ) const
2017-05-05 13:26:27 +02:00
{
2018-11-05 10:29:07 +01:00
switch ( nState ) {
case POOL_STATE_IDLE :
return " IDLE " ;
case POOL_STATE_QUEUE :
return " QUEUE " ;
case POOL_STATE_ACCEPTING_ENTRIES :
return " ACCEPTING_ENTRIES " ;
case POOL_STATE_SIGNING :
return " SIGNING " ;
case POOL_STATE_ERROR :
return " ERROR " ;
default :
return " UNKNOWN " ;
2017-05-05 13:26:27 +02:00
}
}
2021-03-17 23:36:11 +01:00
bool CCoinJoinBaseSession : : IsValidInOuts ( const std : : vector < CTxIn > & vin , const std : : vector < CTxOut > & vout , PoolMessage & nMessageIDRet , bool * fConsumeCollateralRet ) const
2020-01-01 15:12:25 +01:00
{
2021-10-16 21:08:00 +02:00
AssertLockHeld ( cs_main ) ;
2020-01-01 15:12:25 +01:00
std : : set < CScript > setScripPubKeys ;
nMessageIDRet = MSG_NOERR ;
if ( fConsumeCollateralRet ) * fConsumeCollateralRet = false ;
if ( vin . size ( ) ! = vout . size ( ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBaseSession::%s -- ERROR: inputs vs outputs size mismatch! %d vs %d \n " , __func__ , vin . size ( ) , vout . size ( ) ) ;
2020-01-01 15:12:25 +01:00
nMessageIDRet = ERR_SIZE_MISMATCH ;
if ( fConsumeCollateralRet ) * fConsumeCollateralRet = true ;
return false ;
}
auto checkTxOut = [ & ] ( const CTxOut & txout ) {
2021-03-17 23:36:11 +01:00
int nDenom = CCoinJoin : : AmountToDenomination ( txout . nValue ) ;
2020-01-01 15:12:25 +01:00
if ( nDenom ! = nSessionDenom ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBaseSession::IsValidInOuts -- ERROR: incompatible denom %d (%s) != nSessionDenom %d (%s) \n " ,
nDenom , CCoinJoin : : DenominationToString ( nDenom ) , nSessionDenom , CCoinJoin : : DenominationToString ( nSessionDenom ) ) ;
2020-01-01 15:12:25 +01:00
nMessageIDRet = ERR_DENOM ;
if ( fConsumeCollateralRet ) * fConsumeCollateralRet = true ;
return false ;
}
if ( ! txout . scriptPubKey . IsPayToPublicKeyHash ( ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBaseSession::IsValidInOuts -- ERROR: invalid script! scriptPubKey=%s \n " , ScriptToAsmStr ( txout . scriptPubKey ) ) ;
2020-01-01 15:12:25 +01:00
nMessageIDRet = ERR_INVALID_SCRIPT ;
if ( fConsumeCollateralRet ) * fConsumeCollateralRet = true ;
return false ;
}
if ( ! setScripPubKeys . insert ( txout . scriptPubKey ) . second ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBaseSession::IsValidInOuts -- ERROR: already have this script! scriptPubKey=%s \n " , ScriptToAsmStr ( txout . scriptPubKey ) ) ;
2020-01-01 15:12:25 +01:00
nMessageIDRet = ERR_ALREADY_HAVE ;
if ( fConsumeCollateralRet ) * fConsumeCollateralRet = true ;
return false ;
}
// IsPayToPublicKeyHash() above already checks for scriptPubKey size,
2021-07-30 02:01:02 +02:00
// no need to double-check, hence no usage of ERR_NON_STANDARD_PUBKEY
2020-01-01 15:12:25 +01:00
return true ;
} ;
CAmount nFees { 0 } ;
for ( const auto & txout : vout ) {
if ( ! checkTxOut ( txout ) ) {
return false ;
}
nFees - = txout . nValue ;
}
2019-07-24 17:45:04 +02:00
CCoinsViewMemPool viewMemPool ( & : : ChainstateActive ( ) . CoinsTip ( ) , mempool ) ;
2020-01-01 15:12:25 +01:00
for ( const auto & txin : vin ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBaseSession::%s -- txin=%s \n " , __func__ , txin . ToString ( ) ) ;
2020-01-01 15:12:25 +01:00
if ( txin . prevout . IsNull ( ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBaseSession::%s -- ERROR: invalid input! \n " , __func__ ) ;
2020-01-01 15:12:25 +01:00
nMessageIDRet = ERR_INVALID_INPUT ;
if ( fConsumeCollateralRet ) * fConsumeCollateralRet = true ;
return false ;
}
Coin coin ;
if ( ! viewMemPool . GetCoin ( txin . prevout , coin ) | | coin . IsSpent ( ) | |
( coin . nHeight = = MEMPOOL_HEIGHT & & ! llmq : : quorumInstantSendManager - > IsLocked ( txin . prevout . hash ) ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBaseSession::%s -- ERROR: missing, spent or non-locked mempool input! txin=%s \n " , __func__ , txin . ToString ( ) ) ;
2020-01-01 15:12:25 +01:00
nMessageIDRet = ERR_MISSING_TX ;
return false ;
}
if ( ! checkTxOut ( coin . out ) ) {
return false ;
}
nFees + = coin . out . nValue ;
}
// The same size and denom for inputs and outputs ensures their total value is also the same,
2021-07-30 02:01:02 +02:00
// no need to double-check. If not, we are doing something wrong, bail out.
2020-01-01 15:12:25 +01:00
if ( nFees ! = 0 ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoinBaseSession::%s -- ERROR: non-zero fees! fees: %lld \n " , __func__ , nFees ) ;
2020-01-01 15:12:25 +01:00
nMessageIDRet = ERR_FEES ;
return false ;
}
return true ;
}
2017-06-30 20:30:16 +02:00
// Definitions for static data members
2021-03-17 23:36:11 +01:00
std : : vector < CAmount > CCoinJoin : : vecStandardDenominations ;
CCriticalSection CCoinJoin : : cs_mapdstx ;
2021-09-30 20:58:33 +02:00
std : : map < uint256 , CCoinJoinBroadcastTx > CCoinJoin : : mapDSTX GUARDED_BY ( CCoinJoin : : cs_mapdstx ) ;
2017-06-30 20:30:16 +02:00
2021-03-17 23:36:11 +01:00
void CCoinJoin : : InitStandardDenominations ( )
2017-06-30 20:30:16 +02:00
{
vecStandardDenominations . clear ( ) ;
/* Denominations
2018-02-08 06:46:44 +01:00
A note about convertibility . Within mixing pools , each denomination
is convertible to another .
2017-06-30 20:30:16 +02:00
For example :
1 DRK + 1000 = = ( .1 DRK + 100 ) * 10
10 DRK + 10000 = = ( 1 DRK + 1000 ) * 10
*/
/* Disabled
vecStandardDenominations . push_back ( ( 100 * COIN ) + 100000 ) ;
*/
2018-11-05 10:29:07 +01:00
vecStandardDenominations . push_back ( ( 10 * COIN ) + 10000 ) ;
vecStandardDenominations . push_back ( ( 1 * COIN ) + 1000 ) ;
2021-06-26 15:10:53 +02:00
vecStandardDenominations . push_back ( ( COIN / 10 ) + 100 ) ;
vecStandardDenominations . push_back ( ( COIN / 100 ) + 10 ) ;
vecStandardDenominations . push_back ( ( COIN / 1000 ) + 1 ) ;
2017-06-30 20:30:16 +02:00
}
2017-05-05 13:26:27 +02:00
// check to make sure the collateral provided by the client is valid
2021-03-17 23:36:11 +01:00
bool CCoinJoin : : IsCollateralValid ( const CTransaction & txCollateral )
2017-05-05 13:26:27 +02:00
{
2018-11-05 10:29:07 +01:00
if ( txCollateral . vout . empty ( ) ) return false ;
if ( txCollateral . nLockTime ! = 0 ) return false ;
2017-05-05 13:26:27 +02:00
CAmount nValueIn = 0 ;
CAmount nValueOut = 0 ;
2018-02-06 12:09:33 +01:00
for ( const auto & txout : txCollateral . vout ) {
2017-05-05 13:26:27 +02:00
nValueOut + = txout . nValue ;
2018-11-07 08:39:25 +01:00
if ( ! txout . scriptPubKey . IsPayToPublicKeyHash ( ) & & ! txout . scriptPubKey . IsUnspendable ( ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoin::IsCollateralValid -- Invalid Script, txCollateral=%s " , txCollateral . ToString ( ) ) ; /* Continued */
2017-05-05 13:26:27 +02:00
return false ;
}
}
2018-02-06 12:09:33 +01:00
for ( const auto & txin : txCollateral . vin ) {
2017-09-26 16:33:46 +02:00
Coin coin ;
2019-04-29 10:32:08 +02:00
auto mempoolTx = mempool . get ( txin . prevout . hash ) ;
if ( mempoolTx ! = nullptr ) {
if ( mempool . isSpent ( txin . prevout ) | | ! llmq : : quorumInstantSendManager - > IsLocked ( txin . prevout . hash ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoin::IsCollateralValid -- spent or non-locked mempool input! txin=%s \n " , txin . ToString ( ) ) ;
2019-04-29 10:32:08 +02:00
return false ;
}
nValueIn + = mempoolTx - > vout [ txin . prevout . n ] . nValue ;
} else if ( GetUTXOCoin ( txin . prevout , coin ) ) {
nValueIn + = coin . out . nValue ;
} else {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoin::IsCollateralValid -- Unknown inputs in collateral transaction, txCollateral=%s " , txCollateral . ToString ( ) ) ; /* Continued */
2017-08-25 14:56:48 +02:00
return false ;
2017-05-05 13:26:27 +02:00
}
}
2017-06-30 20:30:16 +02:00
//collateral transactions are required to pay out a small fee to the miners
2018-11-05 10:29:07 +01:00
if ( nValueIn - nValueOut < GetCollateralAmount ( ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoin::IsCollateralValid -- did not include enough fees in transaction: fees: %d, txCollateral=%s " , nValueOut - nValueIn , txCollateral . ToString ( ) ) ; /* Continued */
2017-05-05 13:26:27 +02:00
return false ;
}
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoin::IsCollateralValid -- %s " , txCollateral . ToString ( ) ) ; /* Continued */
2017-05-05 13:26:27 +02:00
{
LOCK ( cs_main ) ;
CValidationState validationState ;
2017-09-29 15:07:48 +02:00
if ( ! AcceptToMemoryPool ( mempool , validationState , MakeTransactionRef ( txCollateral ) , nullptr /* pfMissingInputs */ , false /* bypass_limits */ , maxTxFee /* nAbsurdFee */ , true /* fDryRun */ ) ) {
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoin::IsCollateralValid -- didn't pass AcceptToMemoryPool() \n " ) ;
2017-05-05 13:26:27 +02:00
return false ;
}
}
return true ;
}
2021-03-17 23:36:11 +01:00
bool CCoinJoin : : IsCollateralAmount ( CAmount nInputAmount )
2017-12-04 07:06:07 +01:00
{
2018-11-07 08:39:25 +01:00
// collateral input can be anything between 1x and "max" (including both)
return ( nInputAmount > = GetCollateralAmount ( ) & & nInputAmount < = GetMaxCollateralAmount ( ) ) ;
2017-12-04 07:06:07 +01:00
}
Merge #12257: [wallet] Use destination groups instead of coins in coin select
232f96f5c8a3920c09db92f4dbac2ad7d10ce8cf doc: Add release notes for -avoidpartialspends (Karl-Johan Alm)
e00b4699cc6d2ee5697d38dd6607eb2631c9b77a clean-up: Remove no longer used ivars from CInputCoin (Karl-Johan Alm)
43e04d13b1ffc02b1082176e87f420198b40c7b1 wallet: Remove deprecated OutputEligibleForSpending (Karl-Johan Alm)
0128121101fb3ee82f3abd3973a967a4226ffe0e test: Add basic testing for wallet groups (Karl-Johan Alm)
59d6f7b4e2f847ec1f2ff46c84e6157655984f85 wallet: Switch to using output groups instead of coins in coin selection (Karl-Johan Alm)
87ebce25d66952f5ce565bb5130dcf5e24049872 wallet: Add output grouping (Karl-Johan Alm)
bb629cb9dc567cc819724d9f4852652926e60cbf Add -avoidpartialspends and m_avoid_partial_spends (Karl-Johan Alm)
65b3eda458221644616d0fdd6ba0fe01bdbce893 wallet: Add input bytes to CInputCoin (Karl-Johan Alm)
a443d7a0ca333b0bae63e04b5d476f9ad9c7aeac moveonly: CoinElegibilityFilter into coinselection.h (Karl-Johan Alm)
173e18a289088c6087ba6fac708e322aa63b7a94 utils: Add insert() convenience templates (Karl-Johan Alm)
Pull request description:
This PR adds an optional (off by default) `-avoidpartialspends` flag, which changes coin select to use output groups rather than outputs, where each output group corresponds to all outputs with the same destination.
It is a privacy improvement, as each time you spend some output, any other output that is publicly associated with the destination (address) will also be spent at the same time, at the cost of fee increase for cases where coin select without group restriction would find a more optimal set of coins (see example below).
For regular use without address reuse, this PR should have no effect on the user experience whatsoever; it only affects users who, for some reason, have multiple outputs with the same destination (i.e. address reuse).
Nodes with this turned off will still try to avoid partial spending, if the fee of the resulting transaction is not greater than the fee of the original transaction.
Example: a node has four outputs linked to two addresses `A` and `B`:
* 1.0 btc to `A`
* 0.5 btc to `A`
* 1.0 btc to `B`
* 0.5 btc to `B`
The node sends 0.2 btc to `C`. Without `-avoidpartialspends`, the following coin selection will occur:
* 0.5 btc to `A` or `B` is picked
* 0.2 btc is output to `C`
* 0.3 - fee is output to (unique change address)
With `-avoidpartialspends`, the following will instead happen:
* Both of (0.5, 1.0) btc to `A` or `B` is picked (one or the other pair)
* 0.2 btc is output to `C`
* 1.3 - fee is output to (unique change address)
As noted, the pro here is that, assuming nobody sends to the address after you spend from it, you will only ever use one address once. The con is that the transaction becomes slightly larger in this case, because it is overpicking outputs to adhere to the no partial spending rule.
This complements #10386, in particular it addresses @luke-jr and @gmaxwell's concerns in https://github.com/bitcoin/bitcoin/pull/10386#issuecomment-300667926 and https://github.com/bitcoin/bitcoin/pull/10386#issuecomment-302361381.
Together with `-avoidreuse`, this fully addresses the concerns in #10065 I believe.
Tree-SHA512: 24687a4490ba59cf4198ed90052944ff4996653a4257833bb52ed24d058b3e924800c9b3790aeb6be6385b653b49e304453e5d7ff960e64c682fc23bfc447621
# Conflicts:
# src/Makefile.am
# src/bench/coin_selection.cpp
# src/wallet/coincontrol.h
# src/wallet/coinselection.cpp
# src/wallet/coinselection.h
# src/wallet/init.cpp
# src/wallet/test/coinselector_tests.cpp
# src/wallet/wallet.cpp
# src/wallet/wallet.h
# test/functional/test_runner.py
2018-07-24 15:06:21 +02:00
int CCoinJoin : : CalculateAmountPriority ( CAmount nInputAmount )
{
for ( const auto & d : GetStandardDenominations ( ) ) {
// large denoms have lower value
if ( nInputAmount = = d ) {
return ( float ) COIN / d * 10000 ;
}
}
if ( nInputAmount < COIN ) {
return 20000 ;
}
//nondenom return largest first
return - 1 * ( nInputAmount / COIN ) ;
}
2020-03-12 11:31:55 +01:00
/*
Return a bitshifted integer representing a denomination in vecStandardDenominations
or 0 if none was found
2017-05-05 13:26:27 +02:00
*/
2021-03-17 23:36:11 +01:00
int CCoinJoin : : AmountToDenomination ( CAmount nInputAmount )
2017-05-05 13:26:27 +02:00
{
2020-03-12 11:31:55 +01:00
for ( size_t i = 0 ; i < vecStandardDenominations . size ( ) ; + + i ) {
if ( nInputAmount = = vecStandardDenominations [ i ] ) {
return 1 < < i ;
2017-05-05 13:26:27 +02:00
}
}
2020-03-12 11:31:55 +01:00
return 0 ;
2017-05-05 13:26:27 +02:00
}
2020-03-12 11:31:55 +01:00
/*
Returns :
- one of standard denominations from vecStandardDenominations based on the provided bitshifted integer
- 0 for non - initialized sessions ( nDenom = 0 )
2021-07-17 21:15:21 +02:00
- a value below 0 if an error occurred while converting from one to another
2017-05-05 13:26:27 +02:00
*/
2021-03-17 23:36:11 +01:00
CAmount CCoinJoin : : DenominationToAmount ( int nDenom )
2017-05-05 13:26:27 +02:00
{
2020-03-12 11:31:55 +01:00
if ( nDenom = = 0 ) {
// not initialized
return 0 ;
2019-06-18 13:33:05 +02:00
}
2017-05-05 13:26:27 +02:00
2020-03-12 11:31:55 +01:00
size_t nMaxDenoms = vecStandardDenominations . size ( ) ;
2017-05-05 13:26:27 +02:00
2020-03-12 11:31:55 +01:00
if ( nDenom > = ( 1 < < nMaxDenoms ) | | nDenom < 0 ) {
// out of bounds
return - 1 ;
2017-05-05 13:26:27 +02:00
}
2020-03-12 11:31:55 +01:00
if ( ( nDenom & ( nDenom - 1 ) ) ! = 0 ) {
// non-denom
return - 2 ;
}
2017-05-05 13:26:27 +02:00
2020-03-12 11:31:55 +01:00
CAmount nDenomAmount { - 3 } ;
2017-05-05 13:26:27 +02:00
2020-03-12 11:31:55 +01:00
for ( size_t i = 0 ; i < nMaxDenoms ; + + i ) {
2018-11-05 10:29:07 +01:00
if ( nDenom & ( 1 < < i ) ) {
2020-03-12 11:31:55 +01:00
nDenomAmount = vecStandardDenominations [ i ] ;
break ;
2017-05-05 13:26:27 +02:00
}
}
2020-03-12 11:31:55 +01:00
return nDenomAmount ;
2017-05-05 13:26:27 +02:00
}
2020-03-12 11:31:55 +01:00
/*
Same as DenominationToAmount but returns a string representation
*/
2021-03-17 23:36:11 +01:00
std : : string CCoinJoin : : DenominationToString ( int nDenom )
2017-05-05 13:26:27 +02:00
{
2020-03-12 11:31:55 +01:00
CAmount nDenomAmount = DenominationToAmount ( nDenom ) ;
switch ( nDenomAmount ) {
case 0 : return " N/A " ;
case - 1 : return " out-of-bounds " ;
case - 2 : return " non-denom " ;
case - 3 : return " to-amount-error " ;
default : return ValueFromAmount ( nDenomAmount ) . getValStr ( ) ;
2017-05-05 13:26:27 +02:00
}
2020-03-12 11:31:55 +01:00
// shouldn't happen
return " to-string-error " ;
2017-05-05 13:26:27 +02:00
}
2021-03-17 23:36:11 +01:00
bool CCoinJoin : : IsDenominatedAmount ( CAmount nInputAmount )
2017-12-04 07:06:07 +01:00
{
2020-03-12 11:31:55 +01:00
return AmountToDenomination ( nInputAmount ) > 0 ;
}
2021-03-17 23:36:11 +01:00
bool CCoinJoin : : IsValidDenomination ( int nDenom )
2020-03-12 11:31:55 +01:00
{
return DenominationToAmount ( nDenom ) > 0 ;
2017-12-04 07:06:07 +01:00
}
2021-03-17 23:36:11 +01:00
std : : string CCoinJoin : : GetMessageByID ( PoolMessage nMessageID )
2017-05-05 13:26:27 +02:00
{
switch ( nMessageID ) {
2018-11-05 10:29:07 +01:00
case ERR_ALREADY_HAVE :
return _ ( " Already have that input. " ) ;
case ERR_DENOM :
return _ ( " No matching denominations found for mixing. " ) ;
case ERR_ENTRIES_FULL :
return _ ( " Entries are full. " ) ;
case ERR_EXISTING_TX :
return _ ( " Not compatible with existing transactions. " ) ;
case ERR_FEES :
return _ ( " Transaction fees are too high. " ) ;
case ERR_INVALID_COLLATERAL :
return _ ( " Collateral not valid. " ) ;
case ERR_INVALID_INPUT :
return _ ( " Input is not valid. " ) ;
case ERR_INVALID_SCRIPT :
return _ ( " Invalid script detected. " ) ;
case ERR_INVALID_TX :
return _ ( " Transaction not valid. " ) ;
case ERR_MAXIMUM :
return _ ( " Entry exceeds maximum size. " ) ;
case ERR_MN_LIST :
return _ ( " Not in the Masternode list. " ) ;
case ERR_MODE :
return _ ( " Incompatible mode. " ) ;
case ERR_QUEUE_FULL :
return _ ( " Masternode queue is full. " ) ;
case ERR_RECENT :
2021-03-24 11:13:25 +01:00
return _ ( " Last queue was created too recently. " ) ;
2018-11-05 10:29:07 +01:00
case ERR_SESSION :
return _ ( " Session not complete! " ) ;
case ERR_MISSING_TX :
return _ ( " Missing input transaction information. " ) ;
case ERR_VERSION :
return _ ( " Incompatible version. " ) ;
case MSG_NOERR :
return _ ( " No errors detected. " ) ;
case MSG_SUCCESS :
return _ ( " Transaction created successfully. " ) ;
case MSG_ENTRIES_ADDED :
return _ ( " Your entries added successfully. " ) ;
2019-05-23 11:13:34 +02:00
case ERR_SIZE_MISMATCH :
return _ ( " Inputs vs outputs size mismatch. " ) ;
2020-01-01 15:12:25 +01:00
case ERR_NON_STANDARD_PUBKEY :
case ERR_NOT_A_MN :
2018-11-05 10:29:07 +01:00
default :
return _ ( " Unknown response. " ) ;
2017-05-05 13:26:27 +02:00
}
}
2021-03-17 23:36:11 +01:00
void CCoinJoin : : AddDSTX ( const CCoinJoinBroadcastTx & dstx )
2017-05-05 13:26:27 +02:00
{
2017-06-30 20:30:16 +02:00
LOCK ( cs_mapdstx ) ;
2017-09-20 14:02:53 +02:00
mapDSTX . insert ( std : : make_pair ( dstx . tx - > GetHash ( ) , dstx ) ) ;
2017-05-05 13:26:27 +02:00
}
2021-03-17 23:36:11 +01:00
CCoinJoinBroadcastTx CCoinJoin : : GetDSTX ( const uint256 & hash )
2017-05-05 13:26:27 +02:00
{
2017-06-30 20:30:16 +02:00
LOCK ( cs_mapdstx ) ;
auto it = mapDSTX . find ( hash ) ;
2021-03-17 23:36:11 +01:00
return ( it = = mapDSTX . end ( ) ) ? CCoinJoinBroadcastTx ( ) : it - > second ;
2017-05-05 13:26:27 +02:00
}
2021-03-17 23:36:11 +01:00
void CCoinJoin : : CheckDSTXes ( const CBlockIndex * pindex )
2017-07-10 16:42:09 +02:00
{
LOCK ( cs_mapdstx ) ;
2019-06-18 13:33:05 +02:00
auto it = mapDSTX . begin ( ) ;
2018-11-05 10:29:07 +01:00
while ( it ! = mapDSTX . end ( ) ) {
2019-07-07 00:07:03 +02:00
if ( it - > second . IsExpired ( pindex ) ) {
2017-07-10 16:42:09 +02:00
mapDSTX . erase ( it + + ) ;
} else {
+ + it ;
}
}
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoin::CheckDSTXes -- mapDSTX.size()=%llu \n " , mapDSTX . size ( ) ) ;
2017-07-10 16:42:09 +02:00
}
2021-03-17 23:36:11 +01:00
void CCoinJoin : : UpdatedBlockTip ( const CBlockIndex * pindex )
2017-12-07 10:42:47 +01:00
{
2019-06-27 22:24:43 +02:00
if ( pindex & & masternodeSync . IsBlockchainSynced ( ) ) {
2019-07-07 00:07:03 +02:00
CheckDSTXes ( pindex ) ;
2017-12-07 10:42:47 +01:00
}
}
2021-03-17 23:36:11 +01:00
void CCoinJoin : : NotifyChainLock ( const CBlockIndex * pindex )
2020-01-22 11:35:31 +01:00
{
if ( pindex & & masternodeSync . IsBlockchainSynced ( ) ) {
CheckDSTXes ( pindex ) ;
}
}
2021-03-17 23:36:11 +01:00
void CCoinJoin : : UpdateDSTXConfirmedHeight ( const CTransactionRef & tx , int nHeight )
2017-07-10 16:42:09 +02:00
{
2019-05-27 16:22:09 +02:00
AssertLockHeld ( cs_mapdstx ) ;
2017-07-10 16:42:09 +02:00
2019-05-27 16:23:19 +02:00
auto it = mapDSTX . find ( tx - > GetHash ( ) ) ;
if ( it = = mapDSTX . end ( ) ) {
return ;
}
2017-07-10 16:42:09 +02:00
2019-05-27 16:23:19 +02:00
it - > second . SetConfirmedHeight ( nHeight ) ;
2021-03-17 23:36:11 +01:00
LogPrint ( BCLog : : COINJOIN , " CCoinJoin::%s -- txid=%s, nHeight=%d \n " , __func__ , tx - > GetHash ( ) . ToString ( ) , nHeight ) ;
2019-05-27 16:22:09 +02:00
}
2021-03-17 23:36:11 +01:00
void CCoinJoin : : TransactionAddedToMempool ( const CTransactionRef & tx )
2019-05-27 16:22:09 +02:00
{
LOCK ( cs_mapdstx ) ;
UpdateDSTXConfirmedHeight ( tx , - 1 ) ;
}
2021-03-17 23:36:11 +01:00
void CCoinJoin : : BlockConnected ( const std : : shared_ptr < const CBlock > & pblock , const CBlockIndex * pindex , const std : : vector < CTransactionRef > & vtxConflicted )
2019-05-27 16:22:09 +02:00
{
LOCK ( cs_mapdstx ) ;
for ( const auto & tx : vtxConflicted ) {
UpdateDSTXConfirmedHeight ( tx , - 1 ) ;
}
for ( const auto & tx : pblock - > vtx ) {
UpdateDSTXConfirmedHeight ( tx , pindex - > nHeight ) ;
}
}
2021-06-26 15:10:53 +02:00
void CCoinJoin : : BlockDisconnected ( const std : : shared_ptr < const CBlock > & pblock , const CBlockIndex * )
2019-05-27 16:22:09 +02:00
{
LOCK ( cs_mapdstx ) ;
for ( const auto & tx : pblock - > vtx ) {
UpdateDSTXConfirmedHeight ( tx , - 1 ) ;
}
2017-07-10 16:42:09 +02:00
}
2021-10-25 15:55:34 +02:00
int CCoinJoin : : GetMinPoolParticipants ( ) { return Params ( ) . PoolMinParticipants ( ) ; }
int CCoinJoin : : GetMaxPoolParticipants ( ) { return Params ( ) . PoolMaxParticipants ( ) ; }