2017-05-05 13:26:27 +02:00
// Copyright (c) 2014-2017 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.
# include "privatesend-client.h"
# include "coincontrol.h"
# include "consensus/validation.h"
# include "core_io.h"
# include "init.h"
# include "masternode-sync.h"
# include "masternodeman.h"
# include "script/sign.h"
# include "txmempool.h"
# include "util.h"
# include "utilmoneystr.h"
2017-07-03 15:14:07 +02:00
2017-05-28 15:50:07 +02:00
# include <memory>
2017-05-05 13:26:27 +02:00
CPrivateSendClient privateSendClient ;
void CPrivateSendClient : : ProcessMessage ( CNode * pfrom , std : : string & strCommand , CDataStream & vRecv )
{
if ( fMasterNode ) return ;
if ( fLiteMode ) return ; // ignore all Dash related functionality
if ( ! masternodeSync . IsBlockchainSynced ( ) ) return ;
if ( strCommand = = NetMsgType : : DSQUEUE ) {
TRY_LOCK ( cs_darksend , lockRecv ) ;
if ( ! lockRecv ) return ;
if ( pfrom - > nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION ) {
LogPrint ( " privatesend " , " DSQUEUE -- incompatible version! nVersion: %d \n " , pfrom - > nVersion ) ;
return ;
}
CDarksendQueue dsq ;
vRecv > > dsq ;
// process every dsq only once
BOOST_FOREACH ( CDarksendQueue q , vecDarksendQueue ) {
if ( q = = dsq ) {
// LogPrint("privatesend", "DSQUEUE -- %s seen\n", dsq.ToString());
return ;
}
}
LogPrint ( " privatesend " , " DSQUEUE -- %s new \n " , dsq . ToString ( ) ) ;
if ( dsq . IsExpired ( ) | | dsq . nTime > GetTime ( ) + PRIVATESEND_QUEUE_TIMEOUT ) return ;
masternode_info_t infoMn = mnodeman . GetMasternodeInfo ( dsq . vin ) ;
if ( ! infoMn . fInfoValid ) return ;
if ( ! dsq . CheckSignature ( infoMn . pubKeyMasternode ) ) {
// we probably have outdated info
mnodeman . AskForMN ( pfrom , dsq . vin ) ;
return ;
}
// if the queue is ready, submit if we can
if ( dsq . fReady ) {
if ( ! infoMixingMasternode . fInfoValid ) return ;
if ( ( CNetAddr ) infoMixingMasternode . addr ! = ( CNetAddr ) infoMn . addr ) {
LogPrintf ( " DSQUEUE -- message doesn't match current Masternode: infoMixingMasternode=%s, addr=%s \n " , infoMixingMasternode . addr . ToString ( ) , infoMn . addr . ToString ( ) ) ;
return ;
}
if ( nState = = POOL_STATE_QUEUE ) {
LogPrint ( " privatesend " , " DSQUEUE -- PrivateSend queue (%s) is ready on masternode %s \n " , dsq . ToString ( ) , infoMn . addr . ToString ( ) ) ;
SubmitDenominate ( ) ;
}
} else {
BOOST_FOREACH ( CDarksendQueue q , vecDarksendQueue ) {
if ( q . vin = = dsq . vin ) {
// no way same mn can send another "not yet ready" dsq this soon
LogPrint ( " privatesend " , " DSQUEUE -- Masternode %s is sending WAY too many dsq messages \n " , infoMn . addr . ToString ( ) ) ;
return ;
}
}
int nThreshold = infoMn . nLastDsq + mnodeman . CountEnabled ( MIN_PRIVATESEND_PEER_PROTO_VERSION ) / 5 ;
LogPrint ( " privatesend " , " DSQUEUE -- nLastDsq: %d threshold: %d nDsqCount: %d \n " , infoMn . nLastDsq , nThreshold , mnodeman . nDsqCount ) ;
//don't allow a few nodes to dominate the queuing process
if ( infoMn . nLastDsq ! = 0 & & nThreshold > mnodeman . nDsqCount ) {
LogPrint ( " privatesend " , " DSQUEUE -- Masternode %s is sending too many dsq messages \n " , infoMn . addr . ToString ( ) ) ;
return ;
}
mnodeman . nDsqCount + + ;
if ( ! mnodeman . UpdateLastDsq ( dsq . vin ) ) return ;
LogPrint ( " privatesend " , " DSQUEUE -- new PrivateSend queue (%s) from masternode %s \n " , dsq . ToString ( ) , infoMn . addr . ToString ( ) ) ;
if ( infoMixingMasternode . fInfoValid & & infoMixingMasternode . vin . prevout = = dsq . vin . prevout ) {
dsq . fTried = true ;
}
vecDarksendQueue . push_back ( dsq ) ;
dsq . Relay ( ) ;
}
} else if ( strCommand = = NetMsgType : : DSSTATUSUPDATE ) {
if ( pfrom - > nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION ) {
LogPrintf ( " DSSTATUSUPDATE -- incompatible version! nVersion: %d \n " , pfrom - > nVersion ) ;
return ;
}
if ( ! infoMixingMasternode . fInfoValid ) return ;
if ( ( CNetAddr ) infoMixingMasternode . addr ! = ( CNetAddr ) pfrom - > addr ) {
//LogPrintf("DSSTATUSUPDATE -- message doesn't match current Masternode: infoMixingMasternode %s addr %s\n", infoMixingMasternode.addr.ToString(), pfrom->addr.ToString());
return ;
}
int nMsgSessionID ;
int nMsgState ;
int nMsgEntriesCount ;
int nMsgStatusUpdate ;
int nMsgMessageID ;
vRecv > > nMsgSessionID > > nMsgState > > nMsgEntriesCount > > nMsgStatusUpdate > > nMsgMessageID ;
LogPrint ( " privatesend " , " DSSTATUSUPDATE -- nMsgSessionID %d nMsgState: %d nEntriesCount: %d nMsgStatusUpdate: %d nMsgMessageID %d \n " ,
nMsgSessionID , nMsgState , nEntriesCount , nMsgStatusUpdate , nMsgMessageID ) ;
if ( nMsgState < POOL_STATE_MIN | | nMsgState > POOL_STATE_MAX ) {
LogPrint ( " privatesend " , " DSSTATUSUPDATE -- nMsgState is out of bounds: %d \n " , nMsgState ) ;
return ;
}
if ( nMsgStatusUpdate < STATUS_REJECTED | | nMsgStatusUpdate > STATUS_ACCEPTED ) {
LogPrint ( " privatesend " , " DSSTATUSUPDATE -- nMsgStatusUpdate is out of bounds: %d \n " , nMsgStatusUpdate ) ;
return ;
}
if ( nMsgMessageID < MSG_POOL_MIN | | nMsgMessageID > MSG_POOL_MAX ) {
LogPrint ( " privatesend " , " DSSTATUSUPDATE -- nMsgMessageID is out of bounds: %d \n " , nMsgMessageID ) ;
return ;
}
2017-06-30 20:30:16 +02:00
LogPrint ( " privatesend " , " DSSTATUSUPDATE -- GetMessageByID: %s \n " , CPrivateSend : : GetMessageByID ( PoolMessage ( nMsgMessageID ) ) ) ;
2017-05-05 13:26:27 +02:00
if ( ! CheckPoolStateUpdate ( PoolState ( nMsgState ) , nMsgEntriesCount , PoolStatusUpdate ( nMsgStatusUpdate ) , PoolMessage ( nMsgMessageID ) , nMsgSessionID ) ) {
LogPrint ( " privatesend " , " DSSTATUSUPDATE -- CheckPoolStateUpdate failed \n " ) ;
}
} else if ( strCommand = = NetMsgType : : DSFINALTX ) {
if ( pfrom - > nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION ) {
LogPrintf ( " DSFINALTX -- incompatible version! nVersion: %d \n " , pfrom - > nVersion ) ;
return ;
}
if ( ! infoMixingMasternode . fInfoValid ) return ;
if ( ( CNetAddr ) infoMixingMasternode . addr ! = ( CNetAddr ) pfrom - > addr ) {
//LogPrintf("DSFINALTX -- message doesn't match current Masternode: infoMixingMasternode %s addr %s\n", infoMixingMasternode.addr.ToString(), pfrom->addr.ToString());
return ;
}
int nMsgSessionID ;
CTransaction txNew ;
vRecv > > nMsgSessionID > > txNew ;
if ( nSessionID ! = nMsgSessionID ) {
LogPrint ( " privatesend " , " DSFINALTX -- message doesn't match current PrivateSend session: nSessionID: %d nMsgSessionID: %d \n " , nSessionID , nMsgSessionID ) ;
return ;
}
LogPrint ( " privatesend " , " DSFINALTX -- txNew %s " , txNew . ToString ( ) ) ;
//check to see if input is spent already? (and probably not confirmed)
SignFinalTransaction ( txNew , pfrom ) ;
} else if ( strCommand = = NetMsgType : : DSCOMPLETE ) {
if ( pfrom - > nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION ) {
LogPrintf ( " DSCOMPLETE -- incompatible version! nVersion: %d \n " , pfrom - > nVersion ) ;
return ;
}
if ( ! infoMixingMasternode . fInfoValid ) return ;
if ( ( CNetAddr ) infoMixingMasternode . addr ! = ( CNetAddr ) pfrom - > addr ) {
LogPrint ( " privatesend " , " DSCOMPLETE -- message doesn't match current Masternode: infoMixingMasternode=%s addr=%s \n " , infoMixingMasternode . addr . ToString ( ) , pfrom - > addr . ToString ( ) ) ;
return ;
}
int nMsgSessionID ;
int nMsgMessageID ;
vRecv > > nMsgSessionID > > nMsgMessageID ;
if ( nMsgMessageID < MSG_POOL_MIN | | nMsgMessageID > MSG_POOL_MAX ) {
LogPrint ( " privatesend " , " DSCOMPLETE -- nMsgMessageID is out of bounds: %d \n " , nMsgMessageID ) ;
return ;
}
if ( nSessionID ! = nMsgSessionID ) {
LogPrint ( " privatesend " , " DSCOMPLETE -- message doesn't match current PrivateSend session: nSessionID: %d nMsgSessionID: %d \n " , nSessionID , nMsgSessionID ) ;
return ;
}
2017-06-30 20:30:16 +02:00
LogPrint ( " privatesend " , " DSCOMPLETE -- nMsgSessionID %d nMsgMessageID %d (%s) \n " , nMsgSessionID , nMsgMessageID , CPrivateSend : : GetMessageByID ( PoolMessage ( nMsgMessageID ) ) ) ;
2017-05-05 13:26:27 +02:00
CompletedTransaction ( PoolMessage ( nMsgMessageID ) ) ;
}
}
void CPrivateSendClient : : ResetPool ( )
{
nCachedLastSuccessBlock = 0 ;
txMyCollateral = CMutableTransaction ( ) ;
vecMasternodesUsed . clear ( ) ;
UnlockCoins ( ) ;
SetNull ( ) ;
}
void CPrivateSendClient : : SetNull ( )
{
// Client side
nEntriesCount = 0 ;
fLastEntryAccepted = false ;
infoMixingMasternode = masternode_info_t ( ) ;
2017-06-30 20:30:16 +02:00
CPrivateSendBase : : SetNull ( ) ;
2017-05-05 13:26:27 +02:00
}
//
// Unlock coins after mixing fails or succeeds
//
void CPrivateSendClient : : UnlockCoins ( )
{
while ( true ) {
TRY_LOCK ( pwalletMain - > cs_wallet , lockWallet ) ;
if ( ! lockWallet ) { MilliSleep ( 50 ) ; continue ; }
BOOST_FOREACH ( COutPoint outpoint , vecOutPointLocked )
pwalletMain - > UnlockCoin ( outpoint ) ;
break ;
}
vecOutPointLocked . clear ( ) ;
}
std : : string CPrivateSendClient : : GetStatus ( )
{
static int nStatusMessageProgress = 0 ;
nStatusMessageProgress + = 10 ;
std : : string strSuffix = " " ;
if ( ( pCurrentBlockIndex & & pCurrentBlockIndex - > nHeight - nCachedLastSuccessBlock < nMinBlockSpacing ) | | ! masternodeSync . IsBlockchainSynced ( ) )
return strAutoDenomResult ;
switch ( nState ) {
case POOL_STATE_IDLE :
return _ ( " PrivateSend is idle. " ) ;
case POOL_STATE_QUEUE :
if ( nStatusMessageProgress % 70 < = 30 ) strSuffix = " . " ;
else if ( nStatusMessageProgress % 70 < = 50 ) strSuffix = " .. " ;
else if ( nStatusMessageProgress % 70 < = 70 ) strSuffix = " ... " ;
return strprintf ( _ ( " Submitted to masternode, waiting in queue %s " ) , strSuffix ) ; ;
case POOL_STATE_ACCEPTING_ENTRIES :
if ( nEntriesCount = = 0 ) {
nStatusMessageProgress = 0 ;
return strAutoDenomResult ;
} else if ( fLastEntryAccepted ) {
if ( nStatusMessageProgress % 10 > 8 ) {
fLastEntryAccepted = false ;
nStatusMessageProgress = 0 ;
}
return _ ( " PrivateSend request complete: " ) + " " + _ ( " Your transaction was accepted into the pool! " ) ;
} else {
2017-06-30 20:30:16 +02:00
if ( nStatusMessageProgress % 70 < = 40 ) return strprintf ( _ ( " Submitted following entries to masternode: %u / %d " ) , nEntriesCount , CPrivateSend : : GetMaxPoolTransactions ( ) ) ;
2017-05-05 13:26:27 +02:00
else if ( nStatusMessageProgress % 70 < = 50 ) strSuffix = " . " ;
else if ( nStatusMessageProgress % 70 < = 60 ) strSuffix = " .. " ;
else if ( nStatusMessageProgress % 70 < = 70 ) strSuffix = " ... " ;
2017-06-30 20:30:16 +02:00
return strprintf ( _ ( " Submitted to masternode, waiting for more entries ( %u / %d ) % s " ), nEntriesCount, CPrivateSend::GetMaxPoolTransactions(), strSuffix) ;
2017-05-05 13:26:27 +02:00
}
case POOL_STATE_SIGNING :
if ( nStatusMessageProgress % 70 < = 40 ) return _ ( " Found enough users, signing ... " ) ;
else if ( nStatusMessageProgress % 70 < = 50 ) strSuffix = " . " ;
else if ( nStatusMessageProgress % 70 < = 60 ) strSuffix = " .. " ;
else if ( nStatusMessageProgress % 70 < = 70 ) strSuffix = " ... " ;
return strprintf ( _ ( " Found enough users, signing ( waiting %s ) " ), strSuffix) ;
case POOL_STATE_ERROR :
return _ ( " PrivateSend request incomplete: " ) + " " + strLastMessage + " " + _ ( " Will retry... " ) ;
case POOL_STATE_SUCCESS :
return _ ( " PrivateSend request complete: " ) + " " + strLastMessage ;
default :
return strprintf ( _ ( " Unknown state: id = %u " ) , nState ) ;
}
}
//
// Check the mixing progress and send client updates if a Masternode
//
void CPrivateSendClient : : CheckPool ( )
{
// reset if we're here for 10 seconds
if ( ( nState = = POOL_STATE_ERROR | | nState = = POOL_STATE_SUCCESS ) & & GetTimeMillis ( ) - nTimeLastSuccessfulStep > = 10000 ) {
LogPrint ( " privatesend " , " CPrivateSendClient::CheckPool -- timeout, RESETTING \n " ) ;
UnlockCoins ( ) ;
SetNull ( ) ;
}
}
//
// Check for various timeouts (queue objects, mixing, etc)
//
void CPrivateSendClient : : CheckTimeout ( )
{
{
TRY_LOCK ( cs_darksend , lockDS ) ;
if ( ! lockDS ) return ; // it's ok to fail here, we run this quite frequently
// check mixing queue objects for timeouts
std : : vector < CDarksendQueue > : : iterator it = vecDarksendQueue . begin ( ) ;
while ( it ! = vecDarksendQueue . end ( ) ) {
if ( ( * it ) . IsExpired ( ) ) {
LogPrint ( " privatesend " , " CPrivateSendClient::CheckTimeout -- Removing expired queue (%s) \n " , ( * it ) . ToString ( ) ) ;
it = vecDarksendQueue . erase ( it ) ;
} else + + it ;
}
}
if ( ! fEnablePrivateSend & & ! fMasterNode ) return ;
// catching hanging sessions
if ( ! fMasterNode ) {
switch ( nState ) {
case POOL_STATE_ERROR :
LogPrint ( " privatesend " , " CPrivateSendClient::CheckTimeout -- Pool error -- Running CheckPool \n " ) ;
CheckPool ( ) ;
break ;
case POOL_STATE_SUCCESS :
LogPrint ( " privatesend " , " CPrivateSendClient::CheckTimeout -- Pool success -- Running CheckPool \n " ) ;
CheckPool ( ) ;
break ;
default :
break ;
}
}
int nLagTime = fMasterNode ? 0 : 10000 ; // if we're the client, give the server a few extra seconds before resetting.
int nTimeout = ( nState = = POOL_STATE_SIGNING ) ? PRIVATESEND_SIGNING_TIMEOUT : PRIVATESEND_QUEUE_TIMEOUT ;
bool fTimeout = GetTimeMillis ( ) - nTimeLastSuccessfulStep > = nTimeout * 1000 + nLagTime ;
if ( nState ! = POOL_STATE_IDLE & & fTimeout ) {
LogPrint ( " privatesend " , " CPrivateSendClient::CheckTimeout -- %s timed out (%ds) -- restting \n " ,
( nState = = POOL_STATE_SIGNING ) ? " Signing " : " Session " , nTimeout ) ;
UnlockCoins ( ) ;
SetNull ( ) ;
SetState ( POOL_STATE_ERROR ) ;
strLastMessage = _ ( " Session timed out. " ) ;
}
}
//
// Execute a mixing denomination via a Masternode.
// This is only ran from clients
//
bool CPrivateSendClient : : SendDenominate ( const std : : vector < CTxIn > & vecTxIn , const std : : vector < CTxOut > & vecTxOut )
{
if ( fMasterNode ) {
LogPrintf ( " CPrivateSendClient::SendDenominate -- PrivateSend from a Masternode is not supported currently. \n " ) ;
return false ;
}
if ( txMyCollateral = = CMutableTransaction ( ) ) {
LogPrintf ( " CPrivateSendClient:SendDenominate -- PrivateSend collateral not set \n " ) ;
return false ;
}
// lock the funds we're going to use
BOOST_FOREACH ( CTxIn txin , txMyCollateral . vin )
vecOutPointLocked . push_back ( txin . prevout ) ;
BOOST_FOREACH ( CTxIn txin , vecTxIn )
vecOutPointLocked . push_back ( txin . prevout ) ;
// we should already be connected to a Masternode
if ( ! nSessionID ) {
LogPrintf ( " CPrivateSendClient::SendDenominate -- No Masternode has been selected yet. \n " ) ;
UnlockCoins ( ) ;
SetNull ( ) ;
return false ;
}
if ( ! CheckDiskSpace ( ) ) {
UnlockCoins ( ) ;
SetNull ( ) ;
fEnablePrivateSend = false ;
LogPrintf ( " CPrivateSendClient::SendDenominate -- Not enough disk space, disabling PrivateSend. \n " ) ;
return false ;
}
SetState ( POOL_STATE_ACCEPTING_ENTRIES ) ;
strLastMessage = " " ;
LogPrintf ( " CPrivateSendClient::SendDenominate -- Added transaction to pool. \n " ) ;
//check it against the memory pool to make sure it's valid
{
CValidationState validationState ;
CMutableTransaction tx ;
BOOST_FOREACH ( const CTxIn & txin , vecTxIn ) {
LogPrint ( " privatesend " , " CPrivateSendClient::SendDenominate -- txin=%s \n " , txin . ToString ( ) ) ;
tx . vin . push_back ( txin ) ;
}
BOOST_FOREACH ( const CTxOut & txout , vecTxOut ) {
LogPrint ( " privatesend " , " CPrivateSendClient::SendDenominate -- txout=%s \n " , txout . ToString ( ) ) ;
tx . vout . push_back ( txout ) ;
}
LogPrintf ( " CPrivateSendClient::SendDenominate -- Submitting partial tx %s " , tx . ToString ( ) ) ;
mempool . PrioritiseTransaction ( tx . GetHash ( ) , tx . GetHash ( ) . ToString ( ) , 1000 , 0.1 * COIN ) ;
TRY_LOCK ( cs_main , lockMain ) ;
if ( ! lockMain | | ! AcceptToMemoryPool ( mempool , validationState , CTransaction ( tx ) , false , NULL , false , true , true ) ) {
LogPrintf ( " CPrivateSendClient::SendDenominate -- AcceptToMemoryPool() failed! tx=%s " , tx . ToString ( ) ) ;
UnlockCoins ( ) ;
SetNull ( ) ;
return false ;
}
}
// store our entry for later use
CDarkSendEntry entry ( vecTxIn , vecTxOut , txMyCollateral ) ;
vecEntries . push_back ( entry ) ;
RelayIn ( entry ) ;
nTimeLastSuccessfulStep = GetTimeMillis ( ) ;
return true ;
}
// Incoming message from Masternode updating the progress of mixing
bool CPrivateSendClient : : CheckPoolStateUpdate ( PoolState nStateNew , int nEntriesCountNew , PoolStatusUpdate nStatusUpdate , PoolMessage nMessageID , int nSessionIDNew )
{
if ( fMasterNode ) return false ;
// do not update state when mixing client state is one of these
if ( nState = = POOL_STATE_IDLE | | nState = = POOL_STATE_ERROR | | nState = = POOL_STATE_SUCCESS ) return false ;
2017-06-30 20:30:16 +02:00
strAutoDenomResult = _ ( " Masternode: " ) + " " + CPrivateSend : : GetMessageByID ( nMessageID ) ;
2017-05-05 13:26:27 +02:00
// if rejected at any state
if ( nStatusUpdate = = STATUS_REJECTED ) {
LogPrintf ( " CPrivateSendClient::CheckPoolStateUpdate -- entry is rejected by Masternode \n " ) ;
UnlockCoins ( ) ;
SetNull ( ) ;
SetState ( POOL_STATE_ERROR ) ;
2017-06-30 20:30:16 +02:00
strLastMessage = CPrivateSend : : GetMessageByID ( nMessageID ) ;
2017-05-05 13:26:27 +02:00
return true ;
}
if ( nStatusUpdate = = STATUS_ACCEPTED & & nState = = nStateNew ) {
if ( nStateNew = = POOL_STATE_QUEUE & & nSessionID = = 0 & & nSessionIDNew ! = 0 ) {
// new session id should be set only in POOL_STATE_QUEUE state
nSessionID = nSessionIDNew ;
nTimeLastSuccessfulStep = GetTimeMillis ( ) ;
LogPrintf ( " CPrivateSendClient::CheckPoolStateUpdate -- set nSessionID to %d \n " , nSessionID ) ;
return true ;
}
else if ( nStateNew = = POOL_STATE_ACCEPTING_ENTRIES & & nEntriesCount ! = nEntriesCountNew ) {
nEntriesCount = nEntriesCountNew ;
nTimeLastSuccessfulStep = GetTimeMillis ( ) ;
fLastEntryAccepted = true ;
LogPrintf ( " CPrivateSendClient::CheckPoolStateUpdate -- new entry accepted! \n " ) ;
return true ;
}
}
// only situations above are allowed, fail in any other case
return false ;
}
//
// After we receive the finalized transaction from the Masternode, we must
// check it to make sure it's what we want, then sign it if we agree.
// If we refuse to sign, it's possible we'll be charged collateral
//
bool CPrivateSendClient : : SignFinalTransaction ( const CTransaction & finalTransactionNew , CNode * pnode )
{
if ( fMasterNode | | pnode = = NULL ) return false ;
finalMutableTransaction = finalTransactionNew ;
LogPrintf ( " CPrivateSendClient::SignFinalTransaction -- finalMutableTransaction=%s " , finalMutableTransaction . ToString ( ) ) ;
std : : vector < CTxIn > sigs ;
//make sure my inputs/outputs are present, otherwise refuse to sign
BOOST_FOREACH ( const CDarkSendEntry entry , vecEntries ) {
BOOST_FOREACH ( const CTxDSIn txdsin , entry . vecTxDSIn ) {
/* Sign my transaction and all outputs */
int nMyInputIndex = - 1 ;
CScript prevPubKey = CScript ( ) ;
CTxIn txin = CTxIn ( ) ;
for ( unsigned int i = 0 ; i < finalMutableTransaction . vin . size ( ) ; i + + ) {
if ( finalMutableTransaction . vin [ i ] = = txdsin ) {
nMyInputIndex = i ;
prevPubKey = txdsin . prevPubKey ;
txin = txdsin ;
}
}
if ( nMyInputIndex > = 0 ) { //might have to do this one input at a time?
int nFoundOutputsCount = 0 ;
CAmount nValue1 = 0 ;
CAmount nValue2 = 0 ;
for ( unsigned int i = 0 ; i < finalMutableTransaction . vout . size ( ) ; i + + ) {
BOOST_FOREACH ( const CTxOut & txout , entry . vecTxDSOut ) {
if ( finalMutableTransaction . vout [ i ] = = txout ) {
nFoundOutputsCount + + ;
nValue1 + = finalMutableTransaction . vout [ i ] . nValue ;
}
}
}
BOOST_FOREACH ( const CTxOut txout , entry . vecTxDSOut )
nValue2 + = txout . nValue ;
int nTargetOuputsCount = entry . vecTxDSOut . size ( ) ;
if ( nFoundOutputsCount < nTargetOuputsCount | | nValue1 ! = nValue2 ) {
// in this case, something went wrong and we'll refuse to sign. It's possible we'll be charged collateral. But that's
// better then signing if the transaction doesn't look like what we wanted.
LogPrintf ( " CPrivateSendClient::SignFinalTransaction -- My entries are not correct! Refusing to sign: nFoundOutputsCount: %d, nTargetOuputsCount: %d \n " , nFoundOutputsCount , nTargetOuputsCount ) ;
UnlockCoins ( ) ;
SetNull ( ) ;
return false ;
}
const CKeyStore & keystore = * pwalletMain ;
LogPrint ( " privatesend " , " CPrivateSendClient::SignFinalTransaction -- Signing my input %i \n " , nMyInputIndex ) ;
if ( ! SignSignature ( keystore , prevPubKey , finalMutableTransaction , nMyInputIndex , int ( SIGHASH_ALL | SIGHASH_ANYONECANPAY ) ) ) { // changes scriptSig
LogPrint ( " privatesend " , " CPrivateSendClient::SignFinalTransaction -- Unable to sign my own transaction! \n " ) ;
// not sure what to do here, it will timeout...?
}
sigs . push_back ( finalMutableTransaction . vin [ nMyInputIndex ] ) ;
LogPrint ( " privatesend " , " CPrivateSendClient::SignFinalTransaction -- nMyInputIndex: %d, sigs.size(): %d, scriptSig=%s \n " , nMyInputIndex , ( int ) sigs . size ( ) , ScriptToAsmStr ( finalMutableTransaction . vin [ nMyInputIndex ] . scriptSig ) ) ;
}
}
}
if ( sigs . empty ( ) ) {
LogPrintf ( " CPrivateSendClient::SignFinalTransaction -- can't sign anything! \n " ) ;
UnlockCoins ( ) ;
SetNull ( ) ;
return false ;
}
// push all of our signatures to the Masternode
LogPrintf ( " CPrivateSendClient::SignFinalTransaction -- pushing sigs to the masternode, finalMutableTransaction=%s " , finalMutableTransaction . ToString ( ) ) ;
pnode - > PushMessage ( NetMsgType : : DSSIGNFINALTX , sigs ) ;
SetState ( POOL_STATE_SIGNING ) ;
nTimeLastSuccessfulStep = GetTimeMillis ( ) ;
return true ;
}
void CPrivateSendClient : : NewBlock ( )
{
static int64_t nTimeNewBlockReceived = 0 ;
//we we're processing lots of blocks, we'll just leave
if ( GetTime ( ) - nTimeNewBlockReceived < 10 ) return ;
nTimeNewBlockReceived = GetTime ( ) ;
LogPrint ( " privatesend " , " CPrivateSendClient::NewBlock \n " ) ;
CheckTimeout ( ) ;
}
// mixing transaction was completed (failed or successful)
void CPrivateSendClient : : CompletedTransaction ( PoolMessage nMessageID )
{
if ( fMasterNode ) return ;
if ( nMessageID = = MSG_SUCCESS ) {
LogPrintf ( " CompletedTransaction -- success \n " ) ;
nCachedLastSuccessBlock = pCurrentBlockIndex - > nHeight ;
} else {
LogPrintf ( " CompletedTransaction -- error \n " ) ;
}
UnlockCoins ( ) ;
SetNull ( ) ;
2017-06-30 20:30:16 +02:00
strLastMessage = CPrivateSend : : GetMessageByID ( nMessageID ) ;
2017-05-05 13:26:27 +02:00
}
bool CPrivateSendClient : : CheckAutomaticBackup ( )
{
switch ( nWalletBackups ) {
case 0 :
LogPrint ( " privatesend " , " CPrivateSendClient::CheckAutomaticBackup -- Automatic backups disabled, no mixing available. \n " ) ;
strAutoDenomResult = _ ( " Automatic backups disabled " ) + " , " + _ ( " no mixing available. " ) ;
fEnablePrivateSend = false ; // stop mixing
pwalletMain - > nKeysLeftSinceAutoBackup = 0 ; // no backup, no "keys since last backup"
return false ;
case - 1 :
// Automatic backup failed, nothing else we can do until user fixes the issue manually.
// There is no way to bring user attention in daemon mode so we just update status and
// keep spaming if debug is on.
LogPrint ( " privatesend " , " CPrivateSendClient::CheckAutomaticBackup -- ERROR! Failed to create automatic backup. \n " ) ;
strAutoDenomResult = _ ( " ERROR! Failed to create automatic backup " ) + " , " + _ ( " see debug.log for details. " ) ;
return false ;
case - 2 :
// We were able to create automatic backup but keypool was not replenished because wallet is locked.
// There is no way to bring user attention in daemon mode so we just update status and
// keep spaming if debug is on.
LogPrint ( " privatesend " , " CPrivateSendClient::CheckAutomaticBackup -- WARNING! Failed to create replenish keypool, please unlock your wallet to do so. \n " ) ;
strAutoDenomResult = _ ( " WARNING! Failed to replenish keypool, please unlock your wallet to do so. " ) + " , " + _ ( " see debug.log for details. " ) ;
return false ;
}
if ( pwalletMain - > nKeysLeftSinceAutoBackup < PRIVATESEND_KEYS_THRESHOLD_STOP ) {
// We should never get here via mixing itself but probably smth else is still actively using keypool
LogPrint ( " privatesend " , " CPrivateSendClient::CheckAutomaticBackup -- Very low number of keys left: %d, no mixing available. \n " , pwalletMain - > nKeysLeftSinceAutoBackup ) ;
strAutoDenomResult = strprintf ( _ ( " Very low number of keys left: %d " ) + " , " + _ ( " no mixing available. " ) , pwalletMain - > nKeysLeftSinceAutoBackup ) ;
// It's getting really dangerous, stop mixing
fEnablePrivateSend = false ;
return false ;
} else if ( pwalletMain - > nKeysLeftSinceAutoBackup < PRIVATESEND_KEYS_THRESHOLD_WARNING ) {
// Low number of keys left but it's still more or less safe to continue
LogPrint ( " privatesend " , " CPrivateSendClient::CheckAutomaticBackup -- Very low number of keys left: %d \n " , pwalletMain - > nKeysLeftSinceAutoBackup ) ;
strAutoDenomResult = strprintf ( _ ( " Very low number of keys left: %d " ) , pwalletMain - > nKeysLeftSinceAutoBackup ) ;
if ( fCreateAutoBackups ) {
LogPrint ( " privatesend " , " CPrivateSendClient::CheckAutomaticBackup -- Trying to create new backup. \n " ) ;
std : : string warningString ;
std : : string errorString ;
if ( ! AutoBackupWallet ( pwalletMain , " " , warningString , errorString ) ) {
if ( ! warningString . empty ( ) ) {
// There were some issues saving backup but yet more or less safe to continue
LogPrintf ( " CPrivateSendClient::CheckAutomaticBackup -- WARNING! Something went wrong on automatic backup: %s \n " , warningString ) ;
}
if ( ! errorString . empty ( ) ) {
// Things are really broken
LogPrintf ( " CPrivateSendClient::CheckAutomaticBackup -- ERROR! Failed to create automatic backup: %s \n " , errorString ) ;
strAutoDenomResult = strprintf ( _ ( " ERROR! Failed to create automatic backup " ) + " : %s " , errorString ) ;
return false ;
}
}
} else {
// Wait for smth else (e.g. GUI action) to create automatic backup for us
return false ;
}
}
LogPrint ( " privatesend " , " CPrivateSendClient::CheckAutomaticBackup -- Keys left since latest backup: %d \n " , pwalletMain - > nKeysLeftSinceAutoBackup ) ;
return true ;
}
//
// Passively run mixing in the background to anonymize funds based on the given configuration.
//
bool CPrivateSendClient : : DoAutomaticDenominating ( bool fDryRun )
{
if ( ! fEnablePrivateSend | | fMasterNode | | ! pCurrentBlockIndex ) return false ;
if ( ! pwalletMain | | pwalletMain - > IsLocked ( true ) ) return false ;
if ( nState ! = POOL_STATE_IDLE ) return false ;
if ( ! masternodeSync . IsMasternodeListSynced ( ) ) {
strAutoDenomResult = _ ( " Can't mix while sync in progress. " ) ;
return false ;
}
if ( ! CheckAutomaticBackup ( ) )
return false ;
if ( GetEntriesCount ( ) > 0 ) {
strAutoDenomResult = _ ( " Mixing in progress... " ) ;
return false ;
}
TRY_LOCK ( cs_darksend , lockDS ) ;
if ( ! lockDS ) {
strAutoDenomResult = _ ( " Lock is already in place. " ) ;
return false ;
}
if ( ! fDryRun & & pwalletMain - > IsLocked ( true ) ) {
strAutoDenomResult = _ ( " Wallet is locked. " ) ;
return false ;
}
if ( ! fPrivateSendMultiSession & & pCurrentBlockIndex - > nHeight - nCachedLastSuccessBlock < nMinBlockSpacing ) {
LogPrintf ( " CPrivateSendClient::DoAutomaticDenominating -- Last successful PrivateSend action was too recent \n " ) ;
strAutoDenomResult = _ ( " Last successful PrivateSend action was too recent. " ) ;
return false ;
}
if ( mnodeman . size ( ) = = 0 ) {
LogPrint ( " privatesend " , " CPrivateSendClient::DoAutomaticDenominating -- No Masternodes detected \n " ) ;
strAutoDenomResult = _ ( " No Masternodes detected. " ) ;
return false ;
}
2017-06-30 20:30:16 +02:00
CAmount nValueMin = CPrivateSend : : GetSmallestDenomination ( ) ;
2017-05-05 13:26:27 +02:00
// if there are no confirmed DS collateral inputs yet
if ( ! pwalletMain - > HasCollateralInputs ( ) ) {
// should have some additional amount for them
2017-06-30 20:30:16 +02:00
nValueMin + = CPrivateSend : : GetMaxCollateralAmount ( ) ;
2017-05-05 13:26:27 +02:00
}
// including denoms but applying some restrictions
CAmount nBalanceNeedsAnonymized = pwalletMain - > GetNeedsToBeAnonymizedBalance ( nValueMin ) ;
// anonymizable balance is way too small
if ( nBalanceNeedsAnonymized < nValueMin ) {
LogPrintf ( " CPrivateSendClient::DoAutomaticDenominating -- Not enough funds to anonymize \n " ) ;
strAutoDenomResult = _ ( " Not enough funds to anonymize. " ) ;
return false ;
}
// excluding denoms
CAmount nBalanceAnonimizableNonDenom = pwalletMain - > GetAnonymizableBalance ( true ) ;
// denoms
CAmount nBalanceDenominatedConf = pwalletMain - > GetDenominatedBalance ( ) ;
CAmount nBalanceDenominatedUnconf = pwalletMain - > GetDenominatedBalance ( true ) ;
CAmount nBalanceDenominated = nBalanceDenominatedConf + nBalanceDenominatedUnconf ;
LogPrint ( " privatesend " , " CPrivateSendClient::DoAutomaticDenominating -- nValueMin: %f, nBalanceNeedsAnonymized: %f, nBalanceAnonimizableNonDenom: %f, nBalanceDenominatedConf: %f, nBalanceDenominatedUnconf: %f, nBalanceDenominated: %f \n " ,
( float ) nValueMin / COIN ,
( float ) nBalanceNeedsAnonymized / COIN ,
( float ) nBalanceAnonimizableNonDenom / COIN ,
( float ) nBalanceDenominatedConf / COIN ,
( float ) nBalanceDenominatedUnconf / COIN ,
( float ) nBalanceDenominated / COIN ) ;
if ( fDryRun ) return true ;
// Check if we have should create more denominated inputs i.e.
// there are funds to denominate and denominated balance does not exceed
// max amount to mix yet.
2017-06-30 20:30:16 +02:00
if ( nBalanceAnonimizableNonDenom > = nValueMin + CPrivateSend : : GetCollateralAmount ( ) & & nBalanceDenominated < nPrivateSendAmount * COIN )
2017-05-05 13:26:27 +02:00
return CreateDenominated ( ) ;
//check if we have the collateral sized inputs
if ( ! pwalletMain - > HasCollateralInputs ( ) )
return ! pwalletMain - > HasCollateralInputs ( false ) & & MakeCollateralAmounts ( ) ;
if ( nSessionID ) {
strAutoDenomResult = _ ( " Mixing in progress... " ) ;
return false ;
}
// Initial phase, find a Masternode
// Clean if there is anything left from previous session
UnlockCoins ( ) ;
SetNull ( ) ;
// should be no unconfirmed denoms in non-multi-session mode
if ( ! fPrivateSendMultiSession & & nBalanceDenominatedUnconf > 0 ) {
LogPrintf ( " CPrivateSendClient::DoAutomaticDenominating -- Found unconfirmed denominated outputs, will wait till they confirm to continue. \n " ) ;
strAutoDenomResult = _ ( " Found unconfirmed denominated outputs, will wait till they confirm to continue. " ) ;
return false ;
}
//check our collateral and create new if needed
std : : string strReason ;
if ( txMyCollateral = = CMutableTransaction ( ) ) {
if ( ! pwalletMain - > CreateCollateralTransaction ( txMyCollateral , strReason ) ) {
LogPrintf ( " CPrivateSendClient::DoAutomaticDenominating -- create collateral error:%s \n " , strReason ) ;
return false ;
}
} else {
2017-06-30 20:30:16 +02:00
if ( ! CPrivateSend : : IsCollateralValid ( txMyCollateral ) ) {
2017-05-05 13:26:27 +02:00
LogPrintf ( " CPrivateSendClient::DoAutomaticDenominating -- invalid collateral, recreating... \n " ) ;
if ( ! pwalletMain - > CreateCollateralTransaction ( txMyCollateral , strReason ) ) {
LogPrintf ( " CPrivateSendClient::DoAutomaticDenominating -- create collateral error: %s \n " , strReason ) ;
return false ;
}
}
}
int nMnCountEnabled = mnodeman . CountEnabled ( MIN_PRIVATESEND_PEER_PROTO_VERSION ) ;
// If we've used 90% of the Masternode list then drop the oldest first ~30%
int nThreshold_high = nMnCountEnabled * 0.9 ;
int nThreshold_low = nThreshold_high * 0.7 ;
LogPrint ( " privatesend " , " Checking vecMasternodesUsed: size: %d, threshold: %d \n " , ( int ) vecMasternodesUsed . size ( ) , nThreshold_high ) ;
if ( ( int ) vecMasternodesUsed . size ( ) > nThreshold_high ) {
vecMasternodesUsed . erase ( vecMasternodesUsed . begin ( ) , vecMasternodesUsed . begin ( ) + vecMasternodesUsed . size ( ) - nThreshold_low ) ;
LogPrint ( " privatesend " , " vecMasternodesUsed: new size: %d, threshold: %d \n " , ( int ) vecMasternodesUsed . size ( ) , nThreshold_high ) ;
}
bool fUseQueue = GetRandInt ( 100 ) > 33 ;
// don't use the queues all of the time for mixing unless we are a liquidity provider
if ( ( nLiquidityProvider | | fUseQueue ) & & JoinExistingQueue ( nBalanceNeedsAnonymized ) )
return true ;
// do not initiate queue if we are a liquidity provider to avoid useless inter-mixing
if ( nLiquidityProvider ) return false ;
if ( StartNewQueue ( nValueMin , nBalanceNeedsAnonymized ) )
return true ;
strAutoDenomResult = _ ( " No compatible Masternode found. " ) ;
return false ;
}
bool CPrivateSendClient : : JoinExistingQueue ( CAmount nBalanceNeedsAnonymized )
{
2017-06-30 20:30:16 +02:00
std : : vector < CAmount > vecStandardDenoms = CPrivateSend : : GetStandardDenominations ( ) ;
2017-05-05 13:26:27 +02:00
// Look through the queues and see if anything matches
BOOST_FOREACH ( CDarksendQueue & dsq , vecDarksendQueue ) {
// only try each queue once
if ( dsq . fTried ) continue ;
dsq . fTried = true ;
if ( dsq . IsExpired ( ) ) continue ;
masternode_info_t infoMn = mnodeman . GetMasternodeInfo ( dsq . vin ) ;
if ( ! infoMn . fInfoValid ) {
LogPrintf ( " CPrivateSendClient::JoinExistingQueue -- dsq masternode is not in masternode list, masternode=%s \n " , dsq . vin . prevout . ToStringShort ( ) ) ;
continue ;
}
if ( infoMn . nProtocolVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION ) continue ;
std : : vector < int > vecBits ;
2017-06-30 20:30:16 +02:00
if ( ! CPrivateSend : : GetDenominationsBits ( dsq . nDenom , vecBits ) ) {
2017-05-05 13:26:27 +02:00
// incompatible denom
continue ;
}
// mixing rate limit i.e. nLastDsq check should already pass in DSQUEUE ProcessMessage
// in order for dsq to get into vecDarksendQueue, so we should be safe to mix already,
// no need for additional verification here
LogPrint ( " privatesend " , " CPrivateSendClient::JoinExistingQueue -- found valid queue: %s \n " , dsq . ToString ( ) ) ;
CAmount nValueInTmp = 0 ;
std : : vector < CTxIn > vecTxInTmp ;
std : : vector < COutput > vCoinsTmp ;
// Try to match their denominations if possible, select at least 1 denominations
2017-06-30 20:30:16 +02:00
if ( ! pwalletMain - > SelectCoinsByDenominations ( dsq . nDenom , vecStandardDenoms [ vecBits . front ( ) ] , nBalanceNeedsAnonymized , vecTxInTmp , vCoinsTmp , nValueInTmp , 0 , nPrivateSendRounds ) ) {
LogPrintf ( " CPrivateSendClient::JoinExistingQueue -- Couldn't match denominations %d %d (%s) \n " , vecBits . front ( ) , dsq . nDenom , CPrivateSend : : GetDenominationsToString ( dsq . nDenom ) ) ;
2017-05-05 13:26:27 +02:00
continue ;
}
vecMasternodesUsed . push_back ( dsq . vin ) ;
CNode * pnodeFound = NULL ;
{
LOCK ( cs_vNodes ) ;
pnodeFound = FindNode ( infoMn . addr ) ;
if ( pnodeFound ) {
if ( pnodeFound - > fDisconnect ) {
continue ;
} else {
pnodeFound - > AddRef ( ) ;
}
}
}
LogPrintf ( " CPrivateSendClient::JoinExistingQueue -- attempt to connect to masternode from queue, addr=%s \n " , infoMn . addr . ToString ( ) ) ;
// connect to Masternode and submit the queue request
2017-07-05 05:45:23 +02:00
CNode * pnode = ( pnodeFound & & pnodeFound - > fMasternode ) ? pnodeFound : ConnectNode ( CAddress ( infoMn . addr , NODE_NETWORK ) , NULL , true ) ;
2017-05-05 13:26:27 +02:00
if ( pnode ) {
infoMixingMasternode = infoMn ;
nSessionDenom = dsq . nDenom ;
pnode - > PushMessage ( NetMsgType : : DSACCEPT , nSessionDenom , txMyCollateral ) ;
LogPrintf ( " CPrivateSendClient::JoinExistingQueue -- connected (from queue), sending DSACCEPT: nSessionDenom: %d (%s), addr=%s \n " ,
2017-06-30 20:30:16 +02:00
nSessionDenom , CPrivateSend : : GetDenominationsToString ( nSessionDenom ) , pnode - > addr . ToString ( ) ) ;
2017-05-05 13:26:27 +02:00
strAutoDenomResult = _ ( " Mixing in progress... " ) ;
SetState ( POOL_STATE_QUEUE ) ;
nTimeLastSuccessfulStep = GetTimeMillis ( ) ;
if ( pnodeFound ) {
pnodeFound - > Release ( ) ;
}
return true ;
} else {
LogPrintf ( " CPrivateSendClient::JoinExistingQueue -- can't connect, addr=%s \n " , infoMn . addr . ToString ( ) ) ;
strAutoDenomResult = _ ( " Error connecting to Masternode. " ) ;
continue ;
}
}
return false ;
}
bool CPrivateSendClient : : StartNewQueue ( CAmount nValueMin , CAmount nBalanceNeedsAnonymized )
{
int nTries = 0 ;
int nMnCountEnabled = mnodeman . CountEnabled ( MIN_PRIVATESEND_PEER_PROTO_VERSION ) ;
// ** find the coins we'll use
std : : vector < CTxIn > vecTxIn ;
CAmount nValueInTmp = 0 ;
if ( ! pwalletMain - > SelectCoinsDark ( nValueMin , nBalanceNeedsAnonymized , vecTxIn , nValueInTmp , 0 , nPrivateSendRounds ) ) {
// this should never happen
LogPrintf ( " CPrivateSendClient::StartNewQueue -- Can't mix: no compatible inputs found! \n " ) ;
strAutoDenomResult = _ ( " Can't mix: no compatible inputs found! " ) ;
return false ;
}
// otherwise, try one randomly
while ( nTries < 10 ) {
masternode_info_t infoMn = mnodeman . FindRandomNotInVec ( vecMasternodesUsed , MIN_PRIVATESEND_PEER_PROTO_VERSION ) ;
if ( ! infoMn . fInfoValid ) {
LogPrintf ( " CPrivateSendClient::StartNewQueue -- Can't find random masternode! \n " ) ;
strAutoDenomResult = _ ( " Can't find random Masternode. " ) ;
return false ;
}
vecMasternodesUsed . push_back ( infoMn . vin ) ;
if ( infoMn . nLastDsq ! = 0 & & infoMn . nLastDsq + nMnCountEnabled / 5 > mnodeman . nDsqCount ) {
LogPrintf ( " CPrivateSendClient::StartNewQueue -- Too early to mix on this masternode! "
" masternode=%s addr=%s nLastDsq=%d CountEnabled/5=%d nDsqCount=%d \n " ,
infoMn . vin . prevout . ToStringShort ( ) , infoMn . addr . ToString ( ) , infoMn . nLastDsq ,
nMnCountEnabled / 5 , mnodeman . nDsqCount ) ;
nTries + + ;
continue ;
}
CNode * pnodeFound = NULL ;
{
LOCK ( cs_vNodes ) ;
pnodeFound = FindNode ( infoMn . addr ) ;
if ( pnodeFound ) {
if ( pnodeFound - > fDisconnect ) {
nTries + + ;
continue ;
} else {
pnodeFound - > AddRef ( ) ;
}
}
}
LogPrintf ( " CPrivateSendClient::StartNewQueue -- attempt %d connection to Masternode %s \n " , nTries , infoMn . addr . ToString ( ) ) ;
2017-07-05 05:45:23 +02:00
CNode * pnode = ( pnodeFound & & pnodeFound - > fMasternode ) ? pnodeFound : ConnectNode ( CAddress ( infoMn . addr , NODE_NETWORK ) , NULL , true ) ;
2017-05-05 13:26:27 +02:00
if ( pnode ) {
LogPrintf ( " CPrivateSendClient::StartNewQueue -- connected, addr=%s \n " , infoMn . addr . ToString ( ) ) ;
infoMixingMasternode = infoMn ;
std : : vector < CAmount > vecAmounts ;
pwalletMain - > ConvertList ( vecTxIn , vecAmounts ) ;
// try to get a single random denom out of vecAmounts
while ( nSessionDenom = = 0 ) {
2017-06-30 20:30:16 +02:00
nSessionDenom = CPrivateSend : : GetDenominationsByAmounts ( vecAmounts ) ;
2017-05-05 13:26:27 +02:00
}
pnode - > PushMessage ( NetMsgType : : DSACCEPT , nSessionDenom , txMyCollateral ) ;
LogPrintf ( " CPrivateSendClient::StartNewQueue -- connected, sending DSACCEPT, nSessionDenom: %d (%s) \n " ,
2017-06-30 20:30:16 +02:00
nSessionDenom , CPrivateSend : : GetDenominationsToString ( nSessionDenom ) ) ;
2017-05-05 13:26:27 +02:00
strAutoDenomResult = _ ( " Mixing in progress... " ) ;
SetState ( POOL_STATE_QUEUE ) ;
nTimeLastSuccessfulStep = GetTimeMillis ( ) ;
if ( pnodeFound ) {
pnodeFound - > Release ( ) ;
}
return true ;
} else {
LogPrintf ( " CPrivateSendClient::StartNewQueue -- can't connect, addr=%s \n " , infoMn . addr . ToString ( ) ) ;
nTries + + ;
continue ;
}
}
return false ;
}
bool CPrivateSendClient : : SubmitDenominate ( )
{
std : : string strError ;
std : : vector < CTxIn > vecTxInRet ;
std : : vector < CTxOut > vecTxOutRet ;
// Submit transaction to the pool if we get here
// Try to use only inputs with the same number of rounds starting from the highest number of rounds possible
for ( int i = nPrivateSendRounds ; i > 0 ; i - - ) {
if ( PrepareDenominate ( i - 1 , i , strError , vecTxInRet , vecTxOutRet ) ) {
LogPrintf ( " CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success \n " , i ) ;
return SendDenominate ( vecTxInRet , vecTxOutRet ) ;
}
LogPrint ( " privatesend " , " CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for %d rounds, error: %s \n " , i , strError ) ;
}
// We failed? That's strange but let's just make final attempt and try to mix everything
if ( PrepareDenominate ( 0 , nPrivateSendRounds , strError , vecTxInRet , vecTxOutRet ) ) {
LogPrintf ( " CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for all rounds, success \n " ) ;
return SendDenominate ( vecTxInRet , vecTxOutRet ) ;
}
// Should never actually get here but just in case
LogPrintf ( " CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for all rounds, error: %s \n " , strError ) ;
strAutoDenomResult = strError ;
return false ;
}
bool CPrivateSendClient : : PrepareDenominate ( int nMinRounds , int nMaxRounds , std : : string & strErrorRet , std : : vector < CTxIn > & vecTxInRet , std : : vector < CTxOut > & vecTxOutRet )
{
if ( ! pwalletMain ) {
strErrorRet = " Wallet is not initialized " ;
return false ;
}
if ( pwalletMain - > IsLocked ( true ) ) {
strErrorRet = " Wallet locked, unable to create transaction! " ;
return false ;
}
if ( GetEntriesCount ( ) > 0 ) {
strErrorRet = " Already have pending entries in the PrivateSend pool " ;
return false ;
}
// make sure returning vectors are empty before filling them up
vecTxInRet . clear ( ) ;
vecTxOutRet . clear ( ) ;
// ** find the coins we'll use
std : : vector < CTxIn > vecTxIn ;
std : : vector < COutput > vCoins ;
CAmount nValueIn = 0 ;
CReserveKey reservekey ( pwalletMain ) ;
/*
Select the coins we ' ll use
if nMinRounds > = 0 it means only denominated inputs are going in and coming out
*/
std : : vector < int > vecBits ;
2017-06-30 20:30:16 +02:00
if ( ! CPrivateSend : : GetDenominationsBits ( nSessionDenom , vecBits ) ) {
2017-05-05 13:26:27 +02:00
strErrorRet = " Incorrect session denom " ;
return false ;
}
2017-06-30 20:30:16 +02:00
std : : vector < CAmount > vecStandardDenoms = CPrivateSend : : GetStandardDenominations ( ) ;
bool fSelected = pwalletMain - > SelectCoinsByDenominations ( nSessionDenom , vecStandardDenoms [ vecBits . front ( ) ] , CPrivateSend : : GetMaxPoolAmount ( ) , vecTxIn , vCoins , nValueIn , nMinRounds , nMaxRounds ) ;
2017-05-05 13:26:27 +02:00
if ( nMinRounds > = 0 & & ! fSelected ) {
strErrorRet = " Can't select current denominated inputs " ;
return false ;
}
LogPrintf ( " CPrivateSendClient::PrepareDenominate -- max value: %f \n " , ( double ) nValueIn / COIN ) ;
{
LOCK ( pwalletMain - > cs_wallet ) ;
BOOST_FOREACH ( CTxIn txin , vecTxIn ) {
pwalletMain - > LockCoin ( txin . prevout ) ;
}
}
CAmount nValueLeft = nValueIn ;
// Try to add every needed denomination, repeat up to 5-PRIVATESEND_ENTRY_MAX_SIZE times.
// NOTE: No need to randomize order of inputs because they were
// initially shuffled in CWallet::SelectCoinsByDenominations already.
int nStep = 0 ;
int nStepsMax = 5 + GetRandInt ( PRIVATESEND_ENTRY_MAX_SIZE - 5 + 1 ) ;
while ( nStep < nStepsMax ) {
BOOST_FOREACH ( int nBit , vecBits ) {
2017-06-30 20:30:16 +02:00
CAmount nValueDenom = vecStandardDenoms [ nBit ] ;
2017-05-05 13:26:27 +02:00
if ( nValueLeft - nValueDenom < 0 ) continue ;
// Note: this relies on a fact that both vectors MUST have same size
std : : vector < CTxIn > : : iterator it = vecTxIn . begin ( ) ;
std : : vector < COutput > : : iterator it2 = vCoins . begin ( ) ;
while ( it2 ! = vCoins . end ( ) ) {
// we have matching inputs
if ( ( * it2 ) . tx - > vout [ ( * it2 ) . i ] . nValue = = nValueDenom ) {
// add new input in resulting vector
vecTxInRet . push_back ( * it ) ;
// remove corresponting items from initial vectors
vecTxIn . erase ( it ) ;
vCoins . erase ( it2 ) ;
2017-05-29 13:51:40 +02:00
CScript scriptDenom ;
2017-05-05 13:26:27 +02:00
CPubKey vchPubKey ;
2017-05-29 13:51:40 +02:00
// use unique address
assert ( reservekey . GetReservedKey ( vchPubKey , false ) ) ; // should never fail, as we just unlocked
scriptDenom = GetScriptForDestination ( vchPubKey . GetID ( ) ) ;
2017-05-05 13:26:27 +02:00
reservekey . KeepKey ( ) ;
// add new output
2017-05-29 13:51:40 +02:00
CTxOut txout ( nValueDenom , scriptDenom ) ;
2017-05-05 13:26:27 +02:00
vecTxOutRet . push_back ( txout ) ;
// subtract denomination amount
nValueLeft - = nValueDenom ;
// step is complete
break ;
}
+ + it ;
+ + it2 ;
}
}
if ( nValueLeft = = 0 ) break ;
nStep + + ;
}
{
// unlock unused coins
LOCK ( pwalletMain - > cs_wallet ) ;
BOOST_FOREACH ( CTxIn txin , vecTxIn ) {
pwalletMain - > UnlockCoin ( txin . prevout ) ;
}
}
2017-06-30 20:30:16 +02:00
if ( CPrivateSend : : GetDenominations ( vecTxOutRet ) ! = nSessionDenom ) {
2017-05-05 13:26:27 +02:00
// unlock used coins on failure
LOCK ( pwalletMain - > cs_wallet ) ;
BOOST_FOREACH ( CTxIn txin , vecTxInRet ) {
pwalletMain - > UnlockCoin ( txin . prevout ) ;
}
strErrorRet = " Can't make current denominated outputs " ;
return false ;
}
// We also do not care about full amount as long as we have right denominations
return true ;
}
// Create collaterals by looping through inputs grouped by addresses
bool CPrivateSendClient : : MakeCollateralAmounts ( )
{
std : : vector < CompactTallyItem > vecTally ;
if ( ! pwalletMain - > SelectCoinsGrouppedByAddresses ( vecTally , false ) ) {
LogPrint ( " privatesend " , " CPrivateSendClient::MakeCollateralAmounts -- SelectCoinsGrouppedByAddresses can't find any inputs! \n " ) ;
return false ;
}
2017-07-10 16:42:32 +02:00
// First try to use only non-denominated funds
2017-05-05 13:26:27 +02:00
BOOST_FOREACH ( CompactTallyItem & item , vecTally ) {
2017-07-10 16:42:32 +02:00
if ( ! MakeCollateralAmounts ( item , false ) ) continue ;
2017-05-05 13:26:27 +02:00
return true ;
}
2017-07-10 16:42:32 +02:00
// There should be at least some denominated funds we should be able to break in pieces to continue mixing
BOOST_FOREACH ( CompactTallyItem & item , vecTally ) {
if ( ! MakeCollateralAmounts ( item , true ) ) continue ;
return true ;
}
// If we got here then smth is terribly broken actually
LogPrintf ( " CPrivateSendClient::MakeCollateralAmounts -- ERROR: Can't make collaterals! \n " ) ;
2017-05-05 13:26:27 +02:00
return false ;
}
// Split up large inputs or create fee sized inputs
2017-07-10 16:42:32 +02:00
bool CPrivateSendClient : : MakeCollateralAmounts ( const CompactTallyItem & tallyItem , bool fTryDenominated )
2017-05-05 13:26:27 +02:00
{
2017-07-10 16:42:32 +02:00
LOCK2 ( cs_main , pwalletMain - > cs_wallet ) ;
2017-05-05 13:26:27 +02:00
CWalletTx wtx ;
CAmount nFeeRet = 0 ;
int nChangePosRet = - 1 ;
std : : string strFail = " " ;
std : : vector < CRecipient > vecSend ;
// make our collateral address
CReserveKey reservekeyCollateral ( pwalletMain ) ;
// make our change address
CReserveKey reservekeyChange ( pwalletMain ) ;
CScript scriptCollateral ;
CPubKey vchPubKey ;
2017-05-29 13:51:40 +02:00
assert ( reservekeyCollateral . GetReservedKey ( vchPubKey , false ) ) ; // should never fail, as we just unlocked
2017-05-05 13:26:27 +02:00
scriptCollateral = GetScriptForDestination ( vchPubKey . GetID ( ) ) ;
2017-06-30 20:30:16 +02:00
vecSend . push_back ( ( CRecipient ) { scriptCollateral , CPrivateSend : : GetMaxCollateralAmount ( ) , false } ) ;
2017-05-05 13:26:27 +02:00
// try to use non-denominated and not mn-like funds first, select them explicitly
CCoinControl coinControl ;
coinControl . fAllowOtherInputs = false ;
coinControl . fAllowWatchOnly = false ;
// send change to the same address so that we were able create more denoms out of it later
coinControl . destChange = tallyItem . address . Get ( ) ;
BOOST_FOREACH ( const CTxIn & txin , tallyItem . vecTxIn )
coinControl . Select ( txin . prevout ) ;
bool fSuccess = pwalletMain - > CreateTransaction ( vecSend , wtx , reservekeyChange ,
nFeeRet , nChangePosRet , strFail , & coinControl , true , ONLY_NONDENOMINATED_NOT1000IFMN ) ;
if ( ! fSuccess ) {
LogPrintf ( " CPrivateSendClient::MakeCollateralAmounts -- ONLY_NONDENOMINATED_NOT1000IFMN Error: %s \n " , strFail ) ;
2017-07-10 16:42:32 +02:00
// If we failed then most likeky there are not enough funds on this address.
if ( fTryDenominated ) {
// Try to also use denominated coins (we can't mix denominated without collaterals anyway).
// MN-like funds should not be touched in any case.
if ( ! pwalletMain - > CreateTransaction ( vecSend , wtx , reservekeyChange ,
nFeeRet , nChangePosRet , strFail , & coinControl , true , ONLY_NOT1000IFMN ) ) {
LogPrintf ( " CPrivateSendClient::MakeCollateralAmounts -- ONLY_NOT1000IFMN Error: %s \n " , strFail ) ;
reservekeyCollateral . ReturnKey ( ) ;
return false ;
}
} else {
// Nothing else we can do.
2017-05-05 13:26:27 +02:00
reservekeyCollateral . ReturnKey ( ) ;
return false ;
}
}
reservekeyCollateral . KeepKey ( ) ;
LogPrintf ( " CPrivateSendClient::MakeCollateralAmounts -- txid=%s \n " , wtx . GetHash ( ) . GetHex ( ) ) ;
// use the same nCachedLastSuccessBlock as for DS mixinx to prevent race
if ( ! pwalletMain - > CommitTransaction ( wtx , reservekeyChange ) ) {
LogPrintf ( " CPrivateSendClient::MakeCollateralAmounts -- CommitTransaction failed! \n " ) ;
return false ;
}
nCachedLastSuccessBlock = pCurrentBlockIndex - > nHeight ;
return true ;
}
// Create denominations by looping through inputs grouped by addresses
bool CPrivateSendClient : : CreateDenominated ( )
{
2017-05-28 15:50:07 +02:00
LOCK2 ( cs_main , pwalletMain - > cs_wallet ) ;
2017-05-05 13:26:27 +02:00
std : : vector < CompactTallyItem > vecTally ;
if ( ! pwalletMain - > SelectCoinsGrouppedByAddresses ( vecTally ) ) {
LogPrint ( " privatesend " , " CPrivateSendClient::CreateDenominated -- SelectCoinsGrouppedByAddresses can't find any inputs! \n " ) ;
return false ;
}
bool fCreateMixingCollaterals = ! pwalletMain - > HasCollateralInputs ( ) ;
BOOST_FOREACH ( CompactTallyItem & item , vecTally ) {
if ( ! CreateDenominated ( item , fCreateMixingCollaterals ) ) continue ;
return true ;
}
LogPrintf ( " CPrivateSendClient::CreateDenominated -- failed! \n " ) ;
return false ;
}
// Create denominations
bool CPrivateSendClient : : CreateDenominated ( const CompactTallyItem & tallyItem , bool fCreateMixingCollaterals )
{
std : : vector < CRecipient > vecSend ;
CAmount nValueLeft = tallyItem . nAmount ;
2017-06-30 20:30:16 +02:00
nValueLeft - = CPrivateSend : : GetCollateralAmount ( ) ; // leave some room for fees
2017-05-05 13:26:27 +02:00
LogPrintf ( " CreateDenominated0 nValueLeft: %f \n " , ( float ) nValueLeft / COIN ) ;
// make our collateral address
CReserveKey reservekeyCollateral ( pwalletMain ) ;
CScript scriptCollateral ;
CPubKey vchPubKey ;
2017-05-29 13:51:40 +02:00
assert ( reservekeyCollateral . GetReservedKey ( vchPubKey , false ) ) ; // should never fail, as we just unlocked
2017-05-05 13:26:27 +02:00
scriptCollateral = GetScriptForDestination ( vchPubKey . GetID ( ) ) ;
// ****** Add collateral outputs ************ /
if ( fCreateMixingCollaterals ) {
2017-06-30 20:30:16 +02:00
vecSend . push_back ( ( CRecipient ) { scriptCollateral , CPrivateSend : : GetMaxCollateralAmount ( ) , false } ) ;
nValueLeft - = CPrivateSend : : GetMaxCollateralAmount ( ) ;
2017-05-05 13:26:27 +02:00
}
// ****** Add denoms ************ /
// make our denom addresses
2017-05-28 15:50:07 +02:00
std : : vector < std : : shared_ptr < CReserveKey > > reservekeyDenomVec ;
2017-05-05 13:26:27 +02:00
// try few times - skipping smallest denoms first if there are too much already, if failed - use them
int nOutputsTotal = 0 ;
bool fSkip = true ;
do {
2017-06-30 20:30:16 +02:00
std : : vector < CAmount > vecStandardDenoms = CPrivateSend : : GetStandardDenominations ( ) ;
2017-05-05 13:26:27 +02:00
2017-06-30 20:30:16 +02:00
BOOST_REVERSE_FOREACH ( CAmount nDenomValue , vecStandardDenoms ) {
2017-05-05 13:26:27 +02:00
if ( fSkip ) {
// Note: denoms are skipped if there are already DENOMS_COUNT_MAX of them
// and there are still larger denoms which can be used for mixing
// check skipped denoms
if ( IsDenomSkipped ( nDenomValue ) ) continue ;
// find new denoms to skip if any (ignore the largest one)
2017-06-30 20:30:16 +02:00
if ( nDenomValue ! = vecStandardDenoms . front ( ) & & pwalletMain - > CountInputsWithAmount ( nDenomValue ) > DENOMS_COUNT_MAX ) {
2017-05-05 13:26:27 +02:00
strAutoDenomResult = strprintf ( _ ( " Too many %f denominations, removing. " ) , ( float ) nDenomValue / COIN ) ;
LogPrintf ( " CPrivateSendClient::CreateDenominated -- %s \n " , strAutoDenomResult ) ;
vecDenominationsSkipped . push_back ( nDenomValue ) ;
continue ;
}
}
int nOutputs = 0 ;
2017-05-28 15:50:07 +02:00
// add each output up to 11 times until it can't be added again
2017-05-05 13:26:27 +02:00
while ( nValueLeft - nDenomValue > = 0 & & nOutputs < = 10 ) {
CScript scriptDenom ;
CPubKey vchPubKey ;
2017-05-29 13:51:40 +02:00
// use a unique address
2017-05-28 15:50:07 +02:00
std : : shared_ptr < CReserveKey > reservekeyDenom = std : : make_shared < CReserveKey > ( pwalletMain ) ;
reservekeyDenomVec . push_back ( reservekeyDenom ) ;
2017-05-29 13:51:40 +02:00
assert ( reservekeyDenom - > GetReservedKey ( vchPubKey , false ) ) ; // should never fail, as we just unlocked
2017-05-05 13:26:27 +02:00
scriptDenom = GetScriptForDestination ( vchPubKey . GetID ( ) ) ;
vecSend . push_back ( ( CRecipient ) { scriptDenom , nDenomValue , false } ) ;
//increment outputs and subtract denomination amount
nOutputs + + ;
nValueLeft - = nDenomValue ;
2017-05-28 15:50:07 +02:00
LogPrintf ( " CreateDenominated1: totalOutputs: %d, nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f \n " , nOutputsTotal + nOutputs , nOutputsTotal , nOutputs , ( float ) nValueLeft / COIN ) ;
2017-05-05 13:26:27 +02:00
}
nOutputsTotal + = nOutputs ;
if ( nValueLeft = = 0 ) break ;
}
LogPrintf ( " CreateDenominated2: nOutputsTotal: %d, nValueLeft: %f \n " , nOutputsTotal , ( float ) nValueLeft / COIN ) ;
// if there were no outputs added, start over without skipping
fSkip = ! fSkip ;
} while ( nOutputsTotal = = 0 & & ! fSkip ) ;
LogPrintf ( " CreateDenominated3: nOutputsTotal: %d, nValueLeft: %f \n " , nOutputsTotal , ( float ) nValueLeft / COIN ) ;
// if we have anything left over, it will be automatically send back as change - there is no need to send it manually
CCoinControl coinControl ;
coinControl . fAllowOtherInputs = false ;
coinControl . fAllowWatchOnly = false ;
// send change to the same address so that we were able create more denoms out of it later
coinControl . destChange = tallyItem . address . Get ( ) ;
BOOST_FOREACH ( const CTxIn & txin , tallyItem . vecTxIn )
coinControl . Select ( txin . prevout ) ;
CWalletTx wtx ;
CAmount nFeeRet = 0 ;
int nChangePosRet = - 1 ;
std : : string strFail = " " ;
// make our change address
CReserveKey reservekeyChange ( pwalletMain ) ;
bool fSuccess = pwalletMain - > CreateTransaction ( vecSend , wtx , reservekeyChange ,
nFeeRet , nChangePosRet , strFail , & coinControl , true , ONLY_NONDENOMINATED_NOT1000IFMN ) ;
if ( ! fSuccess ) {
LogPrintf ( " CPrivateSendClient::CreateDenominated -- Error: %s \n " , strFail ) ;
2017-05-28 15:50:07 +02:00
for ( auto key : reservekeyDenomVec )
key - > ReturnKey ( ) ;
2017-05-05 13:26:27 +02:00
reservekeyCollateral . ReturnKey ( ) ;
2017-05-28 15:50:07 +02:00
LogPrintf ( " CPrivateSendClient::CreateDenominated -- %d keys returned \n " , reservekeyDenomVec . size ( ) + 1 ) ;
2017-05-05 13:26:27 +02:00
return false ;
}
2017-05-28 15:50:07 +02:00
for ( auto key : reservekeyDenomVec )
key - > KeepKey ( ) ;
2017-05-05 13:26:27 +02:00
reservekeyCollateral . KeepKey ( ) ;
2017-05-28 15:50:07 +02:00
LogPrintf ( " CPrivateSendClient::CreateDenominated -- %d keys keeped \n " , reservekeyDenomVec . size ( ) + 1 ) ;
2017-05-05 13:26:27 +02:00
if ( ! pwalletMain - > CommitTransaction ( wtx , reservekeyChange ) ) {
LogPrintf ( " CPrivateSendClient::CreateDenominated -- CommitTransaction failed! \n " ) ;
return false ;
}
// use the same nCachedLastSuccessBlock as for DS mixing to prevent race
nCachedLastSuccessBlock = pCurrentBlockIndex - > nHeight ;
LogPrintf ( " CPrivateSendClient::CreateDenominated -- txid=%s \n " , wtx . GetHash ( ) . GetHex ( ) ) ;
return true ;
}
void CPrivateSendClient : : RelayIn ( const CDarkSendEntry & entry )
{
if ( ! infoMixingMasternode . fInfoValid ) return ;
LOCK ( cs_vNodes ) ;
CNode * pnode = FindNode ( infoMixingMasternode . addr ) ;
if ( pnode ! = NULL ) {
LogPrintf ( " CPrivateSendClient::RelayIn -- found master, relaying message to %s \n " , pnode - > addr . ToString ( ) ) ;
pnode - > PushMessage ( NetMsgType : : DSVIN , entry ) ;
}
}
void CPrivateSendClient : : SetState ( PoolState nStateNew )
{
LogPrintf ( " CPrivateSendClient::SetState -- nState: %d, nStateNew: %d \n " , nState , nStateNew ) ;
nState = nStateNew ;
}
void CPrivateSendClient : : UpdatedBlockTip ( const CBlockIndex * pindex )
{
pCurrentBlockIndex = pindex ;
LogPrint ( " privatesend " , " CPrivateSendClient::UpdatedBlockTip -- pCurrentBlockIndex->nHeight: %d \n " , pCurrentBlockIndex - > nHeight ) ;
if ( ! fLiteMode & & masternodeSync . IsMasternodeListSynced ( ) ) {
NewBlock ( ) ;
}
2017-07-10 16:42:09 +02:00
CPrivateSend : : CheckDSTXes ( pindex - > nHeight ) ;
2017-05-05 13:26:27 +02:00
}
//TODO: Rename/move to core
void ThreadCheckPrivateSendClient ( )
{
if ( fLiteMode ) return ; // disable all Dash specific functionality
static bool fOneThread ;
if ( fOneThread ) return ;
fOneThread = true ;
// Make this thread recognisable as the PrivateSend thread
RenameThread ( " dash-ps-client " ) ;
unsigned int nTick = 0 ;
unsigned int nDoAutoNextRun = nTick + PRIVATESEND_AUTO_TIMEOUT_MIN ;
while ( true )
{
MilliSleep ( 1000 ) ;
if ( masternodeSync . IsBlockchainSynced ( ) & & ! ShutdownRequested ( ) ) {
nTick + + ;
privateSendClient . CheckTimeout ( ) ;
if ( nDoAutoNextRun = = nTick ) {
privateSendClient . DoAutomaticDenominating ( ) ;
nDoAutoNextRun = nTick + PRIVATESEND_AUTO_TIMEOUT_MIN + GetRandInt ( PRIVATESEND_AUTO_TIMEOUT_MAX - PRIVATESEND_AUTO_TIMEOUT_MIN ) ;
}
}
}
}