Added Darksend high precision matching engine

Darksend is now capable of taking queue objects (which show who wants to mix what)
and looking at it's own inputs to see if it's at all possible to join their mixing
session. This plus other improvements should make Darksend much faster for mixing
coins.
This commit is contained in:
Evan Duffield 2014-12-29 17:09:34 -07:00
parent 762080140b
commit b20581125d
5 changed files with 134 additions and 80 deletions

View File

@ -163,8 +163,7 @@ void ProcessMessageDarksend(CNode* pfrom, std::string& strCommand, CDataStream&
}
if (fDebug) LogPrintf("darksend queue is ready - %s\n", addr.ToString().c_str());
darkSendPool.DoAutomaticDenominating(false, true);
darkSendPool.PrepareDarksendDenominate();
} else {
BOOST_FOREACH(CDarksendQueue q, vecDarksendQueue){
if(q.vin == dsq.vin) return;
@ -1426,22 +1425,23 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
return false;
}
sessionMinRounds = -2; //non denominated funds are rounds of less than 0
sessionMaxRounds = 2;
// ** find the coins we'll use
std::vector<CTxIn> vCoins;
int64_t nValueMin = 0.01*COIN;
int64_t nValueMax = DARKSEND_POOL_MAX;
int64_t nValueIn = 0;
int minRounds = -2; //non denominated funds are rounds of less than 0
int maxRounds = 2;
int maxAmount = DARKSEND_POOL_MAX/COIN;
bool hasFeeInput = false;
int64_t lowestDenom = COIN*0.1;
// If we can find only denominated funds, switch to only-denom mode
if (!pwalletMain->SelectCoinsDark(nValueMin, maxAmount*COIN, vCoins, nValueIn, -2, 2, hasFeeInput) &&
pwalletMain->SelectCoinsDark(nValueMin, maxAmount*COIN, vCoins, nValueIn, 0, 8, hasFeeInput)) {
minRounds = 0;
maxRounds = nDarksendRounds;
pwalletMain->SelectCoinsDark(nValueMin, maxAmount*COIN, vCoins, nValueIn, 0, nDarksendRounds, hasFeeInput)) {
sessionMinRounds = 0;
sessionMaxRounds = nDarksendRounds;
}
//if we're set to less than a thousand, don't submit for than that to the pool
if(nAnonymizeDarkcoinAmount < DARKSEND_POOL_MAX/COIN) maxAmount = nAnonymizeDarkcoinAmount;
@ -1461,13 +1461,13 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
}
// select coins that should be given to the pool
if (!pwalletMain->SelectCoinsDark(nValueMin, maxAmount*COIN, vCoins, nValueIn, minRounds, maxRounds, hasFeeInput))
if (!pwalletMain->SelectCoinsDark(nValueMin, maxAmount*COIN, vCoins, nValueIn, sessionMinRounds, sessionMaxRounds, hasFeeInput))
{
nValueIn = 0;
vCoins.clear();
// look for inputs larger than the max amount, if we find anything we need to split it up
if (pwalletMain->SelectCoinsDark(maxAmount*COIN, 9999999*COIN, vCoins, nValueIn, minRounds, maxRounds, hasFeeInput))
if (pwalletMain->SelectCoinsDark(maxAmount*COIN, 9999999*COIN, vCoins, nValueIn, sessionMinRounds, sessionMaxRounds, hasFeeInput))
{
if(!fDryRun) SplitUpMoney();
return true;
@ -1483,7 +1483,7 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
(vecDisabledDenominations.size() > 0 && nValueIn < COIN*10)
){
//simply look for non-denominated coins
if (pwalletMain->SelectCoinsDark(maxAmount*COIN, 9999999*COIN, vCoins, nValueIn, minRounds, maxRounds, hasFeeInput))
if (pwalletMain->SelectCoinsDark(maxAmount*COIN, 9999999*COIN, vCoins, nValueIn, sessionMinRounds, sessionMaxRounds, hasFeeInput))
{
if(!fDryRun) SplitUpMoney();
return true;
@ -1517,38 +1517,6 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
sessionTotalValue = pwalletMain->GetTotalValue(vCoins);
//randomize the amounts we mix
// if we have minRounds set, or if our non-demon is less than 5% of denom coins
if(minRounds == 0 ||
pwalletMain->GetDenominatedBalance(true) * 0.05 > pwalletMain->GetDenominatedBalance(false)) {
for(int a = 0; a < 10; a++){ //try 10 amounts and see if we match a queue
int r = (rand()%(maxAmount-(nValueMin/COIN)))+(nValueMin/COIN);
vCoins.clear();
nValueIn = 0;
if (pwalletMain->SelectCoinsDark(nValueMin, r*COIN, vCoins, nValueIn, minRounds, maxRounds, hasFeeInput)){
sessionTotalValue = pwalletMain->GetTotalValue(vCoins);
// if it's in the queue, take it
if(nUseQueue > 33 && vecDarksendQueue.size() > 0){
BOOST_FOREACH(CDarksendQueue& dsq, vecDarksendQueue){
CService addr;
if(dsq.time == 0) continue;
if(!dsq.GetAddress(addr)) continue;
std::vector<int64_t> vecAmounts;
pwalletMain->ConvertList(vCoins, vecAmounts);
if(dsq.nDenom == GetDenominationsByAmounts(vecAmounts)) {
break;
}
}
} else {
break;
}
}
}
}
if(sessionTotalValue > maxAmount*COIN) sessionTotalValue = maxAmount*COIN;
double fDarkcoinSubmitted = (sessionTotalValue / CENT);
@ -1567,6 +1535,7 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
BOOST_FOREACH(CDarksendQueue& dsq, vecDarksendQueue){
CService addr;
if(dsq.time == 0) continue;
if(!dsq.GetAddress(addr)) continue;
if(dsq.IsExpired()) continue;
@ -1581,12 +1550,18 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
}
}
// If we don't match the denominations, we don't want to submit our inputs
if(dsq.nDenom != GetDenominationsByAmount(sessionTotalValue)) {
if(fDebug) LogPrintf(" dsq.nDenom != GetDenominationsByAmount %d %d \n", dsq.nDenom, GetDenominationsByAmount(sessionTotalValue));
// Try to match their denominations if possible
if (!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, nValueMin, maxAmount*COIN, vCoins, nValueIn, -2, 2)){
if (!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, nValueMin, maxAmount*COIN, vCoins, nValueIn, 2, nDarksendRounds)){
LogPrintf("DoAutomaticDenominating - Couldn't match denominations\n");
continue;
}
dsq.time = 0; //remove node
sessionMinRounds = 2;
sessionMaxRounds = nDarksendRounds;
} else {
sessionMinRounds = -2;
sessionMaxRounds = 2;
}
// connect to masternode and submit the queue request
if(ConnectNode((CAddress)addr, NULL, true)){
@ -1605,16 +1580,7 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
}
vecMasternodesUsed.push_back(dsq.vin);
if(minRounds >= 0){
//use same denominations
std::vector<int64_t> vecAmounts;
pwalletMain->ConvertList(vCoins, vecAmounts);
sessionDenom = GetDenominationsByAmounts(vecAmounts);
} else {
//use all possible denominations
sessionDenom = GetDenominationsByAmount(sessionTotalValue);
}
sessionDenom = dsq.nDenom;
pnode->PushMessage("dsa", sessionDenom, txCollateral);
LogPrintf("DoAutomaticDenominating --- connected (from queue), sending dsa for %d %d - %s\n", sessionDenom, GetDenominationsByAmount(sessionTotalValue), pnode->addr.ToString().c_str());
@ -1626,6 +1592,8 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
strAutoDenomResult = "Error connecting to masternode";
return DoAutomaticDenominating();
}
dsq.time = 0; //remove node
}
}
@ -1670,7 +1638,7 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
vecMasternodesUsed.push_back(darkSendMasterNodes[i].vin);
if(minRounds >= 0){
if(sessionMinRounds >= 0){
//use same denominations
std::vector<int64_t> vecAmounts;
pwalletMain->ConvertList(vCoins, vecAmounts);
@ -1699,13 +1667,18 @@ bool CDarkSendPool::DoAutomaticDenominating(bool fDryRun, bool ready)
if(!ready) return true;
if(sessionDenom == 0) return true;
}
bool CDarkSendPool::PrepareDarksendDenominate()
{
// Submit transaction to the pool if we get here, use sessionDenom so we use the same amount of money
std::string strError = pwalletMain->PrepareDarksendDenominate(minRounds, maxRounds, sessionTotalValue);
std::string strError = pwalletMain->PrepareDarksendDenominate(sessionMinRounds, sessionMaxRounds);
LogPrintf("DoAutomaticDenominating : Running darksend denominate. Return '%s'\n", strError.c_str());
if(strError == "") return true;
strAutoDenomResult = strError;
LogPrintf("DoAutomaticDenominating : Error running denominate, %s\n", strError.c_str());
return false;
}
@ -1912,7 +1885,7 @@ void CDarkSendPool::GetDenominationsToString(int nDenom, std::string& strDenom){
// bit 0 - 100DRK+1 ( bit on if present )
// bit 1 - 10DRK+1
// bit 2 - 1DRK+1
// bit 2 - .1DRK+1
// bit 3 - .1DRK+1
// bit 3 - non-denom

View File

@ -257,6 +257,8 @@ public:
int sessionUsers; //N Users have said they'll join
bool sessionFoundMasternode; //If we've found a compatible masternode
int sessionTries;
int sessionMinRounds;
int sessionMaxRounds;
int64_t sessionTotalValue; //used for autoDenom
std::vector<CTransaction> vecSessionCollateral;
@ -296,6 +298,8 @@ public:
minBlockSpacing = 1;
nDsqCount = 0;
vecDisabledDenominations.clear();
sessionMinRounds = 0;
sessionMaxRounds = 0;
SetCollateralAddress(strAddress);
SetNull();
@ -383,6 +387,7 @@ public:
// Passively run Darksend in the background according to the configuration in settings (only for QT)
bool DoAutomaticDenominating(bool fDryRun=false, bool ready=false);
bool PrepareDarksendDenominate();
// check for process in Darksend

View File

@ -28,13 +28,13 @@ Value darksend(const Array& params, bool fHelp)
"<amount> is a real and is rounded to the nearest 0.00000001"
+ HelpRequiringPassphrase());
if(fMasterNode)
return "DarkSend is not supported from masternodes";
if (pwalletMain->IsLocked())
throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first.");
if(params[0].get_str() == "auto"){
if(fMasterNode)
return "DarkSend is not supported from masternodes";
darkSendPool.DoAutomaticDenominating();
return "DoAutomaticDenominating";
}

View File

@ -1450,10 +1450,95 @@ struct CompareByPriority
}
};
bool CWallet::SelectCoinsByDenominations(int nDenom, int64_t nValueMin, int64_t nValueMax, std::vector<CTxIn>& setCoinsRet, int64_t& nValueRet, int nDarksendRoundsMin, int nDarksendRoundsMax)
{
CCoinControl *coinControl=NULL;
setCoinsRet.clear();
nValueRet = 0;
vector<COutput> vCoins;
AvailableCoins(vCoins, false, coinControl, ALL_COINS);
set<pair<const CWalletTx*,unsigned int> > setCoinsRet2;
//order the array so fees are first, then denominated money, then the rest.
std::random_shuffle(vCoins.rbegin(), vCoins.rend());
//keep track of each denomination that we have
int fFound100 = false;
int fFound10 = false;
int fFound1 = false;
int fFoundDot1 = false;
int fFoundND = false;
//Check to see if any of the denomination are off, in that case mark them as fulfilled
if(!(nDenom & (1 << 0))) fFound100 = true;
if(!(nDenom & (1 << 1))) fFound10 = true;
if(!(nDenom & (1 << 2))) fFound1 = true;
if(!(nDenom & (1 << 3))) fFoundDot1 = true;
if(!(nDenom & (1 << 4))) fFoundND = true;
BOOST_FOREACH(const COutput& out, vCoins)
{
//there's no reason to allow inputs less than 1 COIN into DS (other than denominations smaller than that amount)
if(out.tx->vout[out.i].nValue < 1*COIN && out.tx->vout[out.i].nValue != (.1*COIN)+1) continue;
if(fMasterNode && out.tx->vout[out.i].nValue == 1000*COIN) continue; //masternode input
if(nValueRet + out.tx->vout[out.i].nValue <= nValueMax){
bool fAccepted = false;
// Function returns as follows:
//
// bit 0 - 100DRK+1 ( bit on if present )
// bit 1 - 10DRK+1
// bit 2 - 1DRK+1
// bit 3 - .1DRK+1
// bit 4 - non-denom
CTxIn vin = CTxIn(out.tx->GetHash(),out.i);
int rounds = GetInputDarksendRounds(vin);
if(rounds >= nDarksendRoundsMax) continue;
if(rounds < nDarksendRoundsMin) continue;
if(fFound100 && fFound10 && fFound1 && fFoundDot1 && fFoundND){ //if fulfilled
//Denomination criterion has been met, we can take any matching denominations
if((nDenom & (1 << 0)) && out.tx->vout[out.i].nValue == ((100*COIN)+1)) {fAccepted = true;}
else if((nDenom & (1 << 1)) && out.tx->vout[out.i].nValue == ((10*COIN)+1)) {fAccepted = true;}
else if((nDenom & (1 << 2)) && out.tx->vout[out.i].nValue == ((1*COIN)+1)) {fAccepted = true;}
else if((nDenom & (1 << 3)) && out.tx->vout[out.i].nValue == ((.1*COIN)+1)) {fAccepted = true;}
else if((nDenom & (1 << 4))) {fAccepted = true;}
} else {
//Criterion has not been satisfied, we will only take 1 of each until it is.
if((nDenom & (1 << 0)) && out.tx->vout[out.i].nValue == ((100*COIN)+1) && !fFound100) {fAccepted = true; fFound100 = true;}
else if((nDenom & (1 << 1)) && out.tx->vout[out.i].nValue == ((10*COIN)+1) && !fFound10) {fAccepted = true; fFound10 = true;}
else if((nDenom & (1 << 2)) && out.tx->vout[out.i].nValue == ((1*COIN)+1) && !fFound1) {fAccepted = true; fFound1 = true;}
else if((nDenom & (1 << 3)) && out.tx->vout[out.i].nValue == ((.1*COIN)+1) && !fFoundDot1) {fAccepted = true; fFoundDot1 = true;}
else if((nDenom & (1 << 4)) && !fFoundND) {fAccepted = true; fFoundND = true;}
}
if(!fAccepted) continue;
vin.prevPubKey = out.tx->vout[out.i].scriptPubKey; // the inputs PubKey
nValueRet += out.tx->vout[out.i].nValue;
setCoinsRet.push_back(vin);
setCoinsRet2.insert(make_pair(out.tx, out.i));
}
}
if(nValueRet >= nValueMin && fFound100 && fFound10 && fFound1 && fFoundDot1 && fFoundND) return true;
return false;
}
bool CWallet::SelectCoinsDark(int64_t nValueMin, int64_t nValueMax, std::vector<CTxIn>& setCoinsRet, int64_t& nValueRet, int nDarksendRoundsMin, int nDarksendRoundsMax, bool& hasFeeInput) const
{
CCoinControl *coinControl=NULL;
setCoinsRet.clear();
nValueRet = 0;
vector<COutput> vCoins;
AvailableCoins(vCoins, false, coinControl, ALL_COINS);
@ -1962,40 +2047,29 @@ int64_t CWallet::GetTotalValue(std::vector<CTxIn> vCoins) {
return nTotalValue;
}
string CWallet::PrepareDarksendDenominate(int minRounds, int maxRounds, int64_t maxAmount)
string CWallet::PrepareDarksendDenominate(int minRounds, int maxRounds)
{
if (IsLocked())
return _("Error: Wallet locked, unable to create transaction!");
if(darkSendPool.GetState() != POOL_STATUS_ERROR && darkSendPool.GetState() != POOL_STATUS_SUCCESS){
if(darkSendPool.GetMyTransactionCount() > 0){
if(darkSendPool.GetState() != POOL_STATUS_ERROR && darkSendPool.GetState() != POOL_STATUS_SUCCESS)
if(darkSendPool.GetMyTransactionCount() > 0)
return _("Error: You already have pending entries in the Darksend pool");
}
}
// ** find the coins we'll use
std::vector<CTxIn> vCoins;
int64_t nValueIn = 0;
CReserveKey reservekey(this);
//make sure we
bool hasFeeInput = false;
//select coins we'll use
if (!SelectCoinsDark(0.1*COIN, maxAmount, vCoins, nValueIn, minRounds, maxRounds, hasFeeInput))
{
vCoins.clear();
if (!SelectCoinsByDenominations(darkSendPool.sessionDenom, 0.1*COIN, DARKSEND_POOL_MAX, vCoins, nValueIn, minRounds, maxRounds))
return _("Insufficient funds");
}
// calculate total value out
int64_t nTotalValue = GetTotalValue(vCoins);
LogPrintf("PrepareDarksendDenominate - preparing darksend denominate . Asked for: %d, Got: %d \n", maxAmount, nTotalValue);
LogPrintf("PrepareDarksendDenominate - preparing darksend denominate . Got: %d \n", nTotalValue);
//--------------
BOOST_FOREACH(CTxIn v, vCoins)
LockCoin(v.prevout);

View File

@ -133,6 +133,8 @@ private:
public:
bool SelectCoins(int64_t nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet, const CCoinControl *coinControl = NULL, AvailableCoinsType coin_type=ALL_COINS) const;
bool SelectCoinsDark(int64_t nValueMin, int64_t nValueMax, std::vector<CTxIn>& setCoinsRet, int64_t& nValueRet, int nDarksendRoundsMin, int nDarksendRoundsMax, bool& hasFeeInput) const;
bool SelectCoinsByDenominations(int nDenom, int64_t nValueMin, int64_t nValueMax, std::vector<CTxIn>& setCoinsRet, int64_t& nValueRet, int nDarksendRoundsMin, int nDarksendRoundsMax);
bool SelectCoinsByDenominations(int nDenom, int64_t nValueMin, int64_t nValueMax, int nDarksendRoundsMin, int nDarksendRoundsMax);
bool SelectCoinsDarkDenominated(int64_t nTargetValue, std::vector<CTxIn>& setCoinsRet, int64_t& nValueRet) const;
bool SelectCoinsMasternode(CTxIn& vin, int64_t& nValueRet, CScript& pubScript) const;
bool HasDarksendFeeInputs() const;
@ -286,7 +288,7 @@ public:
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, std::string strCommand="tx");
std::string SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, AvailableCoinsType coin_type=ALL_COINS);
std::string SendMoneyToDestination(const CTxDestination &address, int64_t nValue, CWalletTx& wtxNew, AvailableCoinsType coin_type=ALL_COINS);
std::string PrepareDarksendDenominate(int minRounds, int maxRounds, int64_t maxAmount);
std::string PrepareDarksendDenominate(int minRounds, int maxRounds);
bool CreateCollateralTransaction(CTransaction& txCollateral, std::string strReason);
bool ConvertList(std::vector<CTxIn> vCoins, std::vector<int64_t>& vecAmounts);