From 84321331d01de7328dc4fe80f6a4f81109464960 Mon Sep 17 00:00:00 2001 From: FornaxA Date: Fri, 21 Feb 2020 13:34:21 +0100 Subject: [PATCH 1/4] Improve staking overhead and finish getstakingstatus - Move Stake() to stakeManager - No staking before masternode sync finished - Port and adapt PivX' coinstake search time based staking status reporting - Introduce sleep periods in the staking loop when staking is not available or effective - RPC call getstakingstatus returns true when all staking conditions are met --- src/pos/kernel.cpp | 46 -------------- src/pos/kernel.h | 1 - src/pos/staking-manager.cpp | 119 ++++++++++++++++++++++++++++++------ src/pos/staking-manager.h | 9 +++ src/wallet/rpcwallet.cpp | 33 +++++----- 5 files changed, 126 insertions(+), 82 deletions(-) diff --git a/src/pos/kernel.cpp b/src/pos/kernel.cpp index 30ebdedb16e3e..4ab9e27a09687 100644 --- a/src/pos/kernel.cpp +++ b/src/pos/kernel.cpp @@ -331,52 +331,6 @@ bool HasStakeMinAgeOrDepth(const int contextHeight, const uint32_t contextTime, return (contextHeight - utxoFromBlockHeight >= Params().GetConsensus().nStakeMinDepth); } -bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, unsigned int& nTimeTx, uint256& hashProofOfStake) -{ - int prevHeight = pindexPrev->nHeight; - - // get stake input pindex - CBlockIndex* pindexFrom = stakeInput->GetIndexFrom(); - if (!pindexFrom || pindexFrom->nHeight < 1) return error("%s : no pindexfrom", __func__); - - const uint32_t nTimeBlockFrom = pindexFrom->nTime; - const int nHeightBlockFrom = pindexFrom->nHeight; - - // check for maturity (min age/depth) requirements - if (!HasStakeMinAgeOrDepth(prevHeight + 1, nTimeTx, nHeightBlockFrom, nTimeBlockFrom)) - return error("%s : min age violation - height=%d - nTimeTx=%d, nTimeBlockFrom=%d, nHeightBlockFrom=%d", - __func__, prevHeight + 1, nTimeTx, nTimeBlockFrom, nHeightBlockFrom); - - // iterate the hashing - bool fSuccess = false; - const unsigned int nHashDrift = 60; - const unsigned int nFutureTimeDriftPoS = 180; - unsigned int nTryTime = nTimeTx - 1; - // iterate from nTimeTx up to nTimeTx + nHashDrift - // but not after the max allowed future blocktime drift (3 minutes for PoS) - const unsigned int maxTime = std::min(nTimeTx + nHashDrift, (uint32_t)GetAdjustedTime() + nFutureTimeDriftPoS); - - while (nTryTime < maxTime) - { - //new block came in, move on - if (chainActive.Height() != prevHeight) - break; - - ++nTryTime; - - // if stake hash does not meet the target then continue to next iteration - if (!CheckStakeKernelHash(pindexPrev, nBits, stakeInput, nTryTime, hashProofOfStake)) - continue; - - // if we made it this far, then we have successfully found a valid kernel hash - fSuccess = true; - nTimeTx = nTryTime; - break; - } - - return fSuccess; -} - bool ContextualCheckZerocoinStake(int nPreviousBlockHeight, CStakeInput* stake) { if (nPreviousBlockHeight < Params().GetConsensus().nBlockZerocoinV2) diff --git a/src/pos/kernel.h b/src/pos/kernel.h index 8e6e8d00fe545..b26ecdd60b687 100644 --- a/src/pos/kernel.h +++ b/src/pos/kernel.h @@ -21,7 +21,6 @@ static const int MODIFIER_INTERVAL_RATIO = 3; bool GetKernelStakeModifier(uint256 hashBlockFrom, uint64_t& nStakeModifier, int& nStakeModifierHeight, int64_t& nStakeModifierTime, bool fPrintProofOfStake); bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64_t& nStakeModifier, bool& fGeneratedStakeModifier); bool ComputeStakeModifierV2(CBlockIndex* pindex, const uint256& kernel); -bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, unsigned int& nTimeTx, uint256& hashProofOfStake); // Initialize the stake input object bool initStakeInput(const CBlock block, std::unique_ptr& stake, int nPreviousBlockHeight); diff --git a/src/pos/staking-manager.cpp b/src/pos/staking-manager.cpp index 526abe34c764e..1bf268588b6ae 100644 --- a/src/pos/staking-manager.cpp +++ b/src/pos/staking-manager.cpp @@ -5,6 +5,7 @@ #include "staking-manager.h" #include "init.h" +#include "masternode/masternode-sync.h" #include "miner.h" #include "net.h" #include "policy/policy.h" @@ -21,8 +22,9 @@ std::shared_ptr stakingManager; CStakingManager::CStakingManager(CWallet * const pwalletIn) : - nMintableLastCheck(0), fMintableCoins(false), nExtraNonce(0), // Currently unused - fEnableStaking(false), fEnableIONStaking(false), nReserveBalance(0), pwallet(pwalletIn) {} + nMintableLastCheck(0), fMintableCoins(false), fLastLoopOrphan(false), nExtraNonce(0), // Currently unused + fEnableStaking(false), fEnableIONStaking(false), nReserveBalance(0), pwallet(pwalletIn), + nHashInterval(22), nLastCoinStakeSearchInterval(0), nLastCoinStakeSearchTime(GetAdjustedTime()) {} bool CStakingManager::MintableCoins() { @@ -87,6 +89,54 @@ bool CStakingManager::SelectStakeCoins(std::list >& return true; } +bool CStakingManager::Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, unsigned int& nTimeTx, uint256& hashProofOfStake) +{ + int prevHeight = pindexPrev->nHeight; + + // get stake input pindex + CBlockIndex* pindexFrom = stakeInput->GetIndexFrom(); + if (!pindexFrom || pindexFrom->nHeight < 1) return error("%s : no pindexfrom", __func__); + + const uint32_t nTimeBlockFrom = pindexFrom->nTime; + const int nHeightBlockFrom = pindexFrom->nHeight; + + // check for maturity (min age/depth) requirements + if (!HasStakeMinAgeOrDepth(prevHeight + 1, nTimeTx, nHeightBlockFrom, nTimeBlockFrom)) + return error("%s : min age violation - height=%d - nTimeTx=%d, nTimeBlockFrom=%d, nHeightBlockFrom=%d", + __func__, prevHeight + 1, nTimeTx, nTimeBlockFrom, nHeightBlockFrom); + + // iterate the hashing + bool fSuccess = false; + const unsigned int nHashDrift = 60; + const unsigned int nFutureTimeDriftPoS = 180; + unsigned int nTryTime = nTimeTx - 1; + // iterate from nTimeTx up to nTimeTx + nHashDrift + // but not after the max allowed future blocktime drift (3 minutes for PoS) + const unsigned int maxTime = std::min(nTimeTx + nHashDrift, (uint32_t)GetAdjustedTime() + nFutureTimeDriftPoS); + + while (nTryTime < maxTime) + { + //new block came in, move on + if (chainActive.Height() != prevHeight) + break; + + ++nTryTime; + + // if stake hash does not meet the target then continue to next iteration + if (!CheckStakeKernelHash(pindexPrev, nBits, stakeInput, nTryTime, hashProofOfStake)) + continue; + + // if we made it this far, then we have successfully found a valid kernel hash + fSuccess = true; + nTimeTx = nTryTime; + break; + } + + mapHashedBlocks.clear(); + mapHashedBlocks[chainActive.Tip()->nHeight] = GetTime(); //store a time stamp of when we last hashed on this block + return fSuccess; +} + bool CStakingManager::CreateCoinStake(const CBlockIndex* pindexPrev, std::shared_ptr& coinstakeTx, std::shared_ptr& coinstakeInput) { // Needs wallet if (pwallet == nullptr || pindexPrev == nullptr) @@ -113,12 +163,6 @@ bool CStakingManager::CreateCoinStake(const CBlockIndex* pindexPrev, std::shared return false; } - if (listInputs.empty()) { - LogPrint(BCLog::STAKING, "CreateCoinStake(): listInputs empty\n"); -// MilliSleep(50000); - return false; - } - if (GetAdjustedTime() - chainActive.Tip()->GetBlockTime() < 60) { if (Params().NetworkIDString() == CBaseChainParams::REGTEST) { // MilliSleep(1000); @@ -190,6 +234,14 @@ bool CStakingManager::CreateCoinStake(const CBlockIndex* pindexPrev, std::shared return true; } +bool CStakingManager::IsStaking() { + bool nStaking = false; + if (mapHashedBlocks.count(chainActive.Tip()->nHeight)) + nStaking = true; + else if (mapHashedBlocks.count(chainActive.Tip()->nHeight - 1) && nLastCoinStakeSearchInterval) + nStaking = true; +} + void CStakingManager::UpdatedBlockTip(const CBlockIndex* pindex) { LOCK(cs); @@ -201,29 +253,55 @@ void CStakingManager::UpdatedBlockTip(const CBlockIndex* pindex) void CStakingManager::DoMaintenance(CConnman& connman) { - if (!fEnableStaking) return; - if (pwallet->IsLocked(true)) return; - - const Consensus::Params& params = Params().GetConsensus(); + if (!fEnableStaking) return; // Should never happen CBlockIndex* pindexPrev = chainActive.Tip(); - int nStakeHeight = pindexPrev->nHeight; - if (!pindexPrev) + bool fHaveConnections = !g_connman ? false : g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) > 0; + if (pwallet->IsLocked(true) || !pindexPrev || !masternodeSync.IsSynced() || !fHaveConnections || nReserveBalance >= pwallet->GetBalance()) { + nLastCoinStakeSearchInterval = 0; + MilliSleep(1 * 60 * 1000); // Wait 1 minute return; + } - // Check block height - const bool fPosPhase = (nStakeHeight >= params.POSStartHeight); - if (nStakeHeight < params.POSStartHeight) { + const int nStakeHeight = pindexPrev->nHeight + 1; + const Consensus::Params& params = Params().GetConsensus(); + const bool fPosPhase = (nStakeHeight >= params.POSStartHeight) || (nStakeHeight >= params.POSPOWStartHeight); + + if (!fPosPhase) { // no POS for at least 1 block + nLastCoinStakeSearchInterval = 0; + MilliSleep(1 * 60 * 1000); // Wait 1 minute return; } - //control the amount of times the client will check for mintable coins + //search our map of hashed blocks, see if bestblock has been hashed yet + if (mapHashedBlocks.count(chainActive.Tip()->nHeight) && !fLastLoopOrphan) { + // wait max 5 seconds if recently hashed + int nTimePast = GetTime() - mapHashedBlocks[chainActive.Tip()->nHeight]; + if (nTimePast < nHashInterval && nTimePast >= 0) { + MilliSleep(std::min(nHashInterval - nTimePast, (unsigned int)5) * 1000); + return; + } + } + fLastLoopOrphan = false; + + //control the amount of times the client will check for mintable coins if (!MintableCoins()) { // No mintable coins + nLastCoinStakeSearchInterval = 0; + LogPrint(BCLog::STAKING, "%s: No mintable coins, waiting..\n", __func__); + MilliSleep(5 * 60 * 1000); // Wait 5 minutes return; } + int64_t nSearchTime = GetAdjustedTime(); + if (nSearchTime < nLastCoinStakeSearchTime) { + MilliSleep((nLastCoinStakeSearchTime - nSearchTime) * 1000); // Wait + return; + } else { + nLastCoinStakeSearchInterval = nSearchTime - nLastCoinStakeSearchTime; + nLastCoinStakeSearchTime = nSearchTime; + } // Create new block std::shared_ptr coinstakeTxPtr = std::shared_ptr(new CMutableTransaction); std::shared_ptr coinstakeInputPtr = nullptr; @@ -233,7 +311,8 @@ void CStakingManager::DoMaintenance(CConnman& connman) try { pblocktemplate = BlockAssembler(Params()).CreateNewBlock(CScript(), coinstakeTxPtr, coinstakeInputPtr); } catch (const std::exception& e) { - LogPrint(BCLog::STAKING, "%s: error creating block - %s", __func__, e.what()); + LogPrint(BCLog::STAKING, "%s: error creating block, waiting.. - %s", __func__, e.what()); + MilliSleep(1 * 60 * 1000); // Wait 1 minute return; } } else { @@ -263,6 +342,8 @@ void CStakingManager::DoMaintenance(CConnman& connman) /// Process block std::shared_ptr shared_pblock = std::make_shared(*pblock); if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr)) { + fLastLoopOrphan = true; LogPrint(BCLog::STAKING, "%s: ProcessNewBlock, block not accepted", __func__); + MilliSleep(10 * 1000); // Wait 10 seconds } } diff --git a/src/pos/staking-manager.h b/src/pos/staking-manager.h index 8a481235305e4..66db3b2b8ac3b 100644 --- a/src/pos/staking-manager.h +++ b/src/pos/staking-manager.h @@ -17,6 +17,7 @@ class CMutableTransaction; class CStakeInput; class CStakingManager; class CWallet; +class uint256; extern std::shared_ptr stakingManager; @@ -29,9 +30,15 @@ class CStakingManager const CBlockIndex* tipIndex{nullptr}; CWallet* pwallet = nullptr; + std::map mapHashedBlocks; + int64_t nMintableLastCheck; bool fMintableCoins; + bool fLastLoopOrphan; + int64_t nLastCoinStakeSearchInterval; + int64_t nLastCoinStakeSearchTime; unsigned int nExtraNonce; + const unsigned int nHashInterval; public: CStakingManager(CWallet * const pwalletIn = nullptr); @@ -43,6 +50,8 @@ class CStakingManager bool MintableCoins(); bool SelectStakeCoins(std::list >& listInputs, CAmount nTargetAmount, int blockHeight); bool CreateCoinStake(const CBlockIndex* pindexPrev, std::shared_ptr& coinstakeTx, std::shared_ptr& coinstakeInput); + bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, unsigned int& nTimeTx, uint256& hashProofOfStake); + bool IsStaking(); void UpdatedBlockTip(const CBlockIndex* pindex); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index db784f5a3facc..bbedeae325cf4 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3371,24 +3371,25 @@ UniValue getstakingstatus(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); + bool fValidTime = chainActive.Tip()->nTime > 1471482000; + bool fHaveConnections = !g_connman ? false : g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) > 0; + bool fWalletUnlocked = !pwallet->IsLocked(); + bool fMintableCoins = stakingManager->MintableCoins(); + bool fEnoughCoins = stakingManager->nReserveBalance <= pwallet->GetBalance(); + bool fMnSync = masternodeSync.IsSynced(); + bool fStakingStatus = stakingManager->IsStaking(); + UniValue obj(UniValue::VOBJ); - obj.push_back(Pair("validtime", chainActive.Tip()->nTime > 1471482000)); - obj.push_back(Pair("haveconnections", !g_connman ? false : g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) > 0)); + obj.push_back(Pair("validtime", fValidTime)); + obj.push_back(Pair("haveconnections", fHaveConnections)); if (pwallet) { - obj.push_back(Pair("walletunlocked", !pwallet->IsLocked())); - obj.push_back(Pair("mintablecoins", stakingManager->MintableCoins())); - obj.push_back(Pair("enoughcoins", stakingManager->nReserveBalance <= pwallet->GetBalance())); - } - 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", nStaking)); -*/ + obj.push_back(Pair("walletunlocked", fWalletUnlocked)); + obj.push_back(Pair("mintablecoins", fMintableCoins)); + obj.push_back(Pair("enoughcoins", fEnoughCoins)); + } + obj.push_back(Pair("mnsync", fMnSync)); + obj.push_back(Pair("staking_status", fStakingStatus)); + return obj; } From 729a3678ffe5ebcd49690a065aa3c51459626551 Mon Sep 17 00:00:00 2001 From: FornaxA Date: Mon, 24 Feb 2020 15:03:23 +0100 Subject: [PATCH 2/4] Add coinstake flag to Coin and Coins --- src/coins.cpp | 3 ++- src/coins.h | 20 +++++++++++++------ src/rpc/blockchain.cpp | 7 ++++--- src/test/coins_tests.cpp | 43 ++++++++++++++++++++-------------------- src/txdb.cpp | 6 ++++-- src/txmempool.cpp | 2 +- src/undo.h | 5 +++-- src/validation.cpp | 4 +++- 8 files changed, 53 insertions(+), 37 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index c5b0f767c0563..ed470b7260f85 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -89,12 +89,13 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check) { bool fCoinbase = tx.IsCoinBase(); + bool fCoinstake = tx.IsCoinStake(); const uint256& txid = tx.GetHash(); for (size_t i = 0; i < tx.vout.size(); ++i) { bool overwrite = check ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase; // Always set the possible_overwrite flag to AddCoin for coinbase txn, in order to correctly // deal with the pre-BIP30 occurrences of duplicate coinbase transactions. - cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite); + cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase, fCoinstake), overwrite); } } diff --git a/src/coins.h b/src/coins.h index 9c2b1e381473e..dbada55888299 100644 --- a/src/coins.h +++ b/src/coins.h @@ -34,31 +34,38 @@ class Coin //! whether containing transaction was a coinbase unsigned int fCoinBase : 1; + //! whether containing transaction was a coinstake + unsigned int fCoinStake : 1; //! at which height this containing transaction was included in the active block chain - uint32_t nHeight : 31; + uint32_t nHeight : 30; //! construct a Coin from a CTxOut and height/coinbase information. - Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {} - Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {} + Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn, bool fCoinStakeIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), fCoinStake(fCoinStakeIn), nHeight(nHeightIn) {} + Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn, bool fCoinStakeIn) : out(outIn), fCoinBase(fCoinBaseIn), fCoinStake(fCoinStakeIn), nHeight(nHeightIn) {} void Clear() { out.SetNull(); fCoinBase = false; + fCoinStake = false; nHeight = 0; } //! empty constructor - Coin() : fCoinBase(false), nHeight(0) { } + Coin() : fCoinBase(false), fCoinStake(false), nHeight(0) { } bool IsCoinBase() const { return fCoinBase; } + bool IsCoinStake() const { + return fCoinStake; + } + template void Serialize(Stream &s) const { assert(!IsSpent()); - uint32_t code = nHeight * 2 + fCoinBase; + uint32_t code = nHeight * 4 + fCoinStake * 2 + fCoinBase; ::Serialize(s, VARINT(code)); ::Serialize(s, CTxOutCompressor(REF(out))); } @@ -67,7 +74,8 @@ class Coin void Unserialize(Stream &s) { uint32_t code = 0; ::Unserialize(s, VARINT(code)); - nHeight = code >> 1; + nHeight = code >> 2; + fCoinStake = (code >> 1) & 1; fCoinBase = code & 1; ::Unserialize(s, REF(CTxOutCompressor(out))); } diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index dd2f1d8112a04..61e5613a731d0 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1069,7 +1069,7 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, { assert(!outputs.empty()); ss << hash; - ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase); + ss << VARINT(outputs.begin()->second.nHeight * 4 + outputs.begin()->second.fCoinStake * 2 + outputs.begin()->second.fCoinBase); stats.nTransactions++; for (const auto output : outputs) { ss << VARINT(output.first + 1); @@ -1288,6 +1288,7 @@ UniValue gettxout(const JSONRPCRequest& request) ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true); ret.push_back(Pair("scriptPubKey", o)); ret.push_back(Pair("coinbase", (bool)coin.fCoinBase)); + ret.push_back(Pair("coinstake", (bool)coin.fCoinStake)); return ret; } @@ -1861,8 +1862,8 @@ static inline bool SetHasKeys(const std::set& set, const Tk& key, const Args& return (set.count(key) != 0) || SetHasKeys(set, args...); } -// outpoint (needed for the utxo index) + nHeight + fCoinBase -static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool); +// outpoint (needed for the utxo index) + nHeight + fCoinBase + fCoinStake +static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool) + sizeof(bool); static UniValue getblockstats(const JSONRPCRequest& request) { diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 26df62b54d7fd..52b76127240d5 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -26,6 +26,7 @@ bool operator==(const Coin &a, const Coin &b) { // Empty Coin objects are always equal. if (a.IsSpent() && b.IsSpent()) return true; return a.fCoinBase == b.fCoinBase && + a.fCoinStake == b.fCoinStake && a.nHeight == b.nHeight && a.out == b.out; } @@ -372,7 +373,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // Update the expected result to know about the new output coins assert(tx.vout.size() == 1); const COutPoint outpoint(tx.GetHash(), 0); - result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase()); + result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase(), CTransaction(tx).IsCoinStake()); // Call UpdateCoins on the top cache CTxUndo undo; @@ -707,7 +708,7 @@ BOOST_AUTO_TEST_CASE(ccoins_spend) CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY ); } -void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase) +void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase, bool coinstake) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); @@ -716,7 +717,7 @@ void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_va try { CTxOut output; output.nValue = modify_value; - test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase); + test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase, coinstake), coinbase); test.cache.SelfTest(); GetCoinsMapEntry(test.cache.map(), result_value, result_flags); } catch (std::logic_error& e) { @@ -750,24 +751,24 @@ BOOST_AUTO_TEST_CASE(ccoins_add) * Cache Write Result Cache Result potential_overwrite * Value Value Value Flags Flags */ - CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false); - CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true ); - CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false); - CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true ); - CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false); - CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true ); - CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false); - CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true ); - CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false); - CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true ); - CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false); - CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true ); - CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false); - CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true ); - CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false); - CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true ); - CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false); - CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true ); + CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false, false); + CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true , false); + CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false, false); + CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true , false); + CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false, false); + CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true , false); + CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false, false); + CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true , false); + CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false, false); + CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true , false); + CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false, false); + CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true , false); + CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false, false); + CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true , false); + CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false, false); + CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true , false); + CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false, false); + CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true , false); } void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags) diff --git a/src/txdb.cpp b/src/txdb.cpp index a47d97381ac05..7a0ff370bcf69 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -471,6 +471,7 @@ class CCoins public: //! whether transaction is a coinbase bool fCoinBase; + bool fCoinStake; //! unspent transaction outputs; spent outputs are .IsNull(); spent outputs at the end of the array are dropped std::vector vout; @@ -479,7 +480,7 @@ class CCoins int nHeight; //! empty constructor - CCoins() : fCoinBase(false), vout(0), nHeight(0) { } + CCoins() : fCoinBase(false), fCoinStake(false), vout(0), nHeight(0) { } template void Unserialize(Stream &s) { @@ -489,6 +490,7 @@ class CCoins ::Unserialize(s, VARINT(nVersionDummy)); // header code ::Unserialize(s, VARINT(nCode)); + fCoinStake = (nCode >> 1) & 1; fCoinBase = nCode & 1; std::vector vAvail(2, false); vAvail[0] = (nCode & 2) != 0; @@ -561,7 +563,7 @@ bool CCoinsViewDB::Upgrade() { COutPoint outpoint(key.second, 0); for (size_t i = 0; i < old_coins.vout.size(); ++i) { if (!old_coins.vout[i].IsNull() && !old_coins.vout[i].scriptPubKey.IsUnspendable()) { - Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight, old_coins.fCoinBase); + Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight, old_coins.fCoinBase, old_coins.fCoinStake); outpoint.n = i; CoinEntry entry(&outpoint); batch.Write(entry, newcoin); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index fd515238e9da8..1c3c79a493fa2 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1382,7 +1382,7 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { CTransactionRef ptx = mempool.get(outpoint.hash); if (ptx) { if (outpoint.n < ptx->vout.size()) { - coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false); + coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false, false); return true; } else { return false; diff --git a/src/undo.h b/src/undo.h index a83b701485d5a..4490311228779 100644 --- a/src/undo.h +++ b/src/undo.h @@ -25,7 +25,7 @@ class TxInUndoSerializer public: template void Serialize(Stream &s) const { - ::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1 : 0))); + ::Serialize(s, VARINT(txout->nHeight * 4 + (txout->fCoinStake ? 2 : 0) + (txout->fCoinBase ? 1 : 0))); if (txout->nHeight > 0) { // Required to maintain compatibility with older undo format. ::Serialize(s, (unsigned char)0); @@ -45,7 +45,8 @@ class TxInUndoDeserializer void Unserialize(Stream &s) { unsigned int nCode = 0; ::Unserialize(s, VARINT(nCode)); - txout->nHeight = nCode / 2; + txout->nHeight = nCode / 4; + txout->fCoinStake = (nCode >> 1) & 1; txout->fCoinBase = nCode & 1; if (txout->nHeight > 0) { // Old versions stored the version number for the last spend of diff --git a/src/validation.cpp b/src/validation.cpp index 91541fac598b9..249fb36e224c7 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1563,6 +1563,7 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out) if (!alternate.IsSpent()) { undo.nHeight = alternate.nHeight; undo.fCoinBase = alternate.fCoinBase; + undo.fCoinStake = alternate.fCoinStake; } else { return DISCONNECT_FAILED; // adding output for transaction without known metadata } @@ -1622,6 +1623,7 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* const CTransaction &tx = *(block.vtx[i]); uint256 hash = tx.GetHash(); bool is_coinbase = tx.IsCoinBase(); + bool is_coinstake = tx.IsCoinStake(); if (fAddressIndex) { @@ -1665,7 +1667,7 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* COutPoint out(hash, o); Coin coin; bool is_spent = view.SpendCoin(out, &coin); - if (!is_spent || tx.vout[o] != coin.out || pindex->nHeight != coin.nHeight || is_coinbase != coin.fCoinBase) { + if (!is_spent || tx.vout[o] != coin.out || pindex->nHeight != coin.nHeight || is_coinbase != coin.fCoinBase || is_coinstake != coin.fCoinStake) { fClean = false; // transaction output mismatch } } From fd77e63ddadb0be6a80b5cbe6c21c9cd68e1d6fe Mon Sep 17 00:00:00 2001 From: FornaxA Date: Mon, 24 Feb 2020 15:04:32 +0100 Subject: [PATCH 3/4] Add stake mint and masternode reward tx types to Qt transaction overview --- src/qt/transactiondesc.cpp | 10 +++++----- src/qt/transactiontablemodel.cpp | 11 +++++++++++ src/qt/transactionview.cpp | 2 ++ src/transactionrecord.cpp | 32 ++++++++++++++++++++++++++++++-- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index e58ae1a1a7bc1..610af07e4533e 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -92,11 +92,11 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco // // From // - if (wtx.IsCoinBase()) + if (wtx.IsCoinBase() || wtx.IsCoinStake()) { strHTML += "" + tr("Source") + ": " + tr("Generated") + "
"; } - else if (wtx.mapValue.count("from") && !wtx.mapValue["from"].empty()) + if (wtx.mapValue.count("from") && !wtx.mapValue["from"].empty()) { // Online transaction strHTML += "" + tr("From") + ": " + GUIUtil::HtmlEscape(wtx.mapValue["from"]) + "
"; @@ -143,7 +143,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco // // Amount // - if (wtx.IsCoinBase() && nCredit == 0) + if ((wtx.IsCoinBase() || wtx.IsCoinStake()) && nCredit == 0) { // // Coinbase @@ -280,9 +280,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco } } - if (wtx.IsCoinBase()) + if (wtx.IsCoinBase() || wtx.IsCoinStake()) { - quint32 numBlocksToMaturity = Consensus::Params().nCoinbaseMaturity + 1; + int numBlocksToMaturity = Consensus::Params().nCoinbaseMaturity; strHTML += "
" + tr("Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.").arg(QString::number(numBlocksToMaturity)) + "
"; } diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index ac8c64e4e781b..6b8a8d2e6d773 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -433,6 +433,11 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const case TransactionRecord::PrivateSend: return tr("PrivateSend"); + case TransactionRecord::MNReward: + return tr("Masternode Reward"); + case TransactionRecord::StakeMint: + return tr("ION Stake"); + default: return QString(); } @@ -443,6 +448,8 @@ QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx switch(wtx->type) { case TransactionRecord::Generated: + case TransactionRecord::StakeMint: + case TransactionRecord::MNReward: return QIcon(":/icons/tx_mined"); case TransactionRecord::RecvWithPrivateSend: case TransactionRecord::RecvWithAddress: @@ -473,6 +480,8 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, b case TransactionRecord::RecvWithPrivateSend: case TransactionRecord::SendToAddress: case TransactionRecord::Generated: + case TransactionRecord::StakeMint: + case TransactionRecord::MNReward: case TransactionRecord::PrivateSend: return formatAddressLabel(wtx->strAddress, QString::fromStdString(wtx->status.label), tooltip) + watchAddress; case TransactionRecord::SendToOther: @@ -491,6 +500,8 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: case TransactionRecord::Generated: + case TransactionRecord::StakeMint: + case TransactionRecord::MNReward: case TransactionRecord::PrivateSend: case TransactionRecord::RecvWithPrivateSend: { diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index d3e4457c29e3a..e94f8f5893a74 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -108,6 +108,8 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa typeWidget->addItem(tr("PrivateSend Collateral Payment"), TransactionFilterProxy::TYPE(TransactionRecord::PrivateSendCollateralPayment)); typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf)); typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); + typeWidget->addItem(tr("Minted"), TransactionFilterProxy::TYPE(TransactionRecord::StakeMint)); + typeWidget->addItem(tr("Masternode Reward"), TransactionFilterProxy::TYPE(TransactionRecord::MNReward)); typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); typeWidget->setCurrentIndex(settings.value("transactionType").toInt()); diff --git a/src/transactionrecord.cpp b/src/transactionrecord.cpp index a0cd7f371c213..5ad4ed91c89a9 100644 --- a/src/transactionrecord.cpp +++ b/src/transactionrecord.cpp @@ -38,7 +38,35 @@ std::vector TransactionRecord::decomposeTransaction(const CWa uint256 hash = wtx.GetHash(); std::map mapValue = wtx.mapValue; - if (nNet > 0 || wtx.IsCoinBase()) + if (wtx.tx->IsCoinStake()) { + TransactionRecord sub(hash, nTime); + CTxDestination address; + if (!ExtractDestination(wtx.tx->vout[1].scriptPubKey, address)) + return parts; + + if (isminetype mine = wallet->IsMine(wtx.tx->vout[1])) { + // ION stake reward + sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; + sub.type = TransactionRecord::StakeMint; + sub.strAddress = CBitcoinAddress(address).ToString(); + sub.credit = nNet; + } else { + //Masternode reward + CTxDestination destMN; + int nIndexMN = wtx.tx->vout.size() - 1; + if (ExtractDestination(wtx.tx->vout[nIndexMN].scriptPubKey, destMN) && IsMine(*wallet, destMN)) { + isminetype mine = wallet->IsMine(wtx.tx->vout[nIndexMN]); + sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; + sub.type = TransactionRecord::MNReward; + sub.strAddress = CBitcoinAddress(destMN).ToString(); + sub.credit = wtx.tx->vout[nIndexMN].nValue; + } + } + + sub.address.SetString(sub.strAddress); + sub.txDest = sub.address.Get(); + parts.push_back(sub); + } else if (nNet > 0 || wtx.IsCoinBase()) { // // Credit @@ -300,7 +328,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx, int chainLockHeight) } } // For generated transactions, determine maturity - else if(type == TransactionRecord::Generated) + else if(type == TransactionRecord::Generated || type == TransactionRecord::StakeMint || type == TransactionRecord::MNReward) { if (wtx.GetBlocksToMaturity() > 0) { From 7eab626365be2039a5830c26398d3fc97ab47cf8 Mon Sep 17 00:00:00 2001 From: FornaxA Date: Mon, 24 Feb 2020 15:05:08 +0100 Subject: [PATCH 4/4] Update/add maturity checks for coinstake and group configuration transactions --- src/consensus/tx_verify.cpp | 17 ++++++++++++++++- src/wallet/wallet.cpp | 8 ++++++-- src/wallet/wallet.h | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 6911cccc5e790..737ad568713ed 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -7,6 +7,7 @@ #include "consensus.h" #include "primitives/transaction.h" #include "script/interpreter.h" +#include "tokens/groups.h" #include "validation.h" // TODO remove the following dependencies @@ -228,13 +229,27 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c const Coin& coin = inputs.AccessCoin(prevout); assert(!coin.IsSpent()); - // If prev is coinbase, check that it's matured + // If prev is coinbase, coinstake or group authority confirguration, check that it's matured + if (coin.IsCoinStake() && nSpendHeight - coin.nHeight < (nSpendHeight <= 100 ? (int)10 : Params().nCoinbaseMaturity)) { + return state.Invalid(false, + REJECT_INVALID, "bad-txns-premature-spend-of-coinstake", + strprintf("tried to spend coinstake at depth %d", nSpendHeight - coin.nHeight)); + } + if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < (nSpendHeight <= 100 ? (int)10 : Params().nCoinbaseMaturity)) { return state.Invalid(false, REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); } + if (IsOutputGroupedAuthority(coin.out)) { + if (nSpendHeight - coin.nHeight < Params().nOpGroupNewRequiredConfirmations) { + return state.Invalid( + error("CheckInputs() : tried to use a token authority before it reached maturity (%d confirmations)", Params().nOpGroupNewRequiredConfirmations), + REJECT_INVALID, "bad-txns-premature-use-of-token-authority"); + } + } + // Check for negative or overflow input values nValueIn += coin.out.nValue; if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b10f5c9ecf43d..a270a8747de06 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -6066,9 +6066,13 @@ bool CMerkleTx::IsChainLocked() const int CMerkleTx::GetBlocksToMaturity() const { - if (!IsCoinBase()) + if (!(IsCoinBase() || IsCoinStake() || IsAnyOutputGroupedAuthority((CTransaction(*this))))) return 0; - return std::max(0, (Consensus::Params().nCoinbaseMaturity+1) - GetDepthInMainChain()); + int depth = GetDepthInMainChain(); + int minBlocksToMaturity = 0; + if (IsAnyOutputGroupedAuthority((CTransaction(*this)))) + minBlocksToMaturity = std::max(0, (Consensus::Params().nOpGroupNewRequiredConfirmations + 1) - depth); + return std::max(minBlocksToMaturity, (Consensus::Params().nCoinbaseMaturity + 1) - depth); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 7676622db539d..41649da866ace 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -296,6 +296,7 @@ class CMerkleTx const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } + bool IsCoinStake() const { return tx->IsCoinStake(); } }; /**