diff --git a/doc/release-notes.md b/doc/release-notes.md index 8bfd64cdd9..44216a07b1 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -136,19 +136,48 @@ RPC Changes The `backupwallet` RPC command no longer allows for overwriting the currently in use wallet.dat file. This was done to avoid potential file corruption caused by multiple conflicting file access operations. -### Spendzerocoin Security Level Removed + +- "isPublicSpend" boolean (optional) input parameter is removed from the following commands: + - `createrawzerocoinspend` + - `spendzerocoin` + - `spendzerocoinmints` + - `spendrawzerocoin` The `securitylevel` argument has been removed from the `spendzerocoin` RPC command. -### Spendzerocoinmints Added + +- "mintchange" and "minimizechange" boolean input parameters are removed from the following commands: + - `spendzerocoin` Introduce the `spendzerocoinmints` RPC call to enable spending specific zerocoins, provided as an array of hex strings (serial hashes). -### Getreceivedbyaddress Update + +- `setstakesplitthreshold` now accepts decimal amounts. If the provided value is `0`, split staking gets disabled. `getstakesplitthreshold` returns a double. When calling `getreceivedbyaddress` with a non-wallet address, return a proper error code/message instead of just `0` -### Validateaddress More Verbosity +- The output of `getstakingstatus` was reworked. It now shows the following information: + ``` + { + "staking_status": true|false, (boolean) whether the wallet is staking or not + "staking_enabled": true|false, (boolean) whether staking is enabled/disabled in pivx.conf + "coldstaking_enabled": true|false, (boolean) whether cold-staking is enabled/disabled in pivx.conf + "haveconnections": true|false, (boolean) whether network connections are present + "mnsync": true|false, (boolean) whether masternode data is synced + "walletunlocked": true|false, (boolean) whether the wallet is unlocked + "stakeablecoins": n, (numeric) number of stakeable UTXOs + "stakingbalance": d, (numeric) DOGEC value of the stakeable coins (minus reserve balance, if any) + "stakesplitthreshold": d, (numeric) value of the current threshold for stake split + "lastattempt_age": n, (numeric) seconds since last stake attempt + "lastattempt_depth": n, (numeric) depth of the block on top of which the last stake attempt was made + "lastattempt_hash": xxx, (hex string) hash of the block on top of which the last stake attempt was made + "lastattempt_coins": n, (numeric) number of stakeable coins available during last stake attempt + "lastattempt_tries": n, (numeric) number of stakeable coins checked during last stake attempt + } + ``` + + +### Removed commands `validateaddress` now has the ability to return more (non-critical or identifying) details about P2SH (multisig) addresses by removing the needless check against ISMINE_NO. diff --git a/src/config/dogecash-config.h.in b/src/config/dogecash-config.h.in index 6d72b54bf4..eeef38a99e 100644 --- a/src/config/dogecash-config.h.in +++ b/src/config/dogecash-config.h.in @@ -198,9 +198,6 @@ /* Define to 1 if you have the `crypt32' library (-lcrypt32). */ #undef HAVE_LIBCRYPT32 -/* Define to 1 if you have the `crypto' library (-lcrypto). */ -#undef HAVE_LIBCRYPTO - /* Define to 1 if you have the `gdi32' library (-lgdi32). */ #undef HAVE_LIBGDI32 diff --git a/src/kernel.cpp b/src/kernel.cpp index 49b3a23f82..a8996fc5f8 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -361,10 +361,6 @@ bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int // Time protocol V2: one-try if (Params().IsTimeProtocolV2(nHeight)) { - // store a time stamp of when we last hashed on this block - mapHashedBlocks.clear(); - mapHashedBlocks[pindexPrev->nHeight] = GetTime(); - // check required min depth for stake const int nHeightBlockFrom = pindexFrom->nHeight; if (nHeight < nHeightBlockFrom + Params().COINSTAKE_MIN_DEPTH()) @@ -400,10 +396,6 @@ bool StakeV1(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, const uint3 return error("%s : stake age violation, nTimeBlockFrom = %d, prevBlockTime = %d -- maxTime = %d ", __func__, nTimeBlockFrom, prevBlockTime, maxTime); while (nTryTime > minTime) { - // store a time stamp of when we last hashed on this block - mapHashedBlocks.clear(); - mapHashedBlocks[pindexPrev->nHeight] = GetTime(); - //new block came in, move on if (chainActive.Height() != pindexPrev->nHeight) break; @@ -418,10 +410,6 @@ bool StakeV1(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, const uint3 } nTimeTx = nTryTime; - - mapHashedBlocks.clear(); - mapHashedBlocks[pindexPrev->nHeight] = GetTime(); //store a time stamp of when we last hashed on this block - return fSuccess; } diff --git a/src/main.cpp b/src/main.cpp index f9442e6c9a..b1ac64882c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -75,9 +75,7 @@ using namespace libzerocoin; RecursiveMutex cs_main; BlockMap mapBlockIndex; -map mapProofOfStake; -set > setStakeSeen; -map mapHashedBlocks; +std::map mapProofOfStake; CChain chainActive; CBlockIndex* pindexBestHeader = nullptr; int64_t nTimeBestReceived = 0; @@ -2926,7 +2924,7 @@ bool RecalculateDOGECSupply(int nHeightStart, bool fSkipZdogec) pindex->nMoneySupply = nSupplyPrev + nValueOut - nValueIn; nSupplyPrev = pindex->nMoneySupply; - // Rewrite zpiv supply too + // Rewrite zdogec supply too if (!fSkipZdogec && pindex->nHeight >= Params().Zerocoin_StartHeight()) { UpdatezdogecSupply(block, pindex, true); } @@ -3016,7 +3014,7 @@ bool UpdatezdogecSupply(const CBlock& block, CBlockIndex* pindex, bool fJustChec //Reset the supply to previous block pindex->mapZerocoinSupply = pindex->pprev->mapZerocoinSupply; - //Add mints to zPIV supply (mints are forever disabled after last checkpoint) + //Add mints to zdogec supply (mints are forever disabled after last checkpoint) if (pindex->nHeight < Params().Zerocoin_Block_LastGoodCheckpoint()) { std::list listMints; std::set setAddedToWallet; @@ -4093,10 +4091,6 @@ CBlockIndex* AddToBlockIndex(const CBlock& block) pindexNew->nSequenceId = 0; BlockMap::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; - //mark as PoS seen - if (pindexNew->IsProofOfStake()) - setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); - pindexNew->phashBlock = &((*mi).first); BlockMap::iterator miPrev = mapBlockIndex.find(block.hashPrevBlock); if (miPrev != mapBlockIndex.end()) { @@ -5244,10 +5238,6 @@ CBlockIndex* InsertBlockIndex(uint256 hash) throw runtime_error("LoadBlockIndex() : new CBlockIndex failed"); mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; - //mark as PoS seen - if (pindexNew->IsProofOfStake()) - setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); - pindexNew->phashBlock = &((*mi).first); return pindexNew; diff --git a/src/main.h b/src/main.h index 574d4bb390..0cf949a328 100644 --- a/src/main.h +++ b/src/main.h @@ -170,14 +170,9 @@ extern bool fClearSpendCache; extern bool fLargeWorkForkFound; extern bool fLargeWorkInvalidChainFound; -extern unsigned int nStakeMinAge; -extern int64_t nLastCoinStakeSearchInterval; -extern int64_t nLastCoinStakeSearchTime; extern int64_t nReserveBalance; extern std::map mapRejectedBlocks; -extern std::map mapHashedBlocks; -extern std::set > setStakeSeen; extern std::map mapZerocoinspends; //txid, time received /** Best header we've seen so far (used for getheaders queries' starting points). */ diff --git a/src/miner.cpp b/src/miner.cpp index 1277ef4792..46f781bf36 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -64,7 +64,6 @@ class COrphan uint64_t nLastBlockTx = 0; uint64_t nLastBlockSize = 0; -int64_t nLastCoinStakeSearchInterval = 0; // We want to sort transactions by priority and fee rate, so: typedef boost::tuple TxPriority; @@ -102,28 +101,30 @@ void UpdateTime(CBlockHeader* pblock, const CBlockIndex* pindexPrev) pblock->nBits = GetNextWorkRequired(pindexPrev, pblock); } +CBlockIndex* GetChainTip() +{ + LOCK(cs_main); + CBlockIndex* p = chainActive.Tip(); + if (!p) + return nullptr; + // Do not pass in the chain active tip, because it can change. + // Instead pass the blockindex directly from mapblockindex, which is const + return mapBlockIndex.at(p->GetBlockHash()); +} + std::pair > pCheckpointCache; CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, bool fProofOfStake) { CReserveKey reservekey(pwallet); // Create new block - unique_ptr pblocktemplate(new CBlockTemplate()); - if (!pblocktemplate.get()) - return NULL; + std::unique_ptr pblocktemplate(new CBlockTemplate()); + if (!pblocktemplate.get()) return nullptr; CBlock* pblock = &pblocktemplate->block; // pointer for convenience // Tip - CBlockIndex* pindexPrev = nullptr; - { // Don't keep cs_main locked - LOCK(cs_main); - pindexPrev = chainActive.Tip(); - if (!pindexPrev) - return nullptr; - // Do not pass in the chain tip, because it can change. - // Instead pass the blockindex directly from mapblockindex, which is const - pindexPrev = mapBlockIndex.at(pindexPrev->GetBlockHash()); - } + CBlockIndex* pindexPrev = GetChainTip(); + if (!pindexPrev) return nullptr; const int nHeight = pindexPrev->nHeight + 1; @@ -156,32 +157,20 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, pblocktemplate->vTxFees.push_back(-1); // updated at end pblocktemplate->vTxSigOps.push_back(-1); // updated at end - // ppcoin: if coinstake available add coinstake tx - static int64_t nLastCoinStakeSearchTime = GetAdjustedTime(); // only initialized at startup - if (fProofOfStake) { boost::this_thread::interruption_point(); pblock->nTime = GetAdjustedTime(); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock); CMutableTransaction txCoinStake; - int64_t nSearchTime = pblock->nTime; // search to current time - bool fStakeFound = false; - if (nSearchTime >= nLastCoinStakeSearchTime) { - int64_t nTxNewTime = 0; - if (pwallet->CreateCoinStake(*pwallet, pindexPrev, pblock->nBits, nSearchTime - nLastCoinStakeSearchTime, txCoinStake, nTxNewTime)) { - pblock->nTime = nTxNewTime; - pblock->vtx[0].vout[0].SetEmpty(); - pblock->vtx.push_back(CTransaction(txCoinStake)); - fStakeFound = true; - } - nLastCoinStakeSearchInterval = nSearchTime - nLastCoinStakeSearchTime; - nLastCoinStakeSearchTime = nSearchTime; - } - - if (!fStakeFound) { + int64_t nTxNewTime = 0; + if (!pwallet->CreateCoinStake(*pwallet, pindexPrev, pblock->nBits, txCoinStake, nTxNewTime)) { LogPrint("staking", "CreateNewBlock(): stake not found\n"); - return NULL; + return nullptr; } + // Stake found + pblock->nTime = nTxNewTime; + pblock->vtx[0].vout[0].SetEmpty(); + pblock->vtx.push_back(CTransaction(txCoinStake)); } // Largest block you're willing to create: @@ -491,7 +480,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, uint256 nCheckpoint; uint256 hashBlockLastAccumulated = chainActive[nHeight - (nHeight % 10) - 10]->GetBlockHash(); if (nHeight >= pCheckpointCache.first || pCheckpointCache.second.first != hashBlockLastAccumulated) { - //For the period before v2 activation, zPIV will be disabled and previous block's checkpoint is all that will be needed + //For the period before v2 activation, zdogec will be disabled and previous block's checkpoint is all that will be needed pCheckpointCache.second.second = pindexPrev->nAccumulatorCheckpoint; if (pindexPrev->nHeight + 1 >= Params().Zerocoin_Block_V2_Start()) { AccumulatorMap mapAccumulators(Params().Zerocoin_Params(false)); @@ -654,18 +643,26 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) LogPrintf("DogeCashMiner started\n"); SetThreadPriority(THREAD_PRIORITY_LOWEST); util::ThreadRename("dogecash-miner"); - const int64_t nSpacingMillis = Params().TargetSpacing() * 1000; - const int last_pow_block = Params().LAST_POW_BLOCK(); // Each thread has its own key and counter CReserveKey reservekey(pwallet); unsigned int nExtraNonce = 0; - bool fLastLoopOrphan = false; bool fColdStake = GetBoolArg("-coldstaking", true); CAmount stakingBalance = 0; while (fGenerateBitcoins || fProofOfStake) { + CBlockIndex* pindexPrev = GetChainTip(); + if (!pindexPrev) { + MilliSleep(Params().TargetSpacing() * 1000); // sleep a block + continue; + } if (fProofOfStake) { + if (pindexPrev->nHeight < Params().LAST_POW_BLOCK()) { + // The last PoW block hasn't even been mined yet. + MilliSleep(Params().TargetSpacing() * 1000); // sleep a block + continue; + } + //control the amount of times the client will check for mintable coins if ((GetTime() - nMintableLastCheck > 5 * 60)) // 5 minute check time { @@ -676,13 +673,11 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) while (vNodes.empty() || pwallet->IsLocked() || !fMintableCoins || (stakingBalance > 0 && nReserveBalance >= stakingBalance) || masternodeSync.NotCompleted()) { - nLastCoinStakeSearchInterval = 0; MilliSleep(5000); continue; } while (vNodes.empty() || pwallet->IsLocked() || !fMintableCoins || (pwallet->GetBalance() > 0 && nReserveBalance >= pwallet->GetBalance()) || !masternodeSync.IsSynced()) { - nLastCoinStakeSearchInterval = 0; // Do a separate 1 minute check here to ensure fMintableCoins is updated if (!fMintableCoins && (GetTime() - nMintableLastCheck > 1 * 60)) // 1 minute check time { @@ -695,28 +690,32 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) continue; } - const bool fTimeV2 = Params().IsTimeProtocolV2(chainActive.Height()+1); + const bool fTimeV2 = Params().IsTimeProtocolV2(pindexPrev->nHeight+1); //search our map of hashed blocks, see if bestblock has been hashed yet - const int chainHeight = chainActive.Height(); - if (mapHashedBlocks.count(chainHeight) && !fLastLoopOrphan) + if (pwallet->pStakerStatus->GetLastHash() == pindexPrev->GetBlockHash()) { - int64_t tipHashTime = mapHashedBlocks[chainHeight]; - if ( (!fTimeV2 && GetTime() < tipHashTime + 22) || - (fTimeV2 && GetCurrentTimeSlot() <= tipHashTime) ) + int64_t lastHashTime = pwallet->pStakerStatus->GetLastTime(); + if ( (!fTimeV2 && GetTime() < lastHashTime + 22) || + (fTimeV2 && GetCurrentTimeSlot() <= lastHashTime) ) { MilliSleep(2000); continue; } } - } + } else { // PoW + if ((pindexPrev->nHeight - 6) > Params().LAST_POW_BLOCK()) + { + // Run for a little while longer, just in case there is a rewind on the chain. + LogPrintf("%s: Exiting Proof of Work Mining Thread at height: %d\n", + __func__, chainActive.Tip()->nHeight); + return; + } + } // // Create new block // unsigned int nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); - CBlockIndex* pindexPrev = chainActive.Tip(); - if (!pindexPrev) - continue; unique_ptr pblocktemplate( fProofOfStake ? CreateNewBlock(CScript(), pwallet, fProofOfStake) : CreateNewBlockWithKey(reservekey, pwallet) @@ -750,10 +749,10 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) continue; } - LogPrintf("CPUMiner : proof-of-stake block was signed %s \n", pblock->GetHash().ToString().c_str()); + LogPrintf("%s : proof-of-stake block was signed %s \n", __func__, pblock->GetHash().ToString().c_str()); SetThreadPriority(THREAD_PRIORITY_NORMAL); if (!ProcessBlockFound(pblock, *pwallet, reservekey)) { - fLastLoopOrphan = true; + LogPrintf("%s: New block orphaned\n", __func__); continue; } SetThreadPriority(THREAD_PRIORITY_LOWEST); diff --git a/src/miner.h b/src/miner.h index 55cf8f4474..c91c1e358f 100644 --- a/src/miner.h +++ b/src/miner.h @@ -18,6 +18,8 @@ class CWallet; struct CBlockTemplate; +/** Get reliable pointer to current chain tip */ +CBlockIndex* GetChainTip(); /** Generate a new block, without valid proof-of-work */ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, bool fProofOfStake); CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey, CWallet* pwallet, bool fProofOfStake); diff --git a/src/qt/dogecash/send.cpp b/src/qt/dogecash/send.cpp index 8404eff90d..59fd436d79 100755 --- a/src/qt/dogecash/send.cpp +++ b/src/qt/dogecash/send.cpp @@ -323,17 +323,17 @@ void SendWidget::setFocusOnLastEntry() void SendWidget::showHideCheckBoxDelegations() { // Show checkbox only when there is any available owned delegation, - // coincontrol is not selected, and we are trying to spend PIV (not zPIV) - const bool isZpiv = ui->pushRight->isChecked(); + // coincontrol is not selected, and we are trying to spend DOGEC (not zdogec) + const bool iszdogec = ui->pushRight->isChecked(); const bool isCControl = CoinControlDialog::coinControl->HasSelected(); const bool hasDel = cachedDelegatedBalance > 0; - const bool showCheckBox = !isZpiv && !isCControl && hasDel; + const bool showCheckBox = !iszdogec && !isCControl && hasDel; ui->checkBoxDelegations->setVisible(showCheckBox); if (showCheckBox) ui->checkBoxDelegations->setToolTip( tr("Possibly spend coins delegated for cold-staking (currently available: %1").arg( - GUIUtil::formatBalance(cachedDelegatedBalance, nDisplayUnit, isZpiv)) + GUIUtil::formatBalance(cachedDelegatedBalance, nDisplayUnit, iszdogec)) ); } diff --git a/src/qt/dogecash/topbar.cpp b/src/qt/dogecash/topbar.cpp index 85e695857d..d3fca44cdd 100755 --- a/src/qt/dogecash/topbar.cpp +++ b/src/qt/dogecash/topbar.cpp @@ -365,22 +365,23 @@ void TopBar::updateAutoMintStatus(){ ui->pushButtonMint->setChecked(fEnableZeromint); } -void TopBar::updateStakingStatus(){ - if (getStakingStatus()) { - if (!ui->pushButtonStack->isChecked()) { - ui->pushButtonStack->setButtonText(tr("Staking active")); - ui->pushButtonStack->setChecked(true); - ui->pushButtonStack->setButtonClassStyle("cssClass", "btn-check-stack", true); - } - }else{ - if (ui->pushButtonStack->isChecked()) { - ui->pushButtonStack->setButtonText(tr("Staking not active")); - ui->pushButtonStack->setChecked(false); - ui->pushButtonStack->setButtonClassStyle("cssClass", "btn-check-stack-inactive", true); - } +void TopBar::setStakingStatusActive(bool fActive) +{ + if (ui->pushButtonStack->isChecked() != fActive) { + ui->pushButtonStack->setButtonText(fActive ? tr("Staking active") : tr("Staking not active")); + ui->pushButtonStack->setChecked(fActive); + ui->pushButtonStack->setButtonClassStyle("cssClass", (fActive ? + "btn-check-stack" : + "btn-check-stack-inactive"), true); } } +void TopBar::updateStakingStatus(){ + setStakingStatusActive(walletModel && + !walletModel->isWalletLocked() && + walletModel->isStakingStatusActive()); +} + void TopBar::setNumConnections(int count) { if(count > 0){ if(!ui->pushButtonConnection->isChecked()) { @@ -539,6 +540,9 @@ void TopBar::refreshStatus(){ case WalletModel::EncryptionStatus::Unlocked: ui->pushButtonLock->setButtonText("Wallet Unlocked"); ui->pushButtonLock->setButtonClassStyle("cssClass", "btn-check-status-unlock", true); + // Directly update the staking status icon when the wallet is manually locked here + // so the feedback is instant (no need to wait for the polling timeout) + setStakingStatusActive(false); break; } updateStyle(ui->pushButtonLock); diff --git a/src/qt/dogecash/topbar.h b/src/qt/dogecash/topbar.h index bbbc9eb63a..5de2ee2fbf 100644 --- a/src/qt/dogecash/topbar.h +++ b/src/qt/dogecash/topbar.h @@ -48,6 +48,7 @@ public slots: void setNumConnections(int count); void setNumBlocks(int count); void updateAutoMintStatus(); + void setStakingStatusActive(bool fActive); void updateStakingStatus(); signals: diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 1d0cb37046..5a043d872f 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -72,6 +72,10 @@ bool WalletModel::isColdStakingNetworkelyEnabled() const { return sporkManager.IsSporkActive(SPORK_17_COLDSTAKING_ENFORCEMENT); } +bool WalletModel::isStakingStatusActive() const { + return wallet->pStakerStatus->IsActive(); +} + CAmount WalletModel::getBalance(const CCoinControl* coinControl, bool fIncludeDelegated) const { if (coinControl) { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 7dfd68be95..43c1db26bb 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -146,7 +146,11 @@ class WalletModel : public QObject bool isColdStakingNetworkelyEnabled() const; CAmount getMinColdStakingAmount() const; + CAmount getBalance(const CCoinControl* coinControl = nullptr, bool fIncludeDelegated = true) const; + /* current staking status from the miner thread **/ + bool isStakingStatusActive() const; + CAmount getUnconfirmedBalance() const; CAmount getImmatureBalance() const; CAmount getLockedBalance() const; @@ -222,19 +226,6 @@ class WalletModel : public QObject CZerocoinSpendReceipt &receipt ); - // ################### - // Cold Staking - // ################### - - - - // ################### - // End Cold Staking - // ################### - - - - // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString& passphrase); // Passphrase only needed when unlocking diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index cd991a2e56..7e693e6da6 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -52,18 +52,19 @@ UniValue getinfo(const UniValue& params, bool fHelp) "\nResult:\n" "{\n" - " \"version\": xxxxx, (numeric) the server version\n" - " \"protocolversion\": xxxxx, (numeric) the protocol version\n" - " \"walletversion\": xxxxx, (numeric) the wallet version\n" - " \"balance\": xxxxxxx, (numeric) the total dogecash balance of the wallet (excluding zerocoins)\n" - " \"zerocoinbalance\": xxxxxxx, (numeric) the total zerocoin balance of the wallet\n" - " \"blocks\": xxxxxx, (numeric) the current number of blocks processed in the server\n" - " \"timeoffset\": xxxxx, (numeric) the time offset\n" - " \"connections\": xxxxx, (numeric) the number of connections\n" - " \"proxy\": \"host:port\", (string, optional) the proxy used by the server\n" - " \"difficulty\": xxxxxx, (numeric) the current difficulty\n" - " \"testnet\": true|false, (boolean) if the server is using testnet or not\n" - " \"moneysupply\" : \"supply\" (numeric) The money supply when this block was added to the blockchain\n" + " \"version\": xxxxx, (numeric) the server version\n" + " \"protocolversion\": xxxxx, (numeric) the protocol version\n" + " \"walletversion\": xxxxx, (numeric) the wallet version\n" + " \"balance\": xxxxxxx, (numeric) the total pivx balance of the wallet (excluding zerocoins)\n" + " \"zerocoinbalance\": xxxxxxx, (numeric) the total zerocoin balance of the wallet\n" + " \"staking status\": true|false, (boolean) if the wallet is staking or not\n" + " \"blocks\": xxxxxx, (numeric) the current number of blocks processed in the server\n" + " \"timeoffset\": xxxxx, (numeric) the time offset\n" + " \"connections\": xxxxx, (numeric) the number of connections\n" + " \"proxy\": \"host:port\", (string, optional) the proxy used by the server\n" + " \"difficulty\": xxxxxx, (numeric) the current difficulty\n" + " \"testnet\": true|false, (boolean) if the server is using testnet or not\n" + " \"moneysupply\" : \"supply\" (numeric) The money supply when this block was added to the blockchain\n" " \"zdogecsupply\" :\n" " {\n" " \"1\" : n, (numeric) supply of 1 zdogec denomination\n" @@ -76,13 +77,12 @@ UniValue getinfo(const UniValue& params, bool fHelp) " \"5000\" : n, (numeric) supply of 5000 zdogec denomination\n" " \"total\" : n, (numeric) The total supply of all zdogec denominations\n" " }\n" - " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n" - " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" - " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" - " \"paytxfee\": x.xxxx, (numeric) the transaction fee set in dogecash/kb\n" - " \"relayfee\": x.xxxx, (numeric) minimum relay fee for non-free transactions in dogecash/kb\n" - " \"staking status\": true|false, (boolean) if the wallet is staking or not\n" - " \"errors\": \"...\" (string) any error messages\n" + " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n" + " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" + " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" + " \"paytxfee\": x.xxxx, (numeric) the transaction fee set in pivx/kb\n" + " \"relayfee\": x.xxxx, (numeric) minimum relay fee for non-free transactions in pivx/kb\n" + " \"errors\": \"...\" (string) any error messages\n" "}\n" "\nExamples:\n" + @@ -124,6 +124,9 @@ UniValue getinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); obj.push_back(Pair("zerocoinbalance", ValueFromAmount(pwalletMain->GetZerocoinBalance(true)))); + obj.push_back(Pair("staking status", (pwalletMain->pStakerStatus->IsActive() ? + "Staking Active" : + "Staking Not Active"))); } #endif obj.push_back(Pair("blocks", (int)chainActive.Height())); @@ -157,19 +160,9 @@ UniValue getinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); #endif obj.push_back(Pair("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()))); - obj.push_back(Pair("staking status", (getStakingStatus() ? "Staking Active" : "Staking Not Active"))); obj.push_back(Pair("errors", GetWarnings("statusbar"))); return obj; } -bool getStakingStatus() { - bool nStaking = false; - if (mapHashedBlocks.count(chainActive.Tip()->nHeight)) - nStaking = true; - else if (mapHashedBlocks.count(chainActive.Tip()->nHeight - 1) && nLastCoinStakeSearchInterval) - nStaking = true; - - return nStaking; -} UniValue mnsync(const UniValue& params, bool fHelp) { std::string strMode; @@ -675,41 +668,54 @@ UniValue getstakingstatus(const UniValue& params, bool fHelp) "\nResult:\n" "{\n" - " \"validtime\": true|false, (boolean) if the chain tip is within staking phases\n" - " \"haveconnections\": true|false, (boolean) if network connections are present\n" - " \"walletunlocked\": true|false, (boolean) if the wallet is unlocked\n" - " \"mintablecoins\": true|false, (boolean) if the wallet has mintable coins\n" - " \"enoughcoins\": true|false, (boolean) if available coins are greater than reserve balance\n" - " \"mnsync\": true|false, (boolean) if masternode data is synced\n" - " \"staking status\": true|false, (boolean) if the wallet is staking or not\n" + " \"staking_status\": true|false, (boolean) whether the wallet is staking or not\n" + " \"staking_enabled\": true|false, (boolean) whether staking is enabled/disabled in pivx.conf\n" + " \"coldstaking_enabled\": true|false, (boolean) whether cold-staking is enabled/disabled in pivx.conf\n" + " \"haveconnections\": true|false, (boolean) whether network connections are present\n" + " \"mnsync\": true|false, (boolean) whether the required masternode/spork data is synced\n" + " \"walletunlocked\": true|false, (boolean) whether the wallet is unlocked\n" + " \"stakeablecoins\": n (numeric) number of stakeable UTXOs\n" + " \"stakingbalance\": d (numeric) DOGEC value of the stakeable coins (minus reserve balance, if any)\n" + " \"stakesplitthreshold\": d (numeric) value of the current threshold for stake split\n" + " \"lastattempt_age\": n (numeric) seconds since last stake attempt\n" + " \"lastattempt_depth\": n (numeric) depth of the block on top of which the last stake attempt was made\n" + " \"lastattempt_hash\": xxx (hex string) hash of the block on top of which the last stake attempt was made\n" + " \"lastattempt_coins\": n (numeric) number of stakeable coins available during last stake attempt\n" + " \"lastattempt_tries\": n (numeric) number of stakeable coins checked during last stake attempt\n" "}\n" "\nExamples:\n" + HelpExampleCli("getstakingstatus", "") + HelpExampleRpc("getstakingstatus", "")); -#ifdef ENABLE_WALLET - LOCK2(cs_main, pwalletMain ? &pwalletMain->cs_wallet : NULL); -#else - LOCK(cs_main); -#endif - UniValue obj(UniValue::VOBJ); - obj.push_back(Pair("validtime", chainActive.Tip()->nTime > 1471482000)); - obj.push_back(Pair("haveconnections", !vNodes.empty())); - if (pwalletMain) { + if (!pwalletMain) + throw JSONRPCError(RPC_IN_WARMUP, "Try again after active chain is loaded"); + { + LOCK2(cs_main, &pwalletMain->cs_wallet); + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("staking_status", pwalletMain->pStakerStatus->IsActive())); + obj.push_back(Pair("staking_enabled", GetBoolArg("-staking", true))); + bool fColdStaking = GetBoolArg("-coldstaking", true); + obj.push_back(Pair("coldstaking_enabled", fColdStaking)); + obj.push_back(Pair("haveconnections", !vNodes.empty())); + obj.push_back(Pair("mnsync", !masternodeSync.NotCompleted())); obj.push_back(Pair("walletunlocked", !pwalletMain->IsLocked())); - obj.push_back(Pair("mintablecoins", pwalletMain->MintableCoins())); - obj.push_back(Pair("enoughcoins", nReserveBalance <= pwalletMain->GetBalance())); + std::vector vCoins; + pwalletMain->StakeableCoins(&vCoins); + obj.push_back(Pair("stakeablecoins", (int)vCoins.size())); + obj.push_back(Pair("stakingbalance", ValueFromAmount(pwalletMain->GetStakingBalance(fColdStaking)))); + obj.push_back(Pair("stakesplitthreshold", ValueFromAmount(pwalletMain->nStakeSplitThreshold))); + CStakerStatus* ss = pwalletMain->pStakerStatus; + if (ss) { + obj.push_back(Pair("lastattempt_age", (int)(GetTime() - ss->GetLastTime()))); + obj.push_back(Pair("lastattempt_depth", (chainActive.Height() - ss->GetLastHeight()))); + obj.push_back(Pair("lastattempt_hash", ss->GetLastHash().GetHex())); + obj.push_back(Pair("lastattempt_coins", ss->GetLastCoins())); + obj.push_back(Pair("lastattempt_tries", ss->GetLastTries())); + } + return obj; } - obj.push_back(Pair("mnsync", masternodeSync.IsSynced())); - bool nStaking = false; - if (mapHashedBlocks.count(chainActive.Tip()->nHeight)) - nStaking = true; - else if (mapHashedBlocks.count(chainActive.Tip()->nHeight - 1) && nLastCoinStakeSearchInterval) - nStaking = true; - obj.push_back(Pair("staking status", getStakingStatus())); - return obj; } #endif // ENABLE_WALLET \ No newline at end of file diff --git a/src/stakeinput.cpp b/src/stakeinput.cpp index fd3f660889..e8e59e19a8 100644 --- a/src/stakeinput.cpp +++ b/src/stakeinput.cpp @@ -86,7 +86,7 @@ bool CzdogecStake::GetModifier(uint64_t& nStakeModifier) } int64_t nTimeBlockFrom = pindex->GetBlockTime(); - // zPIV staking is disabled long before block v7 (and checkpoint is not included in blocks since v7) + // zdogec staking is disabled long before block v7 (and checkpoint is not included in blocks since v7) // just return false for now. !TODO: refactor/remove this method while (pindex && pindex->nHeight + 1 <= std::min(chainActive.Height(), Params().Zerocoin_Block_Last_Checkpoint()-1)) { if (pindex->GetBlockTime() - nTimeBlockFrom > 60 * 60) { diff --git a/src/test/sync_tests.cpp b/src/test/sync_tests.cpp index 14f591bf94..5185d0f65d 100644 --- a/src/test/sync_tests.cpp +++ b/src/test/sync_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "sync.h" -#include "test/test_pivx.h" +#include "test/test_dogecash.h" #include diff --git a/src/txdb.cpp b/src/txdb.cpp index 0a6a9de5fd..a2a7d99b2f 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -271,10 +271,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts() if (!CheckProofOfWork(pindexNew->GetBlockHash(), pindexNew->nBits)) return error("LoadBlockIndex() : CheckProofOfWork failed: %s", pindexNew->ToString()); } - // ppcoin: build setStakeSeen - if (pindexNew->IsProofOfStake()) - setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); - + //populate accumulator checksum map in memory if(pindexNew->nAccumulatorCheckpoint != 0 && pindexNew->nAccumulatorCheckpoint != nPreviousCheckpoint) { //Don't load any checkpoints that exist before v2 zdogec. The accumulator is invalid for v1 and not used. diff --git a/src/uint256.h b/src/uint256.h index 2132a9ca70..68dd391449 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -411,4 +411,6 @@ inline uint512 uint512S(const std::string& str) return rv; } +const uint256 UINT256_ZERO = uint256(); + #endif // dogecash_UINT256_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7320764bb8..72cbe482aa 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2572,7 +2572,7 @@ bool CWallet::AvailableCoins(std::vector* pCoins, // --> populates continue; // Check min depth requirement for stake inputs - if (nCoinType == STAKABLE_COINS && nDepth < Params().COINSTAKE_MIN_DEPTH()) continue; + if (nCoinType == STAKEABLE_COINS && nDepth < Params().COINSTAKE_MIN_DEPTH()) continue; for (unsigned int i = 0; i < pcoin->vout.size(); i++) { bool found = false; @@ -2591,7 +2591,7 @@ bool CWallet::AvailableCoins(std::vector* pCoins, // --> populates } if (!found) continue; - if (nCoinType == STAKABLE_COINS) { + if (nCoinType == STAKEABLE_COINS) { if (pcoin->vout[i].IsZerocoinMint()) continue; } @@ -2735,7 +2735,7 @@ bool CWallet::SelectStakeCoins(std::list >& listInp nullptr, // coin control false, // fIncludeDelegated fIncludeCold, // fIncludeColdStaking - STAKABLE_COINS); // coin type + STAKEABLE_COINS); // coin type CAmount nAmountSelected = 0; if (GetBoolArg("-DOGECstake", true) && !fPrecompute) { for (const COutput &out : vCoins) { @@ -2834,7 +2834,7 @@ bool CWallet::MintableCoins() nullptr, // coin control false, // include zerovalue false, // use IX - STAKABLE_COINS, // coin type + STAKEABLE_COINS, // coin type 1, // watchonly config fIncludeCold, // fIncludeColdStaking true); // delegated @@ -2850,6 +2850,14 @@ bool CWallet::MintableCoins() return false; } +bool CWallet::StakeableCoins(std::vector* pCoins) +{ + const bool fIncludeCold = (sporkManager.IsSporkActive(SPORK_17_COLDSTAKING_ENFORCEMENT) && + GetBoolArg("-coldstaking", true)); + + return AvailableCoins(pCoins, nullptr, true, false, STAKEABLE_COINS, false, 1, fIncludeCold, false); +} + bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, vector vCoins, set >& setCoinsRet, CAmount& nValueRet) const { setCoinsRet.clear(); @@ -3226,7 +3234,7 @@ int CWallet::CountInputsWithAmount(CAmount nInputAmount) bool CWallet::HasCollateralInputs(bool fOnlyConfirmed) const { vector vCoins; - AvailableCoins(&vCoins, nullptr, false, false, STAKABLE_COINS, fOnlyConfirmed); + AvailableCoins(&vCoins, nullptr, false, false, STAKEABLE_COINS, fOnlyConfirmed); int nFound = 0; BOOST_FOREACH (const COutput& out, vCoins) @@ -3537,7 +3545,6 @@ bool CWallet::CreateCoinStake( const CKeyStore& keystore, const CBlockIndex* pindexPrev, unsigned int nBits, - int64_t nSearchInterval, CMutableTransaction& txNew, int64_t& nTxNewTime ) @@ -3572,98 +3579,90 @@ bool CWallet::CreateCoinStake( return false; } - if (GetAdjustedTime() - pindexPrev->GetBlockTime() < 60) { - if (Params().NetworkID() == CBaseChainParams::REGTEST) { - MilliSleep(1000); - } - } + // update staker status (hash) + pStakerStatus->SetLastTip(pindexPrev); + pStakerStatus->SetLastCoins(listInputs.size()); CAmount nCredit; CScript scriptPubKeyKernel; bool fKernelFound = false; int nAttempts = 0; + // update staker status (hash) + pStakerStatus->SetLastTip(pindexPrev); + for (std::unique_ptr& stakeInput : listInputs) { - nCredit = 0; + //new block came in, move on + if (chainActive.Height() != pindexPrev->nHeight) return false; // Make sure the wallet is unlocked and shutdown hasn't been requested - if (IsLocked() || ShutdownRequested()) - return false; + if (IsLocked() || ShutdownRequested()) return false; - uint256 hashProofOfStake = uint256(); + nCredit = 0; + uint256 hashProofOfStake = 0; nAttempts++; - //iterates each utxo inside of CheckStakeKernelHash() + fKernelFound = Stake(pindexPrev, stakeInput.get(), nBits, nTxNewTime, hashProofOfStake); - if (Stake(pindexPrev, stakeInput.get(), nBits, nTxNewTime, hashProofOfStake)) { + // update staker status (time, attempts) + pStakerStatus->SetLastTime(nTxNewTime); + pStakerStatus->SetLastTries(nAttempts); - // Found a kernel - LogPrintf("CreateCoinStake : kernel found\n"); - nCredit += stakeInput->GetValue(); + if (!fKernelFound) continue; - // Calculate reward - CAmount nReward; - nReward = GetBlockValue(pindexPrev->nHeight + 1); - nCredit += nReward; + // Found a kernel + LogPrintf("CreateCoinStake : kernel found\n"); + nCredit += stakeInput->GetValue(); - // Create the output transaction(s) - vector vout; - if (!stakeInput->CreateTxOuts(this, vout, nCredit)) { - LogPrintf("%s : failed to create output\n", __func__); - continue; - } - txNew.vout.insert(txNew.vout.end(), vout.begin(), vout.end()); + // Calculate reward + CAmount nReward; + nReward = GetBlockValue(chainActive.Height() + 1); + nCredit += nReward; - CAmount nMinFee = 0; + // Create the output transaction(s) + std::vector vout; + if (!stakeInput->CreateTxOuts(this, vout, nCredit)) { + LogPrintf("%s : failed to create output\n", __func__); + continue; + } + txNew.vout.insert(txNew.vout.end(), vout.begin(), vout.end()); + + CAmount nMinFee = 0; + // Set output amount + int outputs = txNew.vout.size() - 1; + CAmount nRemaining = nCredit - nMinFee; + if (outputs > 1) { + // Split the stake across the outputs + CAmount nShare = nRemaining / outputs; + for (int i = 1; i < outputs; i++) { + // loop through all but the last one. + txNew.vout[i].nValue = nShare; + nRemaining -= nShare; if (!stakeInput->Iszdogec()) { - // Set output amount - int outputs = txNew.vout.size() - 1; - CAmount nRemaining = nCredit - nMinFee; - if (outputs > 1) { - // Split the stake across the outputs - CAmount nShare = nRemaining / outputs; - for (int i = 1; i < outputs; i++) { - // loop through all but the last one. - txNew.vout[i].nValue = nShare; - nRemaining -= nShare; - } } - // put the remaining on the last output (which all into the first if only one output) - txNew.vout[outputs].nValue += nRemaining; } + // put the remaining on the last output (which all into the first if only one output) + txNew.vout[outputs].nValue += nRemaining; + } - // Limit size - unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION); - if (nBytes >= DEFAULT_BLOCK_MAX_SIZE / 5) - return error("CreateCoinStake : exceeded coinstake size limit"); - - //Masternode payment - FillBlockPayee(txNew, nMinFee, true, stakeInput->Iszdogec()); + // Limit size + unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION); + if (nBytes >= DEFAULT_BLOCK_MAX_SIZE / 5) + return error("CreateCoinStake : exceeded coinstake size limit"); - { - TRY_LOCK(zdogecTracker->cs_spendcache, fLocked); - if (!fLocked) - continue; + //Masternode payment + FillBlockPayee(txNew, nMinFee, true, stakeInput->Iszdogec()); - uint256 hashTxOut = txNew.GetHash(); - CTxIn in; - if (!stakeInput->CreateTxIn(this, in, hashTxOut)) { - LogPrintf("%s : failed to create TxIn\n", __func__); - txNew.vin.clear(); - txNew.vout.clear(); - continue; - } - txNew.vin.emplace_back(in); - } + uint256 hashTxOut = txNew.GetHash(); + CTxIn in; + if (!stakeInput->CreateTxIn(this, in, hashTxOut)) { + LogPrintf("%s : failed to create TxIn\n", __func__); + txNew.vin.clear(); + txNew.vout.clear(); + continue; + } + txNew.vin.emplace_back(in); - //Mark mints as spent - if (stakeInput->Iszdogec()) { - CzdogecStake* z = (CzdogecStake*)stakeInput.get(); - if (!z->MarkSpent(this, txNew.GetHash())) - return error("%s: failed to mark mint as used\n", __func__); - } - fKernelFound = true; - break; - } + break; } LogPrint("staking", "%s: attempted staking %d times\n", __func__, nAttempts); @@ -6330,6 +6329,7 @@ CWallet::CWallet(std::string strWalletFileIn) CWallet::~CWallet() { delete pwalletdbEncryption; + delete pStakerStatus; } void CWallet::SetNull() @@ -6347,6 +6347,11 @@ void CWallet::SetNull() fBackupMints = false; // Stake Settings + if (pStakerStatus) { + pStakerStatus->SetNull(); + } else { + pStakerStatus = new CStakerStatus(); + } nStakeSplitThreshold = STAKE_SPLIT_THRESHOLD; nStakeSetUpdateTime = 300; // 5 minutes diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 3a0478f609..2dcc8e333f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -95,7 +95,7 @@ enum AvailableCoinsType { ONLY_NOT10000IFMN = 3, ONLY_NONDENOMINATED_NOT10000IFMN = 4, // ONLY_NONDENOMINATED and not 10000 DOGEC at the same time ONLY_10000 = 5, // find masternode outputs including locked ones (use with caution) - STAKABLE_COINS = 6 // UTXO's that are valid for staking + STAKEABLE_COINS = 6 // UTXO's that are valid for staking }; // Possible states for zdogec send @@ -109,13 +109,13 @@ enum ZerocoinSpendStatus { zdogec_TRX_FUNDS_PROBLEMS = 6, // Everything related to available funds zdogec_TRX_CREATE = 7, // Everything related to create the transaction zdogec_TRX_CHANGE = 8, // Everything related to transaction change - zdogec_TXMINT_GENERAL = 9, // General errors in MintToTxIn + zdogec_TXMINT_GENERAL = 9, // General errors in MintsToInputVectorPublicSpend zdogec_INVALID_COIN = 10, // Selected mint coin is not valid zdogec_FAILED_ACCUMULATOR_INITIALIZATION = 11, // Failed to initialize witness zdogec_INVALID_WITNESS = 12, // Spend coin transaction did not verify zdogec_BAD_SERIALIZATION = 13, // Transaction verification failed zdogec_SPENT_USED_zdogec = 14, // Coin has already been spend - zdogec_TX_TOO_LARGE = 15, // The transaction is larger than the max tx size + zdogec_TX_TOO_LARGE = 15, // The transaction is larger than the max tx size zdogec_SPEND_V1_SEC_LEVEL // Spend is V1 and security level is not set to 100 }; @@ -165,6 +165,44 @@ class CKeyPool } }; +/** Record info about last stake attempt: + * - tipBlock index of the block on top of which last stake attempt was made + * - nTime time slot of last attempt + * - nTries number of UTXOs hashed during last attempt + * - nCoins number of stakeable utxos during last attempt +**/ +class CStakerStatus +{ +private: + const CBlockIndex* tipBlock{nullptr}; + int64_t nTime{0}; + int nTries{0}; + int nCoins{0}; + +public: + // Get + const CBlockIndex* GetLastTip() const { return tipBlock; } + uint256 GetLastHash() const { return (GetLastTip() == nullptr ? UINT256_ZERO : GetLastTip()->GetBlockHash()); } + int GetLastHeight() const { return (GetLastTip() == nullptr ? 0 : GetLastTip()->nHeight); } + int GetLastCoins() const { return nCoins; } + int GetLastTries() const { return nTries; } + int64_t GetLastTime() const { return nTime; } + // Set + void SetLastCoins(const int coins) { nCoins = coins; } + void SetLastTries(const int tries) { nTries = tries; } + void SetLastTip(const CBlockIndex* lastTip) { tipBlock = lastTip; } + void SetLastTime(const uint64_t lastTime) { nTime = lastTime; } + void SetNull() + { + SetLastCoins(0); + SetLastTries(0); + SetLastTip(nullptr); + SetLastTime(0); + } + // Check whether staking status is active (last attempt earlier than 30 seconds ago) + bool IsActive() const { return (nTime + 30) >= GetTime(); } +}; + /** * A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, * and provides the ability to create new transactions. @@ -311,6 +349,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface // Stake Settings uint64_t nStakeSplitThreshold; int nStakeSetUpdateTime; + CStakerStatus* pStakerStatus = nullptr; //MultiSend std::vector > vMultiSend; @@ -515,7 +554,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, std::string strCommand = "tx"); bool AddAccountingEntry(const CAccountingEntry&, CWalletDB & pwalletdb); int GenerateObfuscationOutputs(int nTotalValue, std::vector& vout); - bool CreateCoinStake(const CKeyStore& keystore, const CBlockIndex* pindexPrev, unsigned int nBits, int64_t nSearchInterval, CMutableTransaction& txNew, int64_t& nTxNewTime); + bool CreateCoinStake(const CKeyStore& keystore, const CBlockIndex* pindexPrev, unsigned int nBits, CMutableTransaction& txNew, int64_t& nTxNewTime); bool MultiSend(); void AutoCombineDust(); void AutoZeromint(); @@ -730,7 +769,7 @@ class CMerkleTx : public CTransaction void Init() { - hashBlock = 0; + hashBlock = UINT256_ZERO; nIndex = -1; } diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 8a567901aa..ecc7fc1c35 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -451,7 +451,426 @@ def _initialize_chain_clean(self): for i in range(self.num_nodes): initialize_datadir(self.options.tmpdir, i) -class ComparisonTestFramework(BitcoinTestFramework): + + ### PIVX Specific TestFramework ### + ################################### + def init_dummy_key(self): + self.DUMMY_KEY = CECKey() + self.DUMMY_KEY.set_secretbytes(hash256(pack(' 0: + assert_equal(self.nodes[i].getbestblockhash(), best_block) + + # balance is mature pow blocks rewards minus stake inputs (spent) + w_info = [self.nodes[i].getwalletinfo() for i in range(num_nodes)] + assert_equal(w_info[0]["balance"], DecimalAmt(250.0 * (62 - 20))) + assert_equal(w_info[1]["balance"], DecimalAmt(250.0 * (62 - 20))) + assert_equal(w_info[2]["balance"], DecimalAmt(250.0 * (56 - 20) - (used_utxos * 250) + zc_change)) + assert_equal(w_info[3]["balance"], DecimalAmt(250.0 * (50 - 20) - (used_utxos * 250) + zc_change)) + for i in range(4, num_nodes): + # only first 4 nodes have mined/staked + assert_equal(w_info[i]["balance"], DecimalAmt(0)) + + # immature balance is immature pow blocks rewards plus + # immature stakes (outputs=inputs+rewards) + assert_equal(w_info[0]["immature_balance"], DecimalAmt(500.0 * 20)) + assert_equal(w_info[1]["immature_balance"], DecimalAmt(500.0 * 20)) + assert_equal(w_info[2]["immature_balance"], DecimalAmt((250.0 * 6) + (500.0 * 20))) + assert_equal(w_info[3]["immature_balance"], DecimalAmt((250.0 * 14) + (500.0 * 20))) + for i in range(4, num_nodes): + # only first 4 nodes have mined/staked + assert_equal(w_info[i]["immature_balance"], DecimalAmt(0)) + + # check zerocoin balances / mints + for peer in [2, 3]: + if num_nodes > peer: + zcBalance = self.nodes[peer].getzerocoinbalance() + zclist = self.nodes[peer].listmintedzerocoins(True) + zclist_spendable = self.nodes[peer].listmintedzerocoins(True, True) + assert_equal(len(zclist), len(vZC_DENOMS)) + assert_equal(zcBalance['Total'], 6666) + assert_equal(zcBalance['Immature'], 0) + if peer == 2: + assert_equal(len(zclist), len(zclist_spendable)) + assert_equal(set([x['denomination'] for x in zclist]), set(vZC_DENOMS)) + assert_equal([x['confirmations'] for x in zclist], [30-peer] * len(vZC_DENOMS)) + + self.log.info("Balances of first %d nodes check out" % num_nodes) + + + def get_prevouts(self, node_id, utxo_list, zpos=False, nHeight=-1): + """ get prevouts (map) for each utxo in a list + :param node_id: (int) index of the CTestNode used as rpc connection. Must own the utxos. + utxo_list: (JSON list) utxos returned from listunspent used as input + (JSON list) mints returned from listmintedzerocoins used as input + zpos: (bool) type of utxo_list + nHeight: (int) height of the previous block. used only if zpos=True for + stake checksum. Optional, if not provided rpc_conn's height is used. + :return: prevouts: ({bytes --> (int, bytes, int)} dictionary) + maps CStake "uniqueness" (i.e. serialized COutPoint -or hash stake, for zdogec-) + to (amount, prevScript, timeBlockFrom). + For zdogec prevScript is replaced with serialHash hex string. + """ + assert_greater_than(len(self.nodes), node_id) + rpc_conn = self.nodes[node_id] + prevouts = {} + + for utxo in utxo_list: + if not zpos: + outPoint = COutPoint(int(utxo['txid'], 16), utxo['vout']) + outValue = int(utxo['amount']) * COIN + prevtx_json = rpc_conn.getrawtransaction(utxo['txid'], 1) + prevTx = CTransaction() + prevTx.deserialize(BytesIO(hex_str_to_bytes(prevtx_json['hex']))) + if (prevTx.is_coinbase() or prevTx.is_coinstake()) and utxo['confirmations'] < 100: + # skip immature coins + continue + prevScript = prevtx_json['vout'][utxo['vout']]['scriptPubKey']['hex'] + prevTime = prevtx_json['blocktime'] + prevouts[outPoint.serialize_uniqueness()] = (outValue, prevScript, prevTime) + + else: + uniqueness = bytes.fromhex(utxo['hash stake'])[::-1] + prevouts[uniqueness] = (int(utxo["denomination"]) * COIN, utxo["serial hash"], 0) + + return prevouts + + + def make_txes(self, node_id, spendingPrevOuts, to_pubKey): + """ makes a list of CTransactions each spending an input from spending PrevOuts to an output to_pubKey + :param node_id: (int) index of the CTestNode used as rpc connection. Must own spendingPrevOuts. + spendingPrevouts: ({bytes --> (int, bytes, int)} dictionary) + maps CStake "uniqueness" (i.e. serialized COutPoint -or hash stake, for zdogec-) + to (amount, prevScript, timeBlockFrom). + For zdogec prevScript is replaced with serialHash hex string. + to_pubKey (bytes) recipient public key + :return: block_txes: ([CTransaction] list) + """ + assert_greater_than(len(self.nodes), node_id) + rpc_conn = self.nodes[node_id] + block_txes = [] + for uniqueness in spendingPrevOuts: + if is_zerocoin(uniqueness): + # spend zdogec + _, serialHash, _ = spendingPrevOuts[uniqueness] + raw_spend = rpc_conn.createrawzerocoinspend(serialHash, "", False) + else: + # spend DOGEC + value_out = int(spendingPrevOuts[uniqueness][0] - DEFAULT_FEE * COIN) + scriptPubKey = CScript([to_pubKey, OP_CHECKSIG]) + prevout = COutPoint() + prevout.deserialize_uniqueness(BytesIO(uniqueness)) + tx = create_transaction_from_outpoint(prevout, b"", value_out, scriptPubKey) + # sign tx + raw_spend = rpc_conn.signrawtransaction(bytes_to_hex_str(tx.serialize()))['hex'] + # add signed tx to the list + signed_tx = CTransaction() + signed_tx.from_hex(raw_spend) + block_txes.append(signed_tx) + + return block_txes + + def stake_block(self, node_id, + nHeight, + prevHhash, + prevModifier, + stakeableUtxos, + startTime=None, + privKeyWIF=None, + vtx=[], + fDoubleSpend=False): + """ manually stakes a block selecting the coinstake input from a list of candidates + :param node_id: (int) index of the CTestNode used as rpc connection. Must own stakeableUtxos. + nHeight: (int) height of the block being produced + prevHash: (string) hex string of the previous block hash + prevModifier (string) hex string of the previous block stake modifier + stakeableUtxos: ({bytes --> (int, bytes, int)} dictionary) + maps CStake "uniqueness" (i.e. serialized COutPoint -or hash stake, for zdogec-) + to (amount, prevScript, timeBlockFrom). + For zdogec prevScript is replaced with serialHash hex string. + startTime: (int) epoch time to be used as blocktime (iterated in solve_stake) + privKeyWIF: (string) private key to be used for staking/signing + If empty string, it will be used the pk from the stake input + (dumping the sk from rpc_conn). If None, then the DUMMY_KEY will be used. + vtx: ([CTransaction] list) transactions to add to block.vtx + fDoubleSpend: (bool) wether any tx in vtx is allowed to spend the coinstake input + :return: block: (CBlock) block produced, must be manually relayed + """ + assert_greater_than(len(self.nodes), node_id) + rpc_conn = self.nodes[node_id] + if not len(stakeableUtxos) > 0: + raise Exception("Need at least one stakeable utxo to stake a block!") + # Get start time to stake + if startTime is None: + startTime = time.time() + # Create empty block with coinbase + nTime = int(startTime) & 0xfffffff0 + coinbaseTx = create_coinbase_pos(nHeight) + block = create_block(int(prevHhash, 16), coinbaseTx, nTime) + + # Find valid kernel hash - iterates stakeableUtxos, then block.nTime + block.solve_stake(stakeableUtxos, int(prevModifier, 16)) + + # Check if this is a zPoS block or regular/cold stake - sign stake tx + block_sig_key = CECKey() + isZPoS = is_zerocoin(block.prevoutStake) + if isZPoS: + # !TODO: remove me + raise Exception("zPOS tests discontinued") + + else: + coinstakeTx_unsigned = CTransaction() + prevout = COutPoint() + prevout.deserialize_uniqueness(BytesIO(block.prevoutStake)) + coinstakeTx_unsigned.vin.append(CTxIn(prevout, b"", 0xffffffff)) + coinstakeTx_unsigned.vout.append(CTxOut()) + amount, prevScript, _ = stakeableUtxos[block.prevoutStake] + outNValue = int(amount + 250 * COIN) + coinstakeTx_unsigned.vout.append(CTxOut(outNValue, hex_str_to_bytes(prevScript))) + if privKeyWIF == "": + # Use dummy key + if not hasattr(self, 'DUMMY_KEY'): + self.init_dummy_key() + block_sig_key = self.DUMMY_KEY + # replace coinstake output script + coinstakeTx_unsigned.vout[1].scriptPubKey = CScript([block_sig_key.get_pubkey(), OP_CHECKSIG]) + else: + if privKeyWIF == None: + # Use pk of the input. Ask sk from rpc_conn + rawtx = rpc_conn.getrawtransaction('{:064x}'.format(prevout.hash), True) + privKeyWIF = rpc_conn.dumpprivkey(rawtx["vout"][prevout.n]["scriptPubKey"]["addresses"][0]) + # Use the provided privKeyWIF (cold staking). + # export the corresponding private key to sign block + privKey, compressed = wif_to_privkey(privKeyWIF) + block_sig_key.set_compressed(compressed) + block_sig_key.set_secretbytes(bytes.fromhex(privKey)) + + # Sign coinstake TX and add it to the block + stake_tx_signed_raw_hex = rpc_conn.signrawtransaction( + bytes_to_hex_str(coinstakeTx_unsigned.serialize()))['hex'] + + # Add coinstake to the block + coinstakeTx = CTransaction() + coinstakeTx.from_hex(stake_tx_signed_raw_hex) + block.vtx.append(coinstakeTx) + + # Add provided transactions to the block. + # Don't add tx doublespending the coinstake input, unless fDoubleSpend=True + for tx in vtx: + if not fDoubleSpend: + # assume txes don't double spend zdogec inputs when fDoubleSpend is false. It needs to + # be checked outside until a convenient tx.spends(zerocoin) is added to the framework. + if not isZPoS and tx.spends(prevout): + continue + block.vtx.append(tx) + + # Get correct MerkleRoot and rehash block + block.hashMerkleRoot = block.calc_merkle_root() + block.rehash() + + # sign block with block signing key and return it + block.sign_block(block_sig_key) + return block + + + def stake_next_block(self, node_id, + stakeableUtxos, + btime=None, + privKeyWIF=None, + vtx=[], + fDoubleSpend=False): + """ Calls stake_block appending to the current tip""" + assert_greater_than(len(self.nodes), node_id) + nHeight = self.nodes[node_id].getblockcount() + prevHhash = self.nodes[node_id].getblockhash(nHeight) + prevModifier = self.nodes[node_id].getblock(prevHhash)['stakeModifier'] + return self.stake_block(node_id, + nHeight+1, + prevHhash, + prevModifier, + stakeableUtxos, + btime, + privKeyWIF, + vtx, + fDoubleSpend) + + + def check_tx_in_chain(self, node_id, txid): + assert_greater_than(len(self.nodes), node_id) + rawTx = self.nodes[node_id].getrawtransaction(txid, 1) + assert_greater_than(rawTx["confirmations"], 0) + + + def spend_inputs(self, node_id, inputs, outputs): + """ auxiliary function used by spend_utxo / spend_utxos """ + assert_greater_than(len(self.nodes), node_id) + rpc_conn = self.nodes[node_id] + spendingTx = rpc_conn.createrawtransaction(inputs, outputs) + spendingTx_signed = rpc_conn.signrawtransaction(spendingTx) + if spendingTx_signed["complete"]: + txhash = rpc_conn.sendrawtransaction(spendingTx_signed["hex"]) + return txhash + else: + return "" + + + def spend_utxo(self, node_id, utxo, recipient=''): + """ spend amount from previously unspent output to a provided address + :param node_id: (int) index of the CTestNode used as rpc connection. Must own the utxo. + utxo: (JSON) returned from listunspent used as input + recipient: (string) destination address (new one if not provided) + :return: txhash: (string) tx hash if successful, empty string otherwise + """ + assert_greater_than(len(self.nodes), node_id) + rpc_conn = self.nodes[node_id] + inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}] + out_amount = float(utxo["amount"]) - DEFAULT_FEE + outputs = {} + if recipient == '': + recipient = rpc_conn.getnewaddress() + outputs[recipient] = out_amount + return self.spend_inputs(node_id, inputs, outputs) + + + def spend_utxos(self, node_id, utxo_list, recipient='', fMultiple=False): + """ spend utxos to provided list of addresses or 10 new generate ones. + :param node_id: (int) index of the CTestNode used as rpc connection. Must own the utxo. + utxo_list: (JSON list) returned from listunspent used as input + recipient: (string, optional) destination address (new one if not provided) + fMultiple: (boolean, optional, default=false) spend each utxo on a different tx + :return: txHashes: (string list) list of hashes of completed txs + """ + assert_greater_than(len(self.nodes), node_id) + rpc_conn = self.nodes[node_id] + txHashes = [] + + # If no recipient is given, create a new one + if recipient == '': + recipient = rpc_conn.getnewaddress() + + # If fMultiple=True send one tx for each utxo + if fMultiple: + for utxo in utxo_list: + txHash = self.spend_utxo(node_id, utxo, recipient) + if txHash != "": + txHashes.append(txHash) + + # Otherwise make a single tx with all the inputs + else: + inputs = [{"txid": x["txid"], "vout": x["vout"]} for x in utxo_list] + out_amount = sum([float(x["amount"]) for x in utxo_list]) - DEFAULT_FEE + outputs = {} + if recipient == '': + recipient = rpc_conn.getnewaddress() + outputs[recipient] = out_amount + txHash = self.spend_inputs(node_id, inputs, outputs) + if txHash != "": + txHashes.append(txHash) + + return txHashes + + + def generate_pos(self, node_id, btime=None): + """ stakes a block using generate on nodes[node_id]""" + assert_greater_than(len(self.nodes), node_id) + rpc_conn = self.nodes[node_id] + ss = rpc_conn.getstakingstatus() + assert ss["walletunlocked"] + assert ss["stakeablecoins"] > 0 + assert ss["stakingbalance"] > 0.0 + if btime is not None: + next_btime = btime + 60 + fStaked = False + failures = 0 + while not fStaked: + try: + rpc_conn.generate(1) + fStaked = True + except JSONRPCException as e: + if ("Couldn't create new block" in str(e)): + failures += 1 + # couldn't generate block. check that this node can still stake (after 60 failures) + if failures > 60: + ss = rpc_conn.getstakingstatus() + if not (ss["walletunlocked"] and ss["stakeablecoins"] > 0 and ss["stakingbalance"] > 0.0): + raise AssertionError("Node %d unable to stake!" % node_id) + # try to stake one sec in the future + if btime is not None: + btime += 1 + set_node_times(self.nodes, btime) + else: + time.sleep(1) + else: + raise e + # block generated. adjust block time + if btime is not None: + btime = max(btime + 1, next_btime) + set_node_times(self.nodes, btime) + return btime + else: + return None + + + def generate_pow(self, node_id, btime=None): + """ stakes a block using generate on nodes[node_id]""" + assert_greater_than(len(self.nodes), node_id) + self.nodes[node_id].generate(1) + if btime is not None: + btime += 60 + set_node_times(self.nodes, btime) + return btime + + + def set_spork(self, node_id, sporkName, value): + assert_greater_than(len(self.nodes), node_id) + return self.nodes[node_id].spork(sporkName, value) + + + def get_spork(self, node_id, sporkName): + assert_greater_than(len(self.nodes), node_id) + return self.nodes[node_id].spork("show")[sporkName] + + + def activate_spork(self, node_id, sporkName): + return self.set_spork(node_id, sporkName, SPORK_ACTIVATION_TIME) + + + def deactivate_spork(self, node_id, sporkName): + return self.set_spork(node_id, sporkName, SPORK_DEACTIVATION_TIME) + + + def is_spork_active(self, node_id, sporkName): + assert_greater_than(len(self.nodes), node_id) + return self.nodes[node_id].spork("active")[sporkName] + + + +### ------------------------------------------------------ + +class ComparisonTestFramework(PivxTestFramework): """Test framework for doing p2p comparison testing Sets up some dogecashd binaries: diff --git a/test/functional/wallet_zerocoin_publicspends.py b/test/functional/wallet_zerocoin_publicspends.py index 64b80218e5..d6eb60a4c7 100755 --- a/test/functional/wallet_zerocoin_publicspends.py +++ b/test/functional/wallet_zerocoin_publicspends.py @@ -62,13 +62,13 @@ def run_test(self): def get_zerocoin_data(coin): return coin["s"], coin["r"], coin["k"], coin["id"], coin["d"], coin["t"] - def check_balances(denom, zpiv_bal, piv_bal): - zpiv_bal -= denom - assert_equal(self.nodes[2].getzerocoinbalance()['Total'], zpiv_bal) + def check_balances(denom, zdogec_bal, piv_bal): + zdogec_bal -= denom + assert_equal(self.nodes[2].getzerocoinbalance()['Total'], zdogec_bal) piv_bal += denom wi = self.nodes[2].getwalletinfo() assert_equal(wi['balance'] + wi['immature_balance'], piv_bal) - return zpiv_bal, piv_bal + return zdogec_bal, piv_bal def stake_4_blocks(block_time): for peer in range(2): @@ -84,9 +84,9 @@ def stake_4_blocks(block_time): # Start with cache balances wi = self.nodes[2].getwalletinfo() balance = wi['balance'] + wi['immature_balance'] - zpiv_balance = self.nodes[2].getzerocoinbalance()['Total'] + zdogec_balance = self.nodes[2].getzerocoinbalance()['Total'] assert_equal(balance, DecimalAmt(13833.92)) - assert_equal(zpiv_balance, 6666) + assert_equal(zdogec_balance, 6666) # Export zerocoin data listmints = self.nodes[2].listmintedzerocoins(True, True) @@ -134,7 +134,7 @@ def stake_4_blocks(block_time): # stake 4 blocks - check it gets included on chain and check balances block_time = stake_4_blocks(block_time) self.check_tx_in_chain(0, txid) - zpiv_balance, balance = check_balances(denom_2, zpiv_balance, balance) + zdogec_balance, balance = check_balances(denom_2, zdogec_balance, balance) self.log.info("--> VALID PUBLIC COIN SPEND (v3) PASSED") # 5) Check double spends - spend v3 @@ -153,7 +153,7 @@ def stake_4_blocks(block_time): # stake 4 blocks - check it gets included on chain and check balances block_time = stake_4_blocks(block_time) self.check_tx_in_chain(0, txid) - zpiv_balance, balance = check_balances(denom_3, zpiv_balance, balance) + zdogec_balance, balance = check_balances(denom_3, zdogec_balance, balance) self.log.info("--> VALID PUBLIC COIN SPEND (v4) PASSED") # 8) Check double spends - spend v4 @@ -163,7 +163,7 @@ def stake_4_blocks(block_time): # 9) Try to relay old v3 spend now (serial_1) self.log.info("Trying to send old v3 spend now...") - assert_raises_rpc_error(-26, "bad-txns-invalid-zpiv", + assert_raises_rpc_error(-26, "bad-txns-invalid-zdogec", self.nodes[2].sendrawtransaction, old_spend_v3) self.log.info("GOOD: Old transaction not sent.") @@ -182,7 +182,7 @@ def stake_4_blocks(block_time): self.check_tx_in_chain(0, txid) # need to reset spent mints since this was a raw broadcast self.nodes[2].resetmintzerocoin() - _, _ = check_balances(denom_1, zpiv_balance, balance) + _, _ = check_balances(denom_1, zdogec_balance, balance) self.log.info("--> VALID PUBLIC COIN SPEND (v3) PASSED")