// ppcoin: optional setting to create coinstake only when unlocked;
// serves to disable the trivial sendmoney when OS account compromised
-bool fWalletUnlockStakeOnly = false;
+bool fWalletUnlockMintOnly = false;
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
{
}
//// debug print
- printf("SelectCoins() best subset: ");
- for (unsigned int i = 0; i < vValue.size(); i++)
- if (vfBest[i])
- printf("%s ", FormatMoney(vValue[i].first).c_str());
- printf("total %s\n", FormatMoney(nBest).c_str());
+ if (fDebug && GetBoolArg("-printselectcoin"))
+ {
+ printf("SelectCoins() best subset: ");
+ for (unsigned int i = 0; i < vValue.size(); i++)
+ if (vfBest[i])
+ printf("%s ", FormatMoney(vValue[i].first).c_str());
+ printf("total %s\n", FormatMoney(nBest).c_str());
+ }
}
return true;
nFeeRet += nMoveToFee;
}
+ // ppcoin: sub-cent change is moved to fee
+ if (nChange > 0 && nChange < MIN_TXOUT_AMOUNT)
+ {
+ nFeeRet += nChange;
+ nChange = 0;
+ }
+
if (nChange > 0)
{
// Note: We use a new key here to keep it from being obvious which side is the change.
}
// ppcoin: create coin stake transaction
-bool CWallet::CreateCoinStake(unsigned int nBits, CTransaction& txNew)
+bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64 nSearchInterval, CTransaction& txNew)
{
+ // The following split & combine thresholds are important to security
+ // Should not be adjusted if you don't understand the consequences
+ static unsigned int nStakeSplitAge = (60 * 60 * 24 * 90);
+ int64 nCombineThreshold = GetProofOfWorkReward(GetLastBlockIndex(pindexBest, false)->nBits) / 3;
+
CBigNum bnTargetPerCoinDay;
bnTargetPerCoinDay.SetCompact(nBits);
if (setCoins.empty())
return false;
int64 nCredit = 0;
+ CScript scriptPubKeyKernel;
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
CTxDB txdb("r");
CBlock block;
if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
continue;
- if (block.GetBlockTime() + STAKE_MIN_AGE > txNew.nTime)
+ if (block.GetBlockTime() + nStakeMinAge > txNew.nTime)
continue; // only count coins meeting min age requirement
int64 nValueIn = pcoin.first->vout[pcoin.second].nValue;
- CBigNum bnCoinDay = CBigNum(nValueIn) * (txNew.nTime-pcoin.first->nTime) / COIN / (24 * 60 * 60);
- // Calculate hash
- CDataStream ss(SER_GETHASH, 0);
- ss << nBits << block.nTime << (txindex.pos.nTxPos - txindex.pos.nBlockPos) << pcoin.first->nTime << pcoin.second << txNew.nTime;
- if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay)
+ CBigNum bnCoinDay = CBigNum(nValueIn) * min(txNew.nTime-pcoin.first->nTime, (unsigned int)STAKE_MAX_AGE) / COIN / (24 * 60 * 60);
+
+ bool fKernelFound = false;
+ for (int n=0; n<min(nSearchInterval,(int64)5) && !fKernelFound && !fShutdown; n++)
{
- txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second));
- nCredit += pcoin.first->vout[pcoin.second].nValue;
- vwtxPrev.push_back(pcoin.first);
- // Set output scriptPubKey
- txNew.vout.push_back(CTxOut(0, pcoin.first->vout[pcoin.second].scriptPubKey));
- break;
+ // Randomly pick a timestamp from protocol allowed range
+ txNew.nTime = GetAdjustedTime() - GetRandInt(60 * 60 * 2 - 60);
+ // Calculate hash
+ CDataStream ss(SER_GETHASH, 0);
+ ss << nBits << block.nTime << (txindex.pos.nTxPos - txindex.pos.nBlockPos) << pcoin.first->nTime << pcoin.second << txNew.nTime;
+ if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay)
+ {
+ // Found a kernel
+ if (fDebug && GetBoolArg("-printcoinstake"))
+ printf("CreateCoinStake : kernel found\n");
+ vector<valtype> vSolutions;
+ txnouttype whichType;
+ CScript scriptPubKeyOut;
+ scriptPubKeyKernel = pcoin.first->vout[pcoin.second].scriptPubKey;
+ if (!Solver(scriptPubKeyKernel, whichType, vSolutions))
+ {
+ if (fDebug && GetBoolArg("-printcoinstake"))
+ printf("CreateCoinStake : failed to parse kernel\n", whichType);
+ break;
+ }
+ if (fDebug && GetBoolArg("-printcoinstake"))
+ printf("CreateCoinStake : parsed kernel type=%d\n", whichType);
+ if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH)
+ {
+ if (fDebug && GetBoolArg("-printcoinstake"))
+ printf("CreateCoinStake : no support for kernel type=%d\n", whichType);
+ break; // only support pay to public key and pay to address
+ }
+ if (whichType == TX_PUBKEYHASH) // pay to address type
+ {
+ // convert to pay to public key type
+ CKey key;
+ if (!keystore.GetKey(uint160(vSolutions[0]), key))
+ {
+ if (fDebug && GetBoolArg("-printcoinstake"))
+ printf("CreateCoinStake : failed to get key for kernel type=%d\n", whichType);
+ break; // unable to find corresponding public key
+ }
+ scriptPubKeyOut << key.GetPubKey() << OP_CHECKSIG;
+ }
+ else
+ scriptPubKeyOut = scriptPubKeyKernel;
+
+ txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second));
+ nCredit += pcoin.first->vout[pcoin.second].nValue;
+ vwtxPrev.push_back(pcoin.first);
+ txNew.vout.push_back(CTxOut(0, scriptPubKeyOut));
+ if (block.GetBlockTime() + nStakeSplitAge > txNew.nTime)
+ txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); //split stake
+ if (fDebug && GetBoolArg("-printcoinstake"))
+ printf("CreateCoinStake : added kernel type=%d\n", whichType);
+ fKernelFound = true;
+ }
}
+ if (fKernelFound || fShutdown)
+ break; // if kernel is found stop searching
}
if (nCredit == 0 || nCredit > nBalance - nReserveBalance)
return false;
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
- if (pcoin.first->vout[pcoin.second].scriptPubKey == txNew.vout[1].scriptPubKey && pcoin.first->GetHash() != txNew.vin[0].prevout.hash)
+ // Attempt to add more inputs
+ // Only add coins of the same key/address as kernel
+ if (txNew.vout.size() == 2 && ((pcoin.first->vout[pcoin.second].scriptPubKey == scriptPubKeyKernel || pcoin.first->vout[pcoin.second].scriptPubKey == txNew.vout[1].scriptPubKey))
+ && pcoin.first->GetHash() != txNew.vin[0].prevout.hash)
{
+ // Stop adding more inputs if value is already pretty significant
+ if (nCredit > nCombineThreshold)
+ break;
+ // Stop adding inputs if reached reserve limit
if (nCredit + pcoin.first->vout[pcoin.second].nValue > nBalance - nReserveBalance)
break;
+ // Do not add additional significant input
+ if (pcoin.first->vout[pcoin.second].nValue > nCombineThreshold)
+ continue;
txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second));
nCredit += pcoin.first->vout[pcoin.second].nValue;
vwtxPrev.push_back(pcoin.first);
return error("CreateCoinStake : failed to calculate coin age");
nCredit += GetProofOfStakeReward(nCoinAge);
}
- // Set output amount
- txNew.vout[1].nValue = nCredit;
- // Sign
- int nIn = 0;
- BOOST_FOREACH(const CWalletTx* pcoin, vwtxPrev)
+ int64 nMinFee = 0;
+ loop
{
- if (!SignSignature(*this, *pcoin, txNew, nIn++))
- return error("CreateCoinStake : failed to sign coinstake");
+ // Set output amount
+ if (txNew.vout.size() == 3)
+ {
+ txNew.vout[1].nValue = ((nCredit - nMinFee) / 2 / CENT) * CENT;
+ txNew.vout[2].nValue = nCredit - nMinFee - txNew.vout[1].nValue;
+ }
+ else
+ txNew.vout[1].nValue = nCredit - nMinFee;
+
+ // Sign
+ int nIn = 0;
+ BOOST_FOREACH(const CWalletTx* pcoin, vwtxPrev)
+ {
+ if (!SignSignature(*this, *pcoin, txNew, nIn++))
+ return error("CreateCoinStake : failed to sign coinstake");
+ }
+
+ // Limit size
+ unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION);
+ if (nBytes >= MAX_BLOCK_SIZE_GEN/5)
+ return false;
+
+ // Check enough fee is paid
+ if (nMinFee < txNew.GetMinFee() - MIN_TX_FEE)
+ {
+ nMinFee = txNew.GetMinFee() - MIN_TX_FEE;
+ continue; // try signing again
+ }
+ else
+ {
+ if (fDebug && GetBoolArg("-printfee"))
+ printf("CreateCoinStake : fee for coinstake %s\n", FormatMoney(nMinFee).c_str());
+ break;
+ }
}
+
+ // Successfully generated coinstake
return true;
}
printf("SendMoney() : %s", strError.c_str());
return strError;
}
- if (fWalletUnlockStakeOnly)
+ if (fWalletUnlockMintOnly)
{
- string strError = _("Error: Wallet unlocked for coinstake only, unable to create transaction.");
+ string strError = _("Error: Wallet unlocked for block minting only, unable to create transaction.");
printf("SendMoney() : %s", strError.c_str());
return strError;
}
if (!HaveKey(Hash160(keypool.vchPubKey)))
throw runtime_error("ReserveKeyFromKeyPool() : unknown key in key pool");
assert(!keypool.vchPubKey.empty());
- printf("keypool reserve %"PRI64d"\n", nIndex);
+ if (fDebug && GetBoolArg("-printkeypool"))
+ printf("keypool reserve %"PRI64d"\n", nIndex);
}
}
LOCK(cs_wallet);
setKeyPool.insert(nIndex);
}
- printf("keypool return %"PRI64d"\n", nIndex);
+ if (fDebug && GetBoolArg("-printkeypool"))
+ printf("keypool return %"PRI64d"\n", nIndex);
}
bool CWallet::GetKeyFromPool(vector<unsigned char>& result, bool fAllowReuse)
continue;
for (int n=0; n < pcoin->vout.size(); n++)
{
- if (pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull()))
+ if (IsMine(pcoin->vout[n]) && pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull()))
{
- printf("CheckSpentCoins found lost coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n);
+ printf("CheckSpentCoins found lost coin %sppc %s[%d]\n", FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n);
nMismatchFound++;
nBalanceInQuestion += pcoin->vout[n].nValue;
}
- else if (!pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull()))
+ else if (IsMine(pcoin->vout[n]) && !pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull()))
{
- printf("CheckSpentCoins found spent coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n);
+ printf("CheckSpentCoins found spent coin %sppc %s[%d]\n", FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n);
nMismatchFound++;
nBalanceInQuestion += pcoin->vout[n].nValue;
}
continue;
for (int n=0; n < pcoin->vout.size(); n++)
{
- if (pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull()))
+ if (IsMine(pcoin->vout[n]) && pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull()))
{
- printf("FixSpentCoins found lost coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n);
+ printf("FixSpentCoins found lost coin %sppc %s[%d]\n", FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n);
nMismatchFound++;
nBalanceInQuestion += pcoin->vout[n].nValue;
pcoin->MarkUnspent(n);
pcoin->WriteToDisk();
}
- else if (!pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull()))
+ else if (IsMine(pcoin->vout[n]) && !pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull()))
{
- printf("FixSpentCoins found spent coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n);
+ printf("FixSpentCoins found spent coin %sppc %s[%d]\n", FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n);
nMismatchFound++;
nBalanceInQuestion += pcoin->vout[n].nValue;
pcoin->MarkSpent(n);