From 0fb6ca1085f15e86b64cc9cc9bacb22bc405e7c9 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 4 Jul 2019 21:59:31 +0200 Subject: [PATCH 01/59] [Core] Define STAKING_ADDRESS addresses --- src/base58.cpp | 38 +++++++++++++++++++++++++++----------- src/base58.h | 9 +++++---- src/chainparams.cpp | 2 ++ src/chainparams.h | 1 + 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/base58.cpp b/src/base58.cpp index da669fc73773..c9e42ae621b9 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -224,32 +224,39 @@ class CBitcoinAddressVisitor : public boost::static_visitor { private: CBitcoinAddress* addr; + bool IsColdStakingAddr = false; public: - CBitcoinAddressVisitor(CBitcoinAddress* addrIn) : addr(addrIn) {} + CBitcoinAddressVisitor(CBitcoinAddress* addrIn, const bool fStakingAddr = false) : + addr(addrIn), + IsColdStakingAddr(fStakingAddr){}; - bool operator()(const CKeyID& id) const { return addr->Set(id); } - bool operator()(const CScriptID& id) const { return addr->Set(id); } + bool operator()(const CKeyID& id) const { return addr->Set(id, IsColdStakingAddr); } + bool operator()(const CScriptID& id) const { return addr->Set(id, IsColdStakingAddr); } bool operator()(const CNoDestination& no) const { return false; } }; } // anon namespace -bool CBitcoinAddress::Set(const CKeyID& id) +bool CBitcoinAddress::Set(const CKeyID& id, const bool fStakingAddr) { - SetData(Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS), &id, 20); + if (fStakingAddr) { + SetData(Params().Base58Prefix(CChainParams::STAKING_ADDRESS), &id, 20); + } else { + SetData(Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS), &id, 20); + } return true; } -bool CBitcoinAddress::Set(const CScriptID& id) +bool CBitcoinAddress::Set(const CScriptID& id, const bool fStakingAddr) { SetData(Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS), &id, 20); return true; } -bool CBitcoinAddress::Set(const CTxDestination& dest) +bool CBitcoinAddress::Set(const CTxDestination& dest, const bool fStakingAddr) { - return boost::apply_visitor(CBitcoinAddressVisitor(this), dest); + return boost::apply_visitor(CBitcoinAddressVisitor(this, fStakingAddr), dest); } bool CBitcoinAddress::IsValid() const @@ -261,7 +268,8 @@ bool CBitcoinAddress::IsValid(const CChainParams& params) const { bool fCorrectSize = vchData.size() == 20; bool fKnownVersion = vchVersion == params.Base58Prefix(CChainParams::PUBKEY_ADDRESS) || - vchVersion == params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); + vchVersion == params.Base58Prefix(CChainParams::SCRIPT_ADDRESS) || + vchVersion == params.Base58Prefix(CChainParams::STAKING_ADDRESS); return fCorrectSize && fKnownVersion; } @@ -271,7 +279,8 @@ CTxDestination CBitcoinAddress::Get() const return CNoDestination(); uint160 id; memcpy(&id, &vchData[0], 20); - if (vchVersion == Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS)) + if (vchVersion == Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS) || + vchVersion == Params().Base58Prefix(CChainParams::STAKING_ADDRESS)) return CKeyID(id); else if (vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS)) return CScriptID(id); @@ -281,7 +290,9 @@ CTxDestination CBitcoinAddress::Get() const bool CBitcoinAddress::GetKeyID(CKeyID& keyID) const { - if (!IsValid() || vchVersion != Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS)) + if (!IsValid() || + (vchVersion != Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS) && + vchVersion != Params().Base58Prefix(CChainParams::STAKING_ADDRESS))) return false; uint160 id; memcpy(&id, &vchData[0], 20); @@ -294,6 +305,11 @@ bool CBitcoinAddress::IsScript() const return IsValid() && vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS); } +bool CBitcoinAddress::IsStakingAddress() const +{ + return IsValid() && vchVersion == Params().Base58Prefix(CChainParams::STAKING_ADDRESS); +} + void CBitcoinSecret::SetKey(const CKey& vchSecret) { assert(vchSecret.IsValid()); diff --git a/src/base58.h b/src/base58.h index 150f0d544402..7dc03d063d7a 100644 --- a/src/base58.h +++ b/src/base58.h @@ -110,20 +110,21 @@ class CBase58Data class CBitcoinAddress : public CBase58Data { public: - bool Set(const CKeyID& id); - bool Set(const CScriptID& id); - bool Set(const CTxDestination& dest); + bool Set(const CKeyID& id, const bool fStakingAddr = false); + bool Set(const CScriptID& id, const bool fStakingAddr = false); + bool Set(const CTxDestination& dest, const bool fStakingAddr = false); bool IsValid() const; bool IsValid(const CChainParams& params) const; CBitcoinAddress() {} - CBitcoinAddress(const CTxDestination& dest) { Set(dest); } + CBitcoinAddress(const CTxDestination& dest, const bool fStakingAddr = false) { Set(dest, fStakingAddr); } CBitcoinAddress(const std::string& strAddress) { SetString(strAddress); } CBitcoinAddress(const char* pszAddress) { SetString(pszAddress); } CTxDestination Get() const; bool GetKeyID(CKeyID& keyID) const; bool IsScript() const; + bool IsStakingAddress() const; }; /** diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b850c634ad2e..f858ade4c9c6 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -233,6 +233,7 @@ class CMainParams : public CChainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 30); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 13); + base58Prefixes[STAKING_ADDRESS] = std::vector(1, 63); // starting with 'S' base58Prefixes[SECRET_KEY] = std::vector(1, 212); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x02)(0x2D)(0x25)(0x33).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x02)(0x21)(0x31)(0x2B).convert_to_container >(); @@ -352,6 +353,7 @@ class CTestNetParams : public CMainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 139); // Testnet pivx addresses start with 'x' or 'y' base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 19); // Testnet pivx script addresses start with '8' or '9' + base58Prefixes[STAKING_ADDRESS] = std::vector(1, 73); // starting with 'W' base58Prefixes[SECRET_KEY] = std::vector(1, 239); // Testnet private keys start with '9' or 'c' (Bitcoin defaults) // Testnet pivx BIP32 pubkeys start with 'DRKV' base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x3a)(0x80)(0x61)(0xa0).convert_to_container >(); diff --git a/src/chainparams.h b/src/chainparams.h index 5a45e57c7335..b19c0343129b 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -41,6 +41,7 @@ class CChainParams EXT_PUBLIC_KEY, // BIP32 EXT_SECRET_KEY, // BIP32 EXT_COIN_TYPE, // BIP44 + STAKING_ADDRESS, MAX_BASE58_TYPES }; From 6ec51f4693c4cbac670d18fde84ab30408a5aa25 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 4 Jul 2019 22:04:14 +0200 Subject: [PATCH 02/59] [Script] Define OP_CHECKCOLDSTAKEVERIFY opcode behaviour --- src/primitives/transaction.cpp | 26 ++++++++++++++++++++++++++ src/primitives/transaction.h | 1 + src/script/interpreter.cpp | 9 +++++++++ src/script/interpreter.h | 8 ++++++++ src/script/script.cpp | 27 +++++++++++++++------------ src/script/script.h | 3 +++ src/script/script_error.cpp | 2 ++ src/script/script_error.h | 1 + src/script/standard.h | 4 ++-- 9 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 96fc5db02116..218ad72fb3bc 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -209,6 +209,32 @@ bool CTransaction::IsCoinStake() const return (vout.size() >= 2 && vout[0].IsEmpty()); } +bool CTransaction::CheckColdStake() const +{ + // tx is a coinstake tx + if (!IsCoinStake()) + return false; + + // all inputs have the same pubKeyScript + CScript firstScript = vin[0].scriptSig; + if (vin.size() > 1) { + for (unsigned int i=1; i 3) { + for (unsigned int i=1; i >& stack, const CScript& } break; + case OP_CHECKCOLDSTAKEVERIFY: + { + // check it is used in a valid cold stake transaction. + if(!checker.CheckColdStake()) { + return set_error(serror, SCRIPT_ERR_CHECKCOLDSTAKEVERIFY); + } + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 800ee1801469..f4a49fd9e2a6 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -98,6 +98,11 @@ class BaseSignatureChecker return false; } + virtual bool CheckColdStake() const + { + return false; + } + virtual ~BaseSignatureChecker() {} }; @@ -114,6 +119,9 @@ class TransactionSignatureChecker : public BaseSignatureChecker TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn) : txTo(txToIn), nIn(nInIn) {} bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode) const; bool CheckLockTime(const CScriptNum& nLockTime) const; + bool CheckColdStake() const override { + return txTo->CheckColdStake(); + } }; class MutableTransactionSignatureChecker : public TransactionSignatureChecker diff --git a/src/script/script.cpp b/src/script/script.cpp index 0fc8178ca0ce..ad1c854cb186 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -138,22 +138,25 @@ const char* GetOpName(opcodetype opcode) case OP_CHECKMULTISIG : return "OP_CHECKMULTISIG"; case OP_CHECKMULTISIGVERIFY : return "OP_CHECKMULTISIGVERIFY"; - // expanson - case OP_NOP1 : return "OP_NOP1"; - case OP_NOP2 : return "OP_NOP2"; - case OP_NOP3 : return "OP_NOP3"; - case OP_NOP4 : return "OP_NOP4"; - case OP_NOP5 : return "OP_NOP5"; - case OP_NOP6 : return "OP_NOP6"; - case OP_NOP7 : return "OP_NOP7"; - case OP_NOP8 : return "OP_NOP8"; - case OP_NOP9 : return "OP_NOP9"; - case OP_NOP10 : return "OP_NOP10"; + // expansion + case OP_NOP1 : return "OP_NOP1"; // OP_NOP1 + case OP_CHECKLOCKTIMEVERIFY : return "OP_CHECKLOCKTIMEVERIFY"; // OP_NOP2 + case OP_NOP3 : return "OP_NOP3"; // OP_NOP3 + case OP_NOP4 : return "OP_NOP4"; // OP_NOP4 + case OP_NOP5 : return "OP_NOP5"; // OP_NOP5 + case OP_NOP6 : return "OP_NOP6"; // OP_NOP6 + case OP_NOP7 : return "OP_NOP7"; // OP_NOP7 + case OP_NOP8 : return "OP_NOP8"; // OP_NOP8 + case OP_NOP9 : return "OP_NOP9"; // OP_NOP9 + case OP_NOP10 : return "OP_NOP10"; // OP_NOP10 // zerocoin case OP_ZEROCOINMINT : return "OP_ZEROCOINMINT"; case OP_ZEROCOINSPEND : return "OP_ZEROCOINSPEND"; - case OP_ZEROCOINPUBLICSPEND : return "OP_ZEROCOINPUBLICSPEND"; + case OP_ZEROCOINPUBLICSPEND : return "OP_ZEROCOINPUBLICSPEND"; + + // cold staking + case OP_CHECKCOLDSTAKEVERIFY : return "OP_CHECKCOLDSTAKEVERIFY"; case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; diff --git a/src/script/script.h b/src/script/script.h index b826c2e9dea8..ba70acedbc05 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -176,6 +176,9 @@ enum opcodetype OP_ZEROCOINSPEND = 0xc2, OP_ZEROCOINPUBLICSPEND = 0xc3, + // cold staking + OP_CHECKCOLDSTAKEVERIFY = 0xd1, + // template matching params OP_SMALLINTEGER = 0xfa, OP_PUBKEYS = 0xfb, diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index 788d7ff4689b..02575e77a534 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -17,6 +17,8 @@ const char* ScriptErrorString(const ScriptError serror) return "Script failed an OP_VERIFY operation"; case SCRIPT_ERR_EQUALVERIFY: return "Script failed an OP_EQUALVERIFY operation"; + case SCRIPT_ERR_CHECKCOLDSTAKEVERIFY: + return "Script failed an OP_CHECKCOLDSTAKEVERIFY operation"; case SCRIPT_ERR_CHECKMULTISIGVERIFY: return "Script failed an OP_CHECKMULTISIGVERIFY operation"; case SCRIPT_ERR_CHECKSIGVERIFY: diff --git a/src/script/script_error.h b/src/script/script_error.h index 7b4c40edaaca..ec5bd9579e7a 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -24,6 +24,7 @@ typedef enum ScriptError_t /* Failed verify operations */ SCRIPT_ERR_VERIFY, SCRIPT_ERR_EQUALVERIFY, + SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, SCRIPT_ERR_CHECKMULTISIGVERIFY, SCRIPT_ERR_CHECKSIGVERIFY, SCRIPT_ERR_NUMEQUALVERIFY, diff --git a/src/script/standard.h b/src/script/standard.h index 9970ded44528..e4f2da8ce315 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -34,7 +34,7 @@ extern unsigned nMaxDatacarrierBytes; * them to be valid. (but old blocks may not comply with) Currently just P2SH, * but in the future other flags may be added, such as a soft-fork to enforce * strict DER encoding. - * + * * Failing one of these tests may trigger a DoS ban - see CheckInputs() for * details. */ @@ -73,7 +73,7 @@ class CNoDestination { friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; } }; -/** +/** * A txout script template with a specific destination. It is either: * * CNoDestination: no destination set * * CKeyID: TX_PUBKEYHASH destination From c7d19342c6431e6ee9c7f608622c1ffff4678a4e Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 4 Jul 2019 22:06:08 +0200 Subject: [PATCH 03/59] [Core] define TX_COLDSTAKE standard and P2CS script --- src/script/standard.cpp | 32 ++++++++++++++++++++++++++++---- src/script/standard.h | 2 ++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 4b4239696af2..8d6e2a2a2a7b 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -11,8 +11,6 @@ #include "util.h" #include "utilstrencodings.h" - - typedef std::vector valtype; unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY; @@ -28,6 +26,7 @@ const char* GetTxnOutputType(txnouttype t) case TX_PUBKEYHASH: return "pubkeyhash"; case TX_SCRIPTHASH: return "scripthash"; case TX_MULTISIG: return "multisig"; + case TX_COLDSTAKE: return "coldstake"; case TX_NULL_DATA: return "nulldata"; case TX_ZEROCOINMINT: return "zerocoinmint"; } @@ -51,6 +50,10 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& keys) { CScript script; diff --git a/src/script/standard.h b/src/script/standard.h index e4f2da8ce315..75bb35819502 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -65,6 +65,7 @@ enum txnouttype TX_MULTISIG, TX_NULL_DATA, TX_ZEROCOINMINT, + TX_COLDSTAKE }; class CNoDestination { @@ -92,5 +93,6 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std:: CScript GetScriptForDestination(const CTxDestination& dest); CScript GetScriptForMultisig(int nRequired, const std::vector& keys); +CScript GetScriptForStakeDelegation(const CKeyID& stakingKey, const CKeyID& spendingKey); #endif // BITCOIN_SCRIPT_STANDARD_H From f6bba2352d9a17eb80ac90f78ff0f80c5925d6d5 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 5 Jul 2019 02:36:35 +0200 Subject: [PATCH 04/59] [Wallet] Get ColdStaking/Delegated balance - Define ismine types for TX_COLDSTAKE txnouttype: ISMINE_COLD / ISMINE_SPENDABLE_DELEGATED - CWalletTx: Add cached Credit/Debit for new balance types - CWalletTx: Get ColdStaking/Delegated available credit - CWallet: Add flags to filter AvailableCoins - CWallet: Get ColdStaking/Delegated balances --- src/rpc/misc.cpp | 2 +- src/wallet/wallet.cpp | 157 +++++++++++++++++++++++++++++++---- src/wallet/wallet.h | 20 ++++- src/wallet/wallet_ismine.cpp | 14 ++++ src/wallet/wallet_ismine.h | 8 +- 5 files changed, 180 insertions(+), 21 deletions(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index c0846e73a6b6..e16ad425b3b1 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -256,7 +256,7 @@ class DescribeAddressVisitor : public boost::static_visitor UniValue obj(UniValue::VOBJ); CPubKey vchPubKey; obj.push_back(Pair("isscript", false)); - if (mine == ISMINE_SPENDABLE) { + if (bool(mine & ISMINE_ALL)) { pwalletMain->GetPubKey(keyID, vchPubKey); obj.push_back(Pair("pubkey", HexStr(vchPubKey))); obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed())); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0d1646f11c57..0d134a7e8475 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1108,6 +1108,24 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const debit += nWatchDebitCached; } } + if (filter & ISMINE_COLD) { + if (fColdDebitCached) + debit += nColdDebitCached; + else { + nColdDebitCached = pwallet->GetDebit(*this, ISMINE_COLD); + fColdDebitCached = true; + debit += nColdDebitCached; + } + } + if (filter & ISMINE_SPENDABLE_DELEGATED) { + if (fDelegatedDebitCached) + debit += nDelegatedDebitCached; + else { + nDelegatedDebitCached = pwallet->GetDebit(*this, ISMINE_SPENDABLE_DELEGATED); + fDelegatedDebitCached = true; + debit += nDelegatedDebitCached; + } + } return debit; } @@ -1137,6 +1155,24 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const credit += nWatchCreditCached; } } + if (filter & ISMINE_COLD) { + if (fColdCreditCached) + credit += nColdCreditCached; + else { + nColdCreditCached = pwallet->GetCredit(*this, ISMINE_COLD); + fColdCreditCached = true; + credit += nColdCreditCached; + } + } + if (filter & ISMINE_SPENDABLE_DELEGATED) { + if (fDelegatedCreditCached) + credit += nDelegatedCreditCached; + else { + nDelegatedCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE_DELEGATED); + fDelegatedCreditCached = true; + credit += nDelegatedCreditCached; + } + } return credit; } @@ -1154,6 +1190,22 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const return 0; } +// Helper function for GetAvailableCredit / GetColdStakingCredit / GetStakeDelegationCredit +CAmount CWalletTx::CreditFor(const isminetype& minetype) const +{ + const uint256 hashTx = GetHash(); + CAmount nCredit = 0; + for (unsigned int i = 0; i < vout.size(); i++) { + if (!pwallet->IsSpent(hashTx, i)) { + const CTxOut& txout = vout[i]; + nCredit += pwallet->GetCredit(txout, minetype); + if (!MoneyRange(nCredit)) + throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range"); + } + } + return nCredit; +} + CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const { if (pwallet == 0) @@ -1166,22 +1218,48 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const if (fUseCache && fAvailableCreditCached) return nAvailableCreditCached; - CAmount nCredit = 0; - uint256 hashTx = GetHash(); - for (unsigned int i = 0; i < vout.size(); i++) { - if (!pwallet->IsSpent(hashTx, i)) { - const CTxOut& txout = vout[i]; - nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); - if (!MoneyRange(nCredit)) - throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range"); - } - } - + CAmount nCredit = CreditFor(ISMINE_SPENDABLE); nAvailableCreditCached = nCredit; fAvailableCreditCached = true; return nCredit; } +CAmount CWalletTx::GetColdStakingCredit(bool fUseCache) const +{ + if (pwallet == 0) + return 0; + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + if (fUseCache && fColdCreditCached) + return nColdCreditCached; + + CAmount nCredit = CreditFor(ISMINE_COLD); + nColdCreditCached = nCredit; + fColdCreditCached = true; + return nCredit; +} + +CAmount CWalletTx::GetStakeDelegationCredit(bool fUseCache) const +{ + if (pwallet == 0) + return 0; + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + if (fUseCache && fDelegatedCreditCached) + return nDelegatedCreditCached; + + CAmount nCredit = CreditFor(ISMINE_SPENDABLE_DELEGATED); + nDelegatedCreditCached = nCredit; + fDelegatedCreditCached = true; + return nCredit; +} + // Return sum of unlocked coins CAmount CWalletTx::GetUnlockedCredit() const { @@ -1643,6 +1721,38 @@ CAmount CWallet::GetBalance() const return nTotal; } +CAmount CWallet::GetColdStakingBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { + const CWalletTx* pcoin = &(*it).second; + + if (pcoin->IsTrusted()) + nTotal += pcoin->GetColdStakingCredit(); + } + } + + return nTotal; +} + +CAmount CWallet::GetDelegatedBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { + const CWalletTx* pcoin = &(*it).second; + + if (pcoin->IsTrusted()) + nTotal += pcoin->GetStakeDelegationCredit(); + } + } + + return nTotal; +} + //std::map mapMintMaturity; //int nLastMaturityCheck = 0; @@ -1825,7 +1935,9 @@ void CWallet::AvailableCoins( bool fIncludeZeroValue, AvailableCoinsType nCoinType, bool fUseIX, - int nWatchonlyConfig) const + int nWatchonlyConfig, + bool fIncludeColdStaking, + bool fIncludeDelegated) const { vCoins.clear(); @@ -1895,11 +2007,10 @@ void CWallet::AvailableCoins( if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected((*it).first, i)) continue; - bool fIsSpendable = false; - if ((mine & ISMINE_SPENDABLE) != ISMINE_NO) - fIsSpendable = true; - if ((mine & ISMINE_MULTISIG) != ISMINE_NO) - fIsSpendable = true; + bool fIsSpendable = ( + ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || + ((mine & (ISMINE_MULTISIG | (fIncludeColdStaking ? ISMINE_COLD : ISMINE_NO) | + (fIncludeDelegated ? ISMINE_SPENDABLE_DELEGATED : ISMINE_NO) )) != ISMINE_NO)); vCoins.emplace_back(COutput(pcoin, i, nDepth, fIsSpendable)); } @@ -5528,6 +5639,10 @@ void CWalletTx::Init(const CWallet* pwalletIn) fImmatureWatchCreditCached = false; fAvailableWatchCreditCached = false; fChangeCached = false; + fColdDebitCached = false; + fColdCreditCached = false; + fDelegatedDebitCached = false; + fDelegatedCreditCached = false; nDebitCached = 0; nCreditCached = 0; nImmatureCreditCached = 0; @@ -5540,6 +5655,10 @@ void CWalletTx::Init(const CWallet* pwalletIn) nWatchCreditCached = 0; nAvailableWatchCreditCached = 0; nImmatureWatchCreditCached = 0; + nColdDebitCached = 0; + nColdCreditCached = 0; + nDelegatedDebitCached = 0; + nDelegatedCreditCached = 0; nChangeCached = 0; nOrderPos = -1; } @@ -5599,6 +5718,10 @@ void CWalletTx::MarkDirty() fImmatureWatchCreditCached = false; fDebitCached = false; fChangeCached = false; + fColdDebitCached = false; + fColdCreditCached = false; + fDelegatedDebitCached = false; + fDelegatedCreditCached = false; } void CWalletTx::BindWallet(CWallet* pwalletIn) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 94c42ff4586e..f292f00b14a6 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -346,7 +346,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! check whether we are allowed to upgrade (or already support) to the named feature bool CanSupportFeature(enum WalletFeature wf); - void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed = true, const CCoinControl* coinControl = NULL, bool fIncludeZeroValue = false, AvailableCoinsType nCoinType = ALL_COINS, bool fUseIX = false, int nWatchonlyConfig = 1) const; + void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed = true, const CCoinControl* coinControl = NULL, bool fIncludeZeroValue = false, AvailableCoinsType nCoinType = ALL_COINS, bool fUseIX = false, int nWatchonlyConfig = 1, bool fIncludeColdStaking=false, bool fIncludeDelegated=true) const; std::map > AvailableCoinsByAddress(bool fConfirmed = true, CAmount maxCoinValue = 0); bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector vCoins, std::set >& setCoinsRet, CAmount& nValueRet) const; @@ -427,6 +427,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void ReacceptWalletTransactions(bool fFirstLoad = false); void ResendWalletTransactions(); CAmount GetBalance() const; + CAmount GetColdStakingBalance() const; // delegated coins for which we have the staking key + CAmount GetDelegatedBalance() const; // delegated coins for which we have the spending key CAmount GetZerocoinBalance(bool fMatureOnly) const; CAmount GetUnconfirmedZerocoinBalance() const; CAmount GetImmatureZerocoinBalance() const; @@ -718,6 +720,10 @@ class CWalletTx : public CMerkleTx mutable bool fImmatureWatchCreditCached; mutable bool fAvailableWatchCreditCached; mutable bool fChangeCached; + mutable bool fColdDebitCached; + mutable bool fColdCreditCached; + mutable bool fDelegatedDebitCached; + mutable bool fDelegatedCreditCached; mutable CAmount nDebitCached; mutable CAmount nCreditCached; mutable CAmount nImmatureCreditCached; @@ -731,6 +737,10 @@ class CWalletTx : public CMerkleTx mutable CAmount nImmatureWatchCreditCached; mutable CAmount nAvailableWatchCreditCached; mutable CAmount nChangeCached; + mutable CAmount nColdDebitCached; + mutable CAmount nColdCreditCached; + mutable CAmount nDelegatedDebitCached; + mutable CAmount nDelegatedCreditCached; CWalletTx(); CWalletTx(const CWallet* pwalletIn); @@ -802,6 +812,10 @@ class CWalletTx : public CMerkleTx CAmount GetLockedWatchOnlyCredit() const; CAmount GetChange() const; + // Cold staking contracts credit + CAmount GetColdStakingCredit(bool fUseCache = true) const; + CAmount GetStakeDelegationCredit(bool fUseCache = true) const; + void GetAmounts(std::list& listReceived, std::list& listSent, CAmount& nFee, @@ -822,8 +836,10 @@ class CWalletTx : public CMerkleTx int64_t GetComputedTxTime() const; int GetRequestCount() const; void RelayWalletTransaction(std::string strCommand = "tx"); - std::set GetConflicts() const; + +protected: + CAmount CreditFor(const isminetype& minetype) const; }; diff --git a/src/wallet/wallet_ismine.cpp b/src/wallet/wallet_ismine.cpp index aaa9a61c0676..b9cf3a63a54b 100644 --- a/src/wallet/wallet_ismine.cpp +++ b/src/wallet/wallet_ismine.cpp @@ -77,6 +77,20 @@ isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) } break; } + case TX_COLDSTAKE: { + keyID = CKeyID(uint160(vSolutions[0])); + bool stakeKeyIsMine = keystore.HaveKey(keyID); + keyID = CKeyID(uint160(vSolutions[1])); + bool spendKeyIsMine = keystore.HaveKey(keyID); + + if (spendKeyIsMine && stakeKeyIsMine) + return ISMINE_SPENDABLE_STAKEABLE; + else if (stakeKeyIsMine) + return ISMINE_COLD; + else if (spendKeyIsMine) + return ISMINE_SPENDABLE_DELEGATED; + break; + } case TX_MULTISIG: { // Only consider transactions "mine" if we own ALL the // keys involved. multi-signature transactions that are diff --git a/src/wallet/wallet_ismine.h b/src/wallet/wallet_ismine.h index 6919af3b6275..3c52419d54ef 100644 --- a/src/wallet/wallet_ismine.h +++ b/src/wallet/wallet_ismine.h @@ -21,7 +21,13 @@ enum isminetype { //! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys ISMINE_MULTISIG = 2, ISMINE_SPENDABLE = 4, - ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE + //! Indicates that we have the staking key of a P2CS + ISMINE_COLD = 8, + //! Indicates that we have the spending key of a P2CS + ISMINE_SPENDABLE_DELEGATED = 16, + ISMINE_SPENDABLE_ALL = ISMINE_SPENDABLE_DELEGATED | ISMINE_SPENDABLE, + ISMINE_SPENDABLE_STAKEABLE = ISMINE_SPENDABLE_DELEGATED | ISMINE_COLD, + ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE | ISMINE_COLD | ISMINE_SPENDABLE_DELEGATED }; /** used for bitflags of isminetype */ typedef uint8_t isminefilter; From b7097486ceb3c73f030c7fa059de4604240b4423 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 4 Jul 2019 22:09:12 +0200 Subject: [PATCH 05/59] [RPC] define: getnewstakingaddress, delegatestake, rawdelegatestake --- src/core_write.cpp | 9 +- src/rpc/client.cpp | 7 ++ src/rpc/server.cpp | 3 + src/rpc/server.h | 5 +- src/wallet/rpcwallet.cpp | 258 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 265 insertions(+), 17 deletions(-) diff --git a/src/core_write.cpp b/src/core_write.cpp index cc91b232489e..cd4a3211238d 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -81,8 +81,13 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey, out.pushKV("type", GetTxnOutputType(type)); UniValue a(UniValue::VARR); - for (const CTxDestination& addr : addresses) - a.push_back(CBitcoinAddress(addr).ToString()); + if (type == TX_COLDSTAKE && addresses.size() == 2) { + a.push_back(CBitcoinAddress(addresses[0], true).ToString()); + a.push_back(CBitcoinAddress(addresses[1], false).ToString()); + } else { + for (const CTxDestination& addr : addresses) + a.push_back(CBitcoinAddress(addr).ToString()); + } out.pushKV("addresses", a); } diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 8122438ed9aa..45e5bb86a2e5 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -35,6 +35,13 @@ static const CRPCConvertParam vRPCConvertParams[] = {"generate", 0}, {"getnetworkhashps", 0}, {"getnetworkhashps", 1}, + {"delegatestake", 1}, + {"delegatestake", 3}, + {"delegatestake", 4}, + {"delegatestake", 5}, + {"rawdelegatestake", 1}, + {"rawdelegatestake", 3}, + {"rawdelegatestake", 4}, {"sendtoaddress", 1}, {"sendtoaddressix", 1}, {"settxfee", 0}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 05cafe7c335a..6c9c1317757e 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -397,6 +397,7 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "addmultisigaddress", &addmultisigaddress, true, false, true}, {"wallet", "autocombinerewards", &autocombinerewards, false, false, true}, {"wallet", "backupwallet", &backupwallet, true, false, true}, + {"wallet", "delegatestake", &delegatestake, false, false, true}, {"wallet", "enableautomintaddress", &enableautomintaddress, true, false, true}, {"wallet", "createautomintaddress", &createautomintaddress, true, false, true}, {"wallet", "dumpprivkey", &dumpprivkey, true, false, true}, @@ -409,6 +410,7 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "getaddressesbyaccount", &getaddressesbyaccount, true, false, true}, {"wallet", "getbalance", &getbalance, false, false, true}, {"wallet", "getnewaddress", &getnewaddress, true, false, true}, + {"wallet", "getnewstakingaddress", &getnewstakingaddress, true, false, true}, {"wallet", "getrawchangeaddress", &getrawchangeaddress, true, false, true}, {"wallet", "getreceivedbyaccount", &getreceivedbyaccount, false, false, true}, {"wallet", "getreceivedbyaddress", &getreceivedbyaddress, false, false, true}, @@ -433,6 +435,7 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "lockunspent", &lockunspent, true, false, true}, {"wallet", "move", &movecmd, false, false, true}, {"wallet", "multisend", &multisend, false, false, true}, + {"wallet", "rawdelegatestake", &rawdelegatestake, false, false, true}, {"wallet", "sendfrom", &sendfrom, false, false, true}, {"wallet", "sendmany", &sendmany, false, false, true}, {"wallet", "sendtoaddress", &sendtoaddress, false, false, true}, diff --git a/src/rpc/server.h b/src/rpc/server.h index 80d66aebd66d..6d1b97d56485 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -212,7 +212,10 @@ extern UniValue submitblock(const UniValue& params, bool fHelp); extern UniValue estimatefee(const UniValue& params, bool fHelp); extern UniValue estimatepriority(const UniValue& params, bool fHelp); -extern UniValue getnewaddress(const UniValue& params, bool fHelp); // in rpcwallet.cpp +extern UniValue delegatestake(const UniValue& params, bool fHelp); // in rpcwallet.cpp +extern UniValue rawdelegatestake(const UniValue& params, bool fHelp); +extern UniValue getnewaddress(const UniValue& params, bool fHelp); +extern UniValue getnewstakingaddress(const UniValue& params, bool fHelp); extern UniValue getaccountaddress(const UniValue& params, bool fHelp); extern UniValue getrawchangeaddress(const UniValue& params, bool fHelp); extern UniValue setaccount(const UniValue& params, bool fHelp); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f9a7929fd111..00cbf8e85611 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -80,6 +80,28 @@ std::string AccountFromValue(const UniValue& value) return strAccount; } +CBitcoinAddress GetNewAddressFromAccount(const std::string name, const bool fStakeAddr, const UniValue ¶ms) +{ + LOCK2(cs_main, pwalletMain->cs_wallet); + // Parse the account first so we don't generate a key if there's an error + std::string strAccount; + if (!params.isNull() && params.size() > 0) + strAccount = AccountFromValue(params[0]); + + if (!pwalletMain->IsLocked()) + pwalletMain->TopUpKeyPool(); + + // Generate a new key that is added to wallet + CPubKey newKey; + if (!pwalletMain->GetKeyFromPool(newKey)) + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + CKeyID keyID = newKey.GetID(); + + pwalletMain->SetAddressBook(keyID, strAccount, name); + + return CBitcoinAddress(keyID, fStakeAddr); +} + UniValue getnewaddress(const UniValue& params, bool fHelp) { if (fHelp || params.size() > 1) @@ -99,25 +121,29 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) HelpExampleCli("getnewaddress", "") + HelpExampleCli("getnewaddress", "\"\"") + HelpExampleCli("getnewaddress", "\"myaccount\"") + HelpExampleRpc("getnewaddress", "\"myaccount\"")); - LOCK2(cs_main, pwalletMain->cs_wallet); + return GetNewAddressFromAccount("receive", false, params).ToString(); +} - // Parse the account first so we don't generate a key if there's an error - std::string strAccount; - if (params.size() > 0) - strAccount = AccountFromValue(params[0]); +UniValue getnewstakingaddress(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw std::runtime_error( + "getnewstakingaddress ( \"account\" )\n" + "\nReturns a new PIVX cold staking address for receiving delegated stakes.\n" + "If 'account' is specified (recommended), it is added to the address book \n" + "so payments received with the address will be credited to 'account'.\n" - if (!pwalletMain->IsLocked()) - pwalletMain->TopUpKeyPool(); + "\nArguments:\n" + "1. \"account\" (string, optional) The account name for the address to be linked to. if not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n" - // Generate a new key that is added to wallet - CPubKey newKey; - if (!pwalletMain->GetKeyFromPool(newKey)) - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); - CKeyID keyID = newKey.GetID(); + "\nResult:\n" + "\"pivxaddress\" (string) The new pivx address\n" - pwalletMain->SetAddressBook(keyID, strAccount, "receive"); + "\nExamples:\n" + + HelpExampleCli("getnewstakingaddress", "") + HelpExampleCli("getnewstakingaddress", "\"\"") + + HelpExampleCli("getnewstakingaddress", "\"myaccount\"") + HelpExampleRpc("getnewstakingaddress", "\"myaccount\"")); - return CBitcoinAddress(keyID).ToString(); + return GetNewAddressFromAccount("coldstaking", true, params).ToString(); } @@ -401,6 +427,210 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) return wtx.GetHash().GetHex(); } +bool CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CReserveKey& reservekey) +{ + LOCK2(cs_main, pwalletMain->cs_wallet); + + // Check that Cold Staking has been enforced or fForceNotEnabled = true + bool fForceNotEnabled = false; + if (params.size() > 5 && !params[5].isNull()) + fForceNotEnabled = params[5].get_bool(); + + int nBestHeight = chainActive.Height(); + if (!Params().Cold_Staking_Enabled(nBestHeight) && !fForceNotEnabled) { + std::string errMsg = strprintf("Cold Staking not enforced yet at block %d.\n" + "If the wallet is syncing, you may force the stake delegation setting fForceNotEnabled to true.\n" + "WARNING: If the network hasn't reached the activation height, this tx will be rejected resulting in a ban.\n", nBestHeight); + throw JSONRPCError(RPC_WALLET_ERROR, errMsg); + } + + // Get Staking Address + CBitcoinAddress stakeAddr(params[0].get_str()); + CKeyID stakeKey; + if (!stakeAddr.IsValid() || !stakeAddr.IsStakingAddress()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX staking address"); + if (!stakeAddr.GetKeyID(stakeKey)) + throw JSONRPCError(RPC_WALLET_ERROR, "Unable to get stake pubkey hash from stakingaddress"); + + // Get Amount + CAmount nValue = AmountFromValue(params[1]); + if (nValue <= 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); + + // Get Owner Address + CBitcoinAddress ownerAddr; + CKeyID ownerKey; + if (params.size() > 2 && !params[2].isNull() && !params[2].get_str().empty()) { + // Address provided + ownerAddr.SetString(params[2].get_str()); + if (!ownerAddr.IsValid() || ownerAddr.IsStakingAddress()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX spending address"); + if (!ownerAddr.GetKeyID(ownerKey)) + throw JSONRPCError(RPC_WALLET_ERROR, "Unable to get spend pubkey hash from owneraddress"); + // Check that the owner address belongs to this wallet, or fForceExternalAddr is true + bool fForceExternalAddr = params.size() > 3 && !params[3].isNull() ? params[3].get_bool() : false; + if (!fForceExternalAddr && !pwalletMain->HaveKey(ownerKey)) { + std::string errMsg = strprintf("The provided owneraddress \"%s\" is not present in this wallet.\n" + "Set 'fExternalOwner' argument to true, in order to force the stake delegation to an external owner address.\n" + "e.g. delegatestake stakingaddress amount owneraddress true.\n" + "WARNING: Only the owner of the key to owneraddress will be allowed to spend these coins after the delegation.", + ownerAddr.ToString()); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errMsg); + } + + } else { + // Get new owner address from keypool + ownerAddr = GetNewAddressFromAccount("delegated", false, NullUniValue); + if (!ownerAddr.GetKeyID(ownerKey)) + throw JSONRPCError(RPC_WALLET_ERROR, "Unable to get spend pubkey hash from owneraddress"); + } + + // Get P2CS script for addresses + CScript scriptPubKey = GetScriptForStakeDelegation(stakeKey, ownerKey); + + // include already delegated coins + bool fUseDelegated = false; + if (params.size() > 4 && !params[4].isNull()) + fUseDelegated = params[4].get_bool(); + + // Check amount + CAmount currBalance = pwalletMain->GetBalance() + (fUseDelegated ? pwalletMain->GetDelegatedBalance() : 0); + if (nValue > currBalance) + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); + + std::string strError; + EnsureWalletIsUnlocked(); + + // Create the transaction + CAmount nFeeRequired; + if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError, NULL, ALL_COINS, /*fUseIX*/ false, (CAmount)0, fUseDelegated)) { + if (nValue + nFeeRequired > currBalance) + strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)); + LogPrintf("%s : %s\n", __func__, strError); + throw JSONRPCError(RPC_WALLET_ERROR, strError); + } + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("owner_address", ownerAddr.ToString())); + result.push_back(Pair("staker_address", stakeAddr.ToString())); + return result; +} + +UniValue delegatestake(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 6) + throw std::runtime_error( + "delegatestake \"stakingaddress\" amount ( \"owneraddress\" fExternalOwner fUseDelegated fForceNotEnabled )\n" + "\nDelegate an amount to a given address for cold staking. The amount is a real and is rounded to the nearest 0.00000001\n" + + HelpRequiringPassphrase() + "\n" + + "\nArguments:\n" + "1. \"stakingaddress\" (string, required) The pivx staking address to delegate.\n" + "2. \"amount\" (numeric, required) The amount in PIV to delegate for staking. eg 100\n" + "3. \"owneraddress\" (string, optional) The pivx address corresponding to the key that will be able to spend the stake. \n" + " If not provided, or empty string, a new wallet address is generated.\n" + "4. \"fExternalOwner\" (boolean, optional, default = false) use the provided 'owneraddress' anyway, even if not present in this wallet.\n" + " WARNING: The owner of the keys to 'owneraddress' will be the only one allowed to spend these coins.\n" + "5. \"fUseDelegated\" (boolean, optional, default = false) include already delegated inputs if needed." + "6. \"fForceNotEnabled\" (boolean, optional, default = false) force the creation before the activation height (during sync)." + + "\nResult:\n" + "{\n" + " \"owner_address\": \"xxx\" (string) The owner (delegator) owneraddress.\n" + " \"staker_address\": \"xxx\" (string) The cold staker (delegate) stakingaddress.\n" + " \"txid\": \"xxx\" (string) The stake delegation transaction id.\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("delegatestake", "\"S1t2a3kab9c8c71VA78xxxy4MxZg6vgeS6\" 100") + + HelpExampleCli("delegatestake", "\"S1t2a3kab9c8c71VA78xxxy4MxZg6vgeS6\" 1000 \"DMJRSsuU9zfyrvxVaAEFQqK4MxZg34fk\"") + + HelpExampleRpc("delegatestake", "\"S1t2a3kab9c8c71VA78xxxy4MxZg6vgeS6\", 1000, \"DMJRSsuU9zfyrvxVaAEFQqK4MxZg34fk\"")); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + CWalletTx wtx; + CReserveKey reservekey(pwalletMain); + CreateColdStakeDelegation(params, wtx, reservekey); + + if (!pwalletMain->CommitTransaction(wtx, reservekey, "tx")) + throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); + + return wtx.GetHash().GetHex(); +} + +UniValue rawdelegatestake(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 5) + throw std::runtime_error( + "rawdelegatestake \"stakingaddress\" amount ( \"owneraddress\" fExternalOwner fUseDelegated )\n" + "\nDelegate an amount to a given address for cold staking. The amount is a real and is rounded to the nearest 0.00000001\n" + "\nDelegate transaction is returned as json object." + + HelpRequiringPassphrase() + "\n" + + "\nArguments:\n" + "1. \"stakingaddress\" (string, required) The pivx staking address to delegate.\n" + "2. \"amount\" (numeric, required) The amount in PIV to delegate for staking. eg 100\n" + "3. \"owneraddress\" (string, optional) The pivx address corresponding to the key that will be able to spend the stake. \n" + " If not provided, or empty string, a new wallet address is generated.\n" + "4. \"fExternalOwner\" (boolean, optional, default = false) use the provided 'owneraddress' anyway, even if not present in this wallet.\n" + " WARNING: The owner of the keys to 'owneraddress' will be the only one allowed to spend these coins.\n" + "5. \"fUseDelegated (boolean, optional, default = false) include already delegated inputs if needed." + + "\nResult:\n" + "{\n" + " \"txid\" : \"id\", (string) The transaction id (same as provided)\n" + " \"version\" : n, (numeric) The version\n" + " \"size\" : n, (numeric) The serialized transaction size\n" + " \"locktime\" : ttt, (numeric) The lock time\n" + " \"vin\" : [ (array of json objects)\n" + " {\n" + " \"txid\": \"id\", (string) The transaction id\n" + " \"vout\": n, (numeric) \n" + " \"scriptSig\": { (json object) The script\n" + " \"asm\": \"asm\", (string) asm\n" + " \"hex\": \"hex\" (string) hex\n" + " },\n" + " \"sequence\": n (numeric) The script sequence number\n" + " }\n" + " ,...\n" + " ],\n" + " \"vout\" : [ (array of json objects)\n" + " {\n" + " \"value\" : x.xxx, (numeric) The value in btc\n" + " \"n\" : n, (numeric) index\n" + " \"scriptPubKey\" : { (json object)\n" + " \"asm\" : \"asm\", (string) the asm\n" + " \"hex\" : \"hex\", (string) the hex\n" + " \"reqSigs\" : n, (numeric) The required sigs\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " \"addresses\" : [ (json array of string)\n" + " \"pivxaddress\" (string) pivx address\n" + " ,...\n" + " ]\n" + " }\n" + " }\n" + " ,...\n" + " ],\n" + " \"hex\" : \"data\", (string) The serialized, hex-encoded data for 'txid'\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("rawdelegatestake", "\"S1t2a3kab9c8c71VA78xxxy4MxZg6vgeS6\" 100") + + HelpExampleCli("rawdelegatestake", "\"S1t2a3kab9c8c71VA78xxxy4MxZg6vgeS6\" 1000 \"DMJRSsuU9zfyrvxVaAEFQqK4MxZg34fk\"") + + HelpExampleRpc("rawdelegatestake", "\"S1t2a3kab9c8c71VA78xxxy4MxZg6vgeS6\", 1000, \"DMJRSsuU9zfyrvxVaAEFQqK4MxZg34fk\"")); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + CWalletTx wtx; + CReserveKey reservekey(pwalletMain); + CreateColdStakeDelegation(params, wtx, reservekey); + + UniValue result(UniValue::VOBJ); + TxToUniv(wtx, 0, result); + + return result; +} + UniValue sendtoaddressix(const UniValue& params, bool fHelp) { if (fHelp || params.size() < 2 || params.size() > 4) From 97b633241d625be58fd75dfd367464b52d39040f Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 5 Jul 2019 23:57:49 +0200 Subject: [PATCH 06/59] [RPC/Wallet] fix IsMine filters and add option to include delegated balance in calls - getbalance (default true) - sendfrom (default false) - sendmany (default false) - listtransactions (default true) --- src/rpc/client.cpp | 2 ++ src/rpc/misc.cpp | 2 +- src/wallet/rpcdump.cpp | 2 +- src/wallet/rpcwallet.cpp | 51 ++++++++++++++++++++++++++-------------- src/wallet/wallet.cpp | 28 ++++++++++++---------- src/wallet/wallet.h | 7 +++--- 6 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 45e5bb86a2e5..459c5afffbb0 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -69,6 +69,8 @@ static const CRPCConvertParam vRPCConvertParams[] = {"listtransactions", 1}, {"listtransactions", 2}, {"listtransactions", 3}, + {"listtransactions", 4}, + {"listtransactions", 5}, {"listaccounts", 0}, {"listaccounts", 1}, {"walletpassphrase", 1}, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index e16ad425b3b1..1b95ffb27753 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -396,7 +396,7 @@ UniValue validateaddress(const UniValue& params, bool fHelp) #ifdef ENABLE_WALLET isminetype mine = pwalletMain ? IsMine(*pwalletMain, dest) : ISMINE_NO; - ret.push_back(Pair("ismine", bool(mine & ISMINE_SPENDABLE))); + ret.push_back(Pair("ismine", bool(mine & ISMINE_SPENDABLE_ALL))); ret.push_back(Pair("iswatchonly", bool(mine & ISMINE_WATCH_ONLY))); UniValue detail = boost::apply_visitor(DescribeAddressVisitor(mine), dest); ret.pushKVs(detail); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 330d0ac075e8..2bcf3890821a 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -194,7 +194,7 @@ UniValue importaddress(const UniValue& params, bool fHelp) fRescan = params[2].get_bool(); { - if (::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) + if (::IsMine(*pwalletMain, script) & ISMINE_SPENDABLE_ALL) throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); // add to address book or update label diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 00cbf8e85611..2f2387d894ec 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -926,9 +926,9 @@ CAmount GetAccountBalance(const std::string& strAccount, int nMinDepth, const is UniValue getbalance(const UniValue& params, bool fHelp) { - if (fHelp || params.size() > 3) + if (fHelp || params.size() > 4) throw std::runtime_error( - "getbalance ( \"account\" minconf includeWatchonly )\n" + "getbalance ( \"account\" minconf includeWatchonly includeDelegated )\n" "\nIf account is not specified, returns the server's total available balance (excluding zerocoins).\n" "If account is specified, returns the balance in the account.\n" "Note that the account \"\" is not the same as leaving the parameter out.\n" @@ -938,6 +938,7 @@ UniValue getbalance(const UniValue& params, bool fHelp) "1. \"account\" (string, optional) The selected account, or \"*\" for entire wallet. It may be the default account using \"\".\n" "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" "3. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress')\n" + "4. includeDelegated (bool, optional, default=true) Also include balance delegated to cold stakers\n" "\nResult:\n" "amount (numeric) The total amount in PIV received for this account.\n" @@ -963,9 +964,10 @@ UniValue getbalance(const UniValue& params, bool fHelp) if (params.size() > 1) nMinDepth = params[1].get_int(); isminefilter filter = ISMINE_SPENDABLE; - if (params.size() > 2) - if (params[2].get_bool()) - filter = filter | ISMINE_WATCH_ONLY; + if ( params.size() > 2 && params[2].get_bool() ) + filter = filter | ISMINE_WATCH_ONLY; + if ( !(params.size() > 3) || params[3].get_bool() ) + filter = filter | ISMINE_SPENDABLE_DELEGATED; if (params[0].get_str() == "*") { // Calculate total balance a different way from GetBalance() @@ -1088,9 +1090,9 @@ UniValue movecmd(const UniValue& params, bool fHelp) UniValue sendfrom(const UniValue& params, bool fHelp) { - if (fHelp || params.size() < 3 || params.size() > 6) + if (fHelp || params.size() < 3 || params.size() > 7) throw std::runtime_error( - "sendfrom \"fromaccount\" \"topivxaddress\" amount ( minconf \"comment\" \"comment-to\" )\n" + "sendfrom \"fromaccount\" \"topivxaddress\" amount ( minconf \"comment\" \"comment-to\" includeDelegated)\n" "\nSent an amount from an account to a pivx address.\n" "The amount is a real and is rounded to the nearest 0.00000001." + HelpRequiringPassphrase() + "\n" @@ -1105,6 +1107,7 @@ UniValue sendfrom(const UniValue& params, bool fHelp) "6. \"comment-to\" (string, optional) An optional comment to store the name of the person or organization \n" " to which you're sending the transaction. This is not part of the transaction, \n" " it is just kept in your wallet.\n" + "7. includeDelegated (bool, optional, default=false) Also include balance delegated to cold stakers\n" "\nResult:\n" "\"transactionid\" (string) The transaction id.\n" @@ -1135,10 +1138,14 @@ UniValue sendfrom(const UniValue& params, bool fHelp) if (params.size() > 5 && !params[5].isNull() && !params[5].get_str().empty()) wtx.mapValue["to"] = params[5].get_str(); + isminefilter filter = ISMINE_SPENDABLE; + if ( params.size() > 6 && params[6].get_bool() ) + filter = filter | ISMINE_SPENDABLE_DELEGATED; + EnsureWalletIsUnlocked(); // Check funds - CAmount nBalance = GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); + CAmount nBalance = GetAccountBalance(strAccount, nMinDepth, filter); if (nAmount > nBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); @@ -1150,9 +1157,9 @@ UniValue sendfrom(const UniValue& params, bool fHelp) UniValue sendmany(const UniValue& params, bool fHelp) { - if (fHelp || params.size() < 2 || params.size() > 4) + if (fHelp || params.size() < 2 || params.size() > 5) throw std::runtime_error( - "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" )\n" + "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" includeDelegated )\n" "\nSend multiple times. Amounts are double-precision floating point numbers." + HelpRequiringPassphrase() + "\n" @@ -1165,6 +1172,7 @@ UniValue sendmany(const UniValue& params, bool fHelp) " }\n" "3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n" "4. \"comment\" (string, optional) A comment\n" + "5. includeDelegated (bool, optional, default=false) Also include balance delegated to cold stakers\n" "\nResult:\n" "\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" @@ -1212,6 +1220,10 @@ UniValue sendmany(const UniValue& params, bool fHelp) vecSend.push_back(std::make_pair(scriptPubKey, nAmount)); } + isminefilter filter = ISMINE_SPENDABLE; + if ( params.size() > 5 && params[5].get_bool() ) + filter = filter | ISMINE_SPENDABLE_DELEGATED; + EnsureWalletIsUnlocked(); // Check funds @@ -1305,7 +1317,7 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) if (params.size() > 1) fIncludeEmpty = params[1].get_bool(); - isminefilter filter = ISMINE_SPENDABLE; + isminefilter filter = ISMINE_SPENDABLE_ALL; if (params.size() > 2) if (params[2].get_bool()) filter = filter | ISMINE_WATCH_ONLY; @@ -1562,9 +1574,9 @@ void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccoun UniValue listtransactions(const UniValue& params, bool fHelp) { - if (fHelp || params.size() > 4) + if (fHelp || params.size() > 6) throw std::runtime_error( - "listtransactions ( \"account\" count from includeWatchonly)\n" + "listtransactions ( \"account\" count from includeWatchonly includeDelegated )\n" "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions for account 'account'.\n" "\nArguments:\n" @@ -1573,6 +1585,8 @@ UniValue listtransactions(const UniValue& params, bool fHelp) "2. count (numeric, optional, default=10) The number of transactions to return\n" "3. from (numeric, optional, default=0) The number of transactions to skip\n" "4. includeWatchonly (bool, optional, default=false) Include transactions to watchonly addresses (see 'importaddress')\n" + "5. includeDelegated (bool, optional, default=true) Also include balance delegated to cold stakers\n" + "6. includeCold (bool, optional, default=true) Also include delegated balance received as cold-staker by this node\n" "\nResult:\n" "[\n" @@ -1635,9 +1649,12 @@ UniValue listtransactions(const UniValue& params, bool fHelp) if (params.size() > 2) nFrom = params[2].get_int(); isminefilter filter = ISMINE_SPENDABLE; - if (params.size() > 3) - if (params[3].get_bool()) + if ( params.size() > 3 && params[3].get_bool() ) filter = filter | ISMINE_WATCH_ONLY; + if ( !(params.size() > 4) || params[4].get_bool() ) + filter = filter | ISMINE_SPENDABLE_DELEGATED; + if ( !(params.size() > 5) || params[5].get_bool() ) + filter = filter | ISMINE_COLD; if (nCount < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count"); @@ -1807,7 +1824,7 @@ UniValue listsinceblock(const UniValue& params, bool fHelp) CBlockIndex* pindex = NULL; int target_confirms = 1; - isminefilter filter = ISMINE_SPENDABLE; + isminefilter filter = ISMINE_SPENDABLE_ALL | ISMINE_COLD; if (params.size() > 0) { uint256 blockId = 0; @@ -1895,7 +1912,7 @@ UniValue gettransaction(const UniValue& params, bool fHelp) uint256 hash; hash.SetHex(params[0].get_str()); - isminefilter filter = ISMINE_SPENDABLE; + isminefilter filter = ISMINE_SPENDABLE_ALL | ISMINE_COLD; if (params.size() > 1) if (params[1].get_bool()) filter = filter | ISMINE_WATCH_ONLY; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0d134a7e8475..24422806839a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -508,9 +508,9 @@ bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& // wait for reindex and/or import to finish if (fImporting || fReindex) return false; - // Find possible candidates + // Find possible candidates (remove delegated) std::vector vPossibleCoins; - AvailableCoins(vPossibleCoins, true, NULL, false, ONLY_10000); + AvailableCoins(vPossibleCoins, true, NULL, false, ONLY_10000, false, 1, false, false); if (vPossibleCoins.empty()) { LogPrintf("CWallet::GetMasternodeVinAndKeys -- Could not locate any valid masternode vin\n"); return false; @@ -1286,7 +1286,7 @@ CAmount CWalletTx::GetUnlockedCredit() const return nCredit; } - // Return sum of unlocked coins +// Return sum of locked coins CAmount CWalletTx::GetLockedCredit() const { if (pwallet == 0) @@ -1304,6 +1304,9 @@ CAmount CWalletTx::GetLockedCredit() const // Skip spent coins if (pwallet->IsSpent(hashTx, i)) continue; + // Add delegated coins + nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE_DELEGATED); + // Add locked coins if (pwallet->IsLockedCoin(hashTx, i)) { nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); @@ -2021,7 +2024,8 @@ void CWallet::AvailableCoins( std::map > CWallet::AvailableCoinsByAddress(bool fConfirmed, CAmount maxCoinValue) { std::vector vCoins; - AvailableCoins(vCoins, fConfirmed); + // include delegated and cold + AvailableCoins(vCoins, fConfirmed, nullptr, false, ALL_COINS, false, 1, true, true); std::map > mapCoins; for (COutput out : vCoins) { @@ -2101,7 +2105,7 @@ bool CWallet::SelectStakeCoins(std::list >& listInp LOCK(cs_main); //Add PIV std::vector vCoins; - AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS); + AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1); CAmount nAmountSelected = 0; if (GetBoolArg("-pivstake", true) && !fPrecompute) { for (const COutput &out : vCoins) { @@ -2312,12 +2316,11 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int return true; } -bool CWallet::SelectCoins(const CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl, AvailableCoinsType coin_type, bool useIX) const +bool CWallet::SelectCoins(const CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl, AvailableCoinsType coin_type, bool useIX, bool fIncludeColdStaking, bool fIncludeDelegated) const { // Note: this function should never be used for "always free" tx types like dstx - std::vector vCoins; - AvailableCoins(vCoins, true, coinControl, false, coin_type, useIX); + AvailableCoins(vCoins, true, coinControl, false, coin_type, useIX, 1, fIncludeColdStaking, fIncludeDelegated); // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) if (coinControl && coinControl->HasSelected()) { @@ -2395,7 +2398,8 @@ bool CWallet::CreateTransaction(const std::vector >& const CCoinControl* coinControl, AvailableCoinsType coin_type, bool useIX, - CAmount nFeePay) + CAmount nFeePay, + bool fIncludeDelegated) { if (useIX && nFeePay < CENT) nFeePay = CENT; @@ -2464,7 +2468,7 @@ bool CWallet::CreateTransaction(const std::vector >& std::set > setCoins; CAmount nValueIn = 0; - if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl, coin_type, useIX)) { + if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl, coin_type, useIX, false, fIncludeDelegated)) { if (coin_type == ALL_COINS) { strFailReason = _("Insufficient funds."); } else if (coin_type == ONLY_NOT10000IFMN) { @@ -2623,11 +2627,11 @@ bool CWallet::CreateTransaction(const std::vector >& return true; } -bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, AvailableCoinsType coin_type, bool useIX, CAmount nFeePay) +bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, AvailableCoinsType coin_type, bool useIX, CAmount nFeePay, bool fIncludeDelegated) { std::vector > vecSend; vecSend.push_back(std::make_pair(scriptPubKey, nValue)); - return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason, coinControl, coin_type, useIX, nFeePay); + return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason, coinControl, coin_type, useIX, nFeePay, fIncludeDelegated); } // ppcoin: create coin stake transaction diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f292f00b14a6..ee593cf20b00 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -169,7 +169,7 @@ class CAddressBookData class CWallet : public CCryptoKeyStore, public CValidationInterface { private: - bool SelectCoins(const CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl = NULL, AvailableCoinsType coin_type = ALL_COINS, bool useIX = true) const; + bool SelectCoins(const CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl = NULL, AvailableCoinsType coin_type = ALL_COINS, bool useIX = true, bool fIncludeColdStaking=false, bool fIncludeDelegated=true) const; //it was public bool SelectCoins(int64_t nTargetValue, std::set >& setCoinsRet, int64_t& nValueRet, const CCoinControl *coinControl = NULL, AvailableCoinsType coin_type=ALL_COINS, bool useIX = true) const; CWalletDB* pwalletdbEncryption; @@ -450,8 +450,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface const CCoinControl* coinControl = NULL, AvailableCoinsType coin_type = ALL_COINS, bool useIX = false, - CAmount nFeePay = 0); - bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl = NULL, AvailableCoinsType coin_type = ALL_COINS, bool useIX = false, CAmount nFeePay = 0); + CAmount nFeePay = 0, + bool fIncludeDelegated = false); + bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl = NULL, AvailableCoinsType coin_type = ALL_COINS, bool useIX = false, CAmount nFeePay = 0, bool fIncludeDelegated = false); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, std::string strCommand = "tx"); bool AddAccountingEntry(const CAccountingEntry&, CWalletDB & pwalletdb); int GenerateObfuscationOutputs(int nTotalValue, std::vector& vout); From e8abd216cc4e32571140a4af345c6b53e35076e4 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 5 Jul 2019 04:37:10 +0200 Subject: [PATCH 07/59] [Cleanup] Remove re-declaration of EnsureWalletIsUnlocked in rpcdump --- src/wallet/rpcdump.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 2bcf3890821a..fe163198ab09 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -25,7 +25,6 @@ #include -void EnsureWalletIsUnlocked(bool fAllowAnonOnly); std::string static EncodeDumpTime(int64_t nTime) { From dea30c71c674807930c942ea972bd0098c8bed36 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 5 Jul 2019 04:38:19 +0200 Subject: [PATCH 08/59] [Script] Define IsPayToColdStaking() fast test --- src/script/script.cpp | 12 ++++++++++++ src/script/script.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/script/script.cpp b/src/script/script.cpp index ad1c854cb186..60b5bb3a37fd 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -251,6 +251,18 @@ bool CScript::IsPayToScriptHash() const this->at(22) == OP_EQUAL); } +bool CScript::IsPayToColdStaking() const +{ + // Extra-fast test for pay-to-cold-staking CScripts: + return (this->size() == 51 && + this->at(2) == OP_ROT && + this->at(4) == OP_CHECKCOLDSTAKEVERIFY && + this->at(5) == 0x14 && + this->at(27) == 0x14 && + this->at(49) == OP_EQUALVERIFY && + this->at(50) == OP_CHECKSIG); +} + bool CScript::StartsWithOpcode(const opcodetype opcode) const { return (!this->empty() && this->at(0) == opcode); diff --git a/src/script/script.h b/src/script/script.h index ba70acedbc05..45a38daa861e 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -605,6 +605,7 @@ class CScript : public std::vector bool IsNormalPaymentScript() const; bool IsPayToScriptHash() const; + bool IsPayToColdStaking() const; bool StartsWithOpcode(const opcodetype opcode) const; bool IsZerocoinMint() const; bool IsZerocoinSpend() const; From ca90d02072c8ed4f9bf07bde52f735cabda011c0 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 5 Jul 2019 04:51:49 +0200 Subject: [PATCH 09/59] [Script] Sign P2CS scripts --- src/script/sign.cpp | 27 ++++++++++++++++++++++----- src/script/sign.h | 4 ++-- src/wallet/wallet.cpp | 2 +- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 1f9ecc7564f7..84768d67c05f 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -53,7 +53,7 @@ bool SignN(const std::vector& multisigdata, const CKeyStore& keystore, * Returns false if scriptPubKey could not be completely satisfied. */ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash, int nHashType, - CScript& scriptSigRet, txnouttype& whichTypeRet) + CScript& scriptSigRet, txnouttype& whichTypeRet, bool fColdStake = false) { scriptSigRet.clear(); @@ -103,12 +103,28 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash case TX_MULTISIG: scriptSigRet << OP_0; // workaround CHECKMULTISIG bug return (SignN(vSolutions, keystore, hash, nHashType, scriptSigRet)); + + case TX_COLDSTAKE: + if (fColdStake) { + // sign with the cold staker key + keyID = CKeyID(uint160(vSolutions[0])); + } else { + // sign with the owner key + keyID = CKeyID(uint160(vSolutions[1])); + } + if (!Sign1(keyID, keystore, hash, nHashType, scriptSigRet)) + return error("*** %s: failed to sign with the %s key.", + __func__, fColdStake ? "cold staker" : "owner"); + CPubKey vch; + keystore.GetPubKey(keyID, vch); + scriptSigRet << (fColdStake ? (int)OP_TRUE : OP_FALSE) << ToByteVector(vch); + return true; } LogPrintf("*** solver no case met \n"); return false; } -bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, int nHashType) +bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, int nHashType, bool fColdStake) { assert(nIn < txTo.vin.size()); CTxIn& txin = txTo.vin[nIn]; @@ -118,7 +134,7 @@ bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutabl uint256 hash = SignatureHash(fromPubKey, txTo, nIn, nHashType); txnouttype whichType; - if (!Solver(keystore, fromPubKey, hash, nHashType, txin.scriptSig, whichType)) + if (!Solver(keystore, fromPubKey, hash, nHashType, txin.scriptSig, whichType, fColdStake)) return false; if (whichType == TX_SCRIPTHASH) @@ -143,14 +159,14 @@ bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutabl return VerifyScript(txin.scriptSig, fromPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&txTo, nIn)); } -bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType) +bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType, bool fColdStake) { assert(nIn < txTo.vin.size()); CTxIn& txin = txTo.vin[nIn]; assert(txin.prevout.n < txFrom.vout.size()); const CTxOut& txout = txFrom.vout[txin.prevout.n]; - return SignSignature(keystore, txout.scriptPubKey, txTo, nIn, nHashType); + return SignSignature(keystore, txout.scriptPubKey, txTo, nIn, nHashType, fColdStake); } static CScript PushAll(const std::vector& values) @@ -231,6 +247,7 @@ static CScript CombineSignatures(const CScript& scriptPubKey, const CTransaction return PushAll(sigs2); case TX_PUBKEY: case TX_PUBKEYHASH: + case TX_COLDSTAKE: // Signatures are bigger than placeholders or empty scripts: if (sigs1.empty() || sigs1[0].empty()) return PushAll(sigs2); diff --git a/src/script/sign.h b/src/script/sign.h index ea57ced969d7..f7fdc9b1fdbf 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -19,8 +19,8 @@ class CTransaction; struct CMutableTransaction; bool Sign1(const CKeyID& address, const CKeyStore& keystore, uint256 hash, int nHashType, CScript& scriptSigRet); -bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL); -bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL); +bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL, bool fColdStake = false); +bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL, bool fColdStake = false); /** * Given two sets of signatures for scriptPubKey, possibly with OP_0 placeholders, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 24422806839a..db001eae73c4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2786,7 +2786,7 @@ bool CWallet::CreateCoinStake( if (!txNew.vin[0].scriptSig.IsZerocoinSpend()) { for (CTxIn txIn : txNew.vin) { const CWalletTx *wtx = GetWalletTx(txIn.prevout.hash); - if (!SignSignature(*this, *wtx, txNew, nIn++)) + if (!SignSignature(*this, *wtx, txNew, nIn++, SIGHASH_ALL, true)) return error("CreateCoinStake : failed to sign coinstake"); } } else { From e19bfa4466f87602cda7751d51c918324cd1ffe7 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 5 Jul 2019 04:52:25 +0200 Subject: [PATCH 10/59] [Wallet] Add flag 'fStakeDelegationVoided' to tx when P2PC gets spent --- src/wallet/wallet.cpp | 3 +++ src/wallet/wallet.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index db001eae73c4..f2a9de8f42cf 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2489,6 +2489,8 @@ bool CWallet::CreateTransaction(const std::vector >& for (PAIRTYPE(const CWalletTx*, unsigned int) pcoin : setCoins) { + if(pcoin.first->vout[pcoin.second].scriptPubKey.IsPayToColdStaking()) + wtxNew.fStakeDelegationVoided = true; CAmount nCredit = pcoin.first->vout[pcoin.second].nValue; //The coin age after the next block (depth+1) is used instead of the current, //reflecting an assumption the user would accept a bit more delay for @@ -5647,6 +5649,7 @@ void CWalletTx::Init(const CWallet* pwalletIn) fColdCreditCached = false; fDelegatedDebitCached = false; fDelegatedCreditCached = false; + fStakeDelegationVoided = false; nDebitCached = 0; nCreditCached = 0; nImmatureCreditCached = 0; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index ee593cf20b00..1056009be174 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -441,7 +441,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; CAmount GetLockedWatchOnlyBalance() const; - bool CreateTransaction(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl); bool CreateTransaction(const std::vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, @@ -725,6 +724,7 @@ class CWalletTx : public CMerkleTx mutable bool fColdCreditCached; mutable bool fDelegatedDebitCached; mutable bool fDelegatedCreditCached; + mutable bool fStakeDelegationVoided; mutable CAmount nDebitCached; mutable CAmount nCreditCached; mutable CAmount nImmatureCreditCached; From 8ac0ed3ec6cbf39d7a37325691d8d6fc86b61ed0 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 5 Jul 2019 13:15:25 +0200 Subject: [PATCH 11/59] [Script] Add fColdStake to ExtractDestination add a boolean flag to decide which (single) address should be returned from scriptPubkey --- src/script/standard.cpp | 19 ++++++++--------- src/script/standard.h | 2 +- src/stakeinput.cpp | 32 ++++++++++++++++------------- src/wallet/wallet.cpp | 45 ++++++++++++++++++++++++++++++----------- src/wallet/wallet.h | 2 +- 5 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 8d6e2a2a2a7b..96ca2dc33288 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -216,31 +216,32 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType) return whichType != TX_NONSTANDARD; } -bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) +bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet, bool fColdStake) { std::vector vSolutions; txnouttype whichType; if (!Solver(scriptPubKey, whichType, vSolutions)) return false; - if (whichType == TX_PUBKEY) - { + if (whichType == TX_PUBKEY) { CPubKey pubKey(vSolutions[0]); if (!pubKey.IsValid()) return false; addressRet = pubKey.GetID(); return true; - } - else if (whichType == TX_PUBKEYHASH) - { + + } else if (whichType == TX_PUBKEYHASH) { addressRet = CKeyID(uint160(vSolutions[0])); return true; - } - else if (whichType == TX_SCRIPTHASH) - { + + } else if (whichType == TX_SCRIPTHASH) { addressRet = CScriptID(uint160(vSolutions[0])); return true; + } else if (whichType == TX_COLDSTAKE) { + int sol = fColdStake ? 0 : 1; + addressRet = CKeyID(uint160(vSolutions[sol])); + return true; } // Multisig txns have more than one address... return false; diff --git a/src/script/standard.h b/src/script/standard.h index 75bb35819502..d5ca54ef2b76 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -88,7 +88,7 @@ const char* GetTxnOutputType(txnouttype t); bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector >& vSolutionsRet); int ScriptSigArgsExpected(txnouttype t, const std::vector >& vSolutions); bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType); -bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet); +bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet, bool fColdStake = false); bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); CScript GetScriptForDestination(const CTxDestination& dest); diff --git a/src/stakeinput.cpp b/src/stakeinput.cpp index 400f2584ae0f..0a77332fe374 100644 --- a/src/stakeinput.cpp +++ b/src/stakeinput.cpp @@ -199,26 +199,30 @@ bool CPivStake::CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmoun std::vector vSolutions; txnouttype whichType; CScript scriptPubKeyKernel = txFrom.vout[nPosition].scriptPubKey; - if (!Solver(scriptPubKeyKernel, whichType, vSolutions)) { - LogPrintf("CreateCoinStake : failed to parse kernel\n"); - return false; - } + if (!Solver(scriptPubKeyKernel, whichType, vSolutions)) + return error("%s: failed to parse kernel", __func__); - if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH) - return false; // only support pay to public key and pay to address + if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH && whichType != TX_COLDSTAKE) + return error("%s: type=%d (%s) not supported for scriptPubKeyKernel", __func__, whichType, GetTxnOutputType(whichType)); CScript scriptPubKey; - if (whichType == TX_PUBKEYHASH) // pay to address type - { - //convert to pay to public key type - CKey key; - CKeyID keyID = CKeyID(uint160(vSolutions[0])); - if (!pwallet->GetKey(keyID, key)) - return false; + CKey key; + if (whichType == TX_PUBKEYHASH) { + // if P2PKH check that we have the input private key + if (!pwallet->GetKey(CKeyID(uint160(vSolutions[0])), key)) + return error("%s: Unable to get staking private key", __func__); + // convert to P2PK inputs scriptPubKey << key.GetPubKey() << OP_CHECKSIG; - } else + + } else { + // if P2CS, check that we have the coldstaking private key + if ( whichType == TX_COLDSTAKE && !pwallet->GetKey(CKeyID(uint160(vSolutions[0])), key) ) + return error("%s: Unable to get cold staking private key", __func__); + + // keep the same script scriptPubKey = scriptPubKeyKernel; + } vout.emplace_back(CTxOut(0, scriptPubKey)); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f2a9de8f42cf..0c5004dcc1f3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -538,7 +538,7 @@ bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& return false; } -bool CWallet::GetVinAndKeysFromOutput(COutput out, CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet) +bool CWallet::GetVinAndKeysFromOutput(COutput out, CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet, bool fColdStake) { // wait for reindex and/or import to finish if (fImporting || fReindex) return false; @@ -549,7 +549,7 @@ bool CWallet::GetVinAndKeysFromOutput(COutput out, CTxIn& txinRet, CPubKey& pubK pubScript = out.tx->vout[out.i].scriptPubKey; // the inputs PubKey CTxDestination address1; - ExtractDestination(pubScript, address1); + ExtractDestination(pubScript, address1, fColdStake); CBitcoinAddress address2(address1); CKeyID keyID; @@ -1434,10 +1434,11 @@ void CWalletTx::GetAmounts(std::list& listReceived, continue; // In either case, we need to get the destination address + const bool fColdStake = (filter & ISMINE_COLD); CTxDestination address; if (txout.IsZerocoinMint()) { address = CNoDestination(); - } else if (!ExtractDestination(txout.scriptPubKey, address)) { + } else if (!ExtractDestination(txout.scriptPubKey, address, fColdStake)) { if (!IsCoinStake() && !IsCoinBase()) { LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", this->GetHash().ToString()); } @@ -1997,6 +1998,18 @@ void CWallet::AvailableCoins( if (mine == ISMINE_NO) continue; + // skip cold coins + if (!fIncludeColdStaking && mine == ISMINE_COLD) + continue; + + // skip delegated coins + if (!fIncludeDelegated && mine == ISMINE_SPENDABLE_DELEGATED) + continue; + + // skip auto-delegated coins + if (!fIncludeColdStaking && !fIncludeDelegated && mine == ISMINE_SPENDABLE_STAKEABLE) + continue; + if ((mine == ISMINE_MULTISIG || mine == ISMINE_SPENDABLE) && nWatchonlyConfig == 2) continue; @@ -2010,12 +2023,12 @@ void CWallet::AvailableCoins( if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected((*it).first, i)) continue; - bool fIsSpendable = ( + bool fIsValid = ( ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || ((mine & (ISMINE_MULTISIG | (fIncludeColdStaking ? ISMINE_COLD : ISMINE_NO) | (fIncludeDelegated ? ISMINE_SPENDABLE_DELEGATED : ISMINE_NO) )) != ISMINE_NO)); - vCoins.emplace_back(COutput(pcoin, i, nDepth, fIsSpendable)); + vCoins.emplace_back(COutput(pcoin, i, nDepth, fIsValid)); } } } @@ -2024,7 +2037,7 @@ void CWallet::AvailableCoins( std::map > CWallet::AvailableCoinsByAddress(bool fConfirmed, CAmount maxCoinValue) { std::vector vCoins; - // include delegated and cold + // include cold and delegated coins AvailableCoins(vCoins, fConfirmed, nullptr, false, ALL_COINS, false, 1, true, true); std::map > mapCoins; @@ -2033,10 +2046,16 @@ std::map > CWallet::AvailableCoinsByAddres continue; CTxDestination address; - if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) - continue; + bool fColdStakeAddr = false; + if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address, fColdStakeAddr)) { + // check if we have the staking key + fColdStakeAddr = true; + if ( !out.tx->vout[out.i].scriptPubKey.IsPayToColdStaking() || + !ExtractDestination(out.tx->vout[out.i].scriptPubKey, address, fColdStakeAddr) ) + continue; + } - mapCoins[CBitcoinAddress(address)].push_back(out); + mapCoins[CBitcoinAddress(address, fColdStakeAddr)].push_back(out); } return mapCoins; @@ -2490,6 +2509,7 @@ bool CWallet::CreateTransaction(const std::vector >& for (PAIRTYPE(const CWalletTx*, unsigned int) pcoin : setCoins) { if(pcoin.first->vout[pcoin.second].scriptPubKey.IsPayToColdStaking()) + // P2CS contract is being spent wtxNew.fStakeDelegationVoided = true; CAmount nCredit = pcoin.first->vout[pcoin.second].nValue; //The coin age after the next block (depth+1) is used instead of the current, @@ -2658,7 +2678,7 @@ bool CWallet::CreateCoinStake( txNew.vout.push_back(CTxOut(0, scriptEmpty)); // Choose coins to use - CAmount nBalance = GetBalance(); + CAmount nBalance = GetBalance() + GetColdStakingBalance(); if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) return error("CreateCoinStake : invalid reserve balance amount"); @@ -2720,7 +2740,7 @@ bool CWallet::CreateCoinStake( // Create the output transaction(s) std::vector vout; if (!stakeInput->CreateTxOuts(this, vout, nCredit)) { - LogPrintf("%s : failed to get scriptPubKey\n", __func__); + LogPrintf("%s : failed to create output\n", __func__); continue; } txNew.vout.insert(txNew.vout.end(), vout.begin(), vout.end()); @@ -3160,7 +3180,8 @@ std::map CWallet::GetAddressBalances() CTxDestination addr; if (!IsMine(pcoin->vout[i])) continue; - if (!ExtractDestination(pcoin->vout[i].scriptPubKey, addr)) + if ( !ExtractDestination(pcoin->vout[i].scriptPubKey, addr) && + !ExtractDestination(pcoin->vout[i].scriptPubKey, addr, true) ) continue; CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->vout[i].nValue; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1056009be174..98b44fa8f43f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -353,7 +353,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /// Get 10000 PIV output and keys which can be used for the Masternode bool GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet, std::string strTxHash = "", std::string strOutputIndex = ""); /// Extract txin information and keys from output - bool GetVinAndKeysFromOutput(COutput out, CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet); + bool GetVinAndKeysFromOutput(COutput out, CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet, bool fColdStake = false); bool IsSpent(const uint256& hash, unsigned int n) const; From cdb758b78703a0c9576267f688b218fd67b5bae3 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 6 Jul 2019 00:29:23 +0200 Subject: [PATCH 12/59] [RPC] define getcoldstakingbalance --- src/rpc/misc.cpp | 3 ++ src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + src/wallet/rpcwallet.cpp | 98 ++++++++++++++++++++++++++++++++++------ 4 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 1b95ffb27753..530e03be9994 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -267,6 +267,7 @@ class DescribeAddressVisitor : public boost::static_visitor UniValue operator()(const CScriptID &scriptID) const { UniValue obj(UniValue::VOBJ); obj.push_back(Pair("isscript", true)); + obj.push_back(Pair("iscoldstaking", false)); CScript subscript; pwalletMain->GetCScript(scriptID, subscript); std::vector addresses; @@ -365,6 +366,7 @@ UniValue validateaddress(const UniValue& params, bool fHelp) " \"address\" : \"pivxaddress\", (string) The pivx address validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n" " \"ismine\" : true|false, (boolean) If the address is yours or not\n" + " \"iscoldstaking\" : true|false, (boolean) If the address is a PIVX cold staking address\n" " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" " \"isscript\" : true|false, (boolean) If the key is a script\n" " \"hex\" : \"hex\", (string, optional) The redeemscript for the P2SH address\n" @@ -397,6 +399,7 @@ UniValue validateaddress(const UniValue& params, bool fHelp) #ifdef ENABLE_WALLET isminetype mine = pwalletMain ? IsMine(*pwalletMain, dest) : ISMINE_NO; ret.push_back(Pair("ismine", bool(mine & ISMINE_SPENDABLE_ALL))); + ret.push_back(Pair("iscoldstaking", bool(mine & ISMINE_COLD))); ret.push_back(Pair("iswatchonly", bool(mine & ISMINE_WATCH_ONLY))); UniValue detail = boost::apply_visitor(DescribeAddressVisitor(mine), dest); ret.pushKVs(detail); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 6c9c1317757e..c92637d832ef 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -409,6 +409,7 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "getaccount", &getaccount, true, false, true}, {"wallet", "getaddressesbyaccount", &getaddressesbyaccount, true, false, true}, {"wallet", "getbalance", &getbalance, false, false, true}, + {"wallet", "getcoldstakingbalance", &getcoldstakingbalance, false, false, true}, {"wallet", "getnewaddress", &getnewaddress, true, false, true}, {"wallet", "getnewstakingaddress", &getnewstakingaddress, true, false, true}, {"wallet", "getrawchangeaddress", &getrawchangeaddress, true, false, true}, diff --git a/src/rpc/server.h b/src/rpc/server.h index 6d1b97d56485..770199c4a2ec 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -227,6 +227,7 @@ extern UniValue signmessage(const UniValue& params, bool fHelp); extern UniValue getreceivedbyaddress(const UniValue& params, bool fHelp); extern UniValue getreceivedbyaccount(const UniValue& params, bool fHelp); extern UniValue getbalance(const UniValue& params, bool fHelp); +extern UniValue getcoldstakingbalance(const UniValue& params, bool fHelp); extern UniValue getunconfirmedbalance(const UniValue& params, bool fHelp); extern UniValue movecmd(const UniValue& params, bool fHelp); extern UniValue sendfrom(const UniValue& params, bool fHelp); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 2f2387d894ec..0497044f2f1a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -118,7 +118,7 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) "\"pivxaddress\" (string) The new pivx address\n" "\nExamples:\n" + - HelpExampleCli("getnewaddress", "") + HelpExampleCli("getnewaddress", "\"\"") + + HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "\"\"") + HelpExampleCli("getnewaddress", "\"myaccount\"") + HelpExampleRpc("getnewaddress", "\"myaccount\"")); return GetNewAddressFromAccount("receive", false, params).ToString(); @@ -126,21 +126,21 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) UniValue getnewstakingaddress(const UniValue& params, bool fHelp) { + if (fHelp || params.size() > 1) throw std::runtime_error( "getnewstakingaddress ( \"account\" )\n" - "\nReturns a new PIVX cold staking address for receiving delegated stakes.\n" - "If 'account' is specified (recommended), it is added to the address book \n" - "so payments received with the address will be credited to 'account'.\n" + "\nReturns a new PIVX cold staking address for receiving delegated cold stakes.\n" "\nArguments:\n" "1. \"account\" (string, optional) The account name for the address to be linked to. if not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n" + "\nResult:\n" "\"pivxaddress\" (string) The new pivx address\n" "\nExamples:\n" + - HelpExampleCli("getnewstakingaddress", "") + HelpExampleCli("getnewstakingaddress", "\"\"") + + HelpExampleCli("getnewstakingaddress", "") + HelpExampleRpc("getnewstakingaddress", "\"\"") + HelpExampleCli("getnewstakingaddress", "\"myaccount\"") + HelpExampleRpc("getnewstakingaddress", "\"myaccount\"")); return GetNewAddressFromAccount("coldstaking", true, params).ToString(); @@ -407,7 +407,7 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); CBitcoinAddress address(params[0].get_str()); - if (!address.IsValid()) + if (!address.IsValid() || address.IsStakingAddress()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); // Amount @@ -427,7 +427,7 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) return wtx.GetHash().GetHex(); } -bool CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CReserveKey& reservekey) +UniValue CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CReserveKey& reservekey) { LOCK2(cs_main, pwalletMain->cs_wallet); @@ -550,12 +550,13 @@ UniValue delegatestake(const UniValue& params, bool fHelp) CWalletTx wtx; CReserveKey reservekey(pwalletMain); - CreateColdStakeDelegation(params, wtx, reservekey); + UniValue ret = CreateColdStakeDelegation(params, wtx, reservekey); if (!pwalletMain->CommitTransaction(wtx, reservekey, "tx")) throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); - return wtx.GetHash().GetHex(); + ret.push_back(Pair("txid", wtx.GetHash().GetHex())); + return ret; } UniValue rawdelegatestake(const UniValue& params, bool fHelp) @@ -659,7 +660,7 @@ UniValue sendtoaddressix(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); CBitcoinAddress address(params[0].get_str()); - if (!address.IsValid()) + if (!address.IsValid() || address.IsStakingAddress()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); // Amount @@ -1005,6 +1006,69 @@ UniValue getbalance(const UniValue& params, bool fHelp) return ValueFromAmount(nBalance); } +UniValue getcoldstakingbalance(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw std::runtime_error( + "getcoldstakingbalance ( \"account\" )\n" + "\nIf account is not specified, returns the server's total available cold balance.\n" + "If account is specified, returns the cold balance in the account.\n" + "Note that the account \"\" is not the same as leaving the parameter out.\n" + "The server total may be different to the balance in the default \"\" account.\n" + + "\nArguments:\n" + "1. \"account\" (string, optional) The selected account, or \"*\" for entire wallet. It may be the default account using \"\".\n" + + "\nResult:\n" + "amount (numeric) The total amount in PIV received for this account in P2CS contracts.\n" + + "\nExamples:\n" + "\nThe total amount in the server across all accounts\n" + + HelpExampleCli("getcoldstakingbalance", "") + + "\nThe total amount in the account named tabby\n" + + HelpExampleCli("getcoldstakingbalance", "\"tabby\"") + + "\nAs a json rpc call\n" + + HelpExampleRpc("getcoldstakingbalance", "\"tabby\"")); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + if (params.size() == 0) + return ValueFromAmount(pwalletMain->GetColdStakingBalance()); + + const int nMinDepth = 1; + + if (params[0].get_str() == "*") { + // Calculate total balance a different way from GetBalance() + // (GetBalance() sums up all unspent TxOuts) + CAmount nBalance = 0; + for (std::map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { + const CWalletTx& wtx = (*it).second; + if (!IsFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0) + continue; + + CAmount allFee; + std::string strSentAccount; + std::list listReceived; + std::list listSent; + wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, ISMINE_COLD); + if (wtx.GetDepthInMainChain() >= nMinDepth) { + for (const COutputEntry& r : listReceived) + nBalance += r.amount; + } + for (const COutputEntry& s : listSent) + nBalance -= s.amount; + nBalance -= allFee; + } + return ValueFromAmount(nBalance); + } + + std::string strAccount = AccountFromValue(params[0]); + + CAmount nBalance = GetAccountBalance(strAccount, nMinDepth, ISMINE_COLD); + + return ValueFromAmount(nBalance); +} + UniValue getunconfirmedbalance(const UniValue ¶ms, bool fHelp) { if (fHelp || params.size() > 0) @@ -1124,7 +1188,7 @@ UniValue sendfrom(const UniValue& params, bool fHelp) std::string strAccount = AccountFromValue(params[0]); CBitcoinAddress address(params[1].get_str()); - if (!address.IsValid()) + if (!address.IsValid() || address.IsStakingAddress()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); CAmount nAmount = AmountFromValue(params[2]); int nMinDepth = 1; @@ -1206,7 +1270,7 @@ UniValue sendmany(const UniValue& params, bool fHelp) std::vector keys = sendTo.getKeys(); for (const std::string& name_ : keys) { CBitcoinAddress address(name_); - if (!address.IsValid()) + if (!address.IsValid() || address.IsStakingAddress()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid PIVX address: ")+name_); if (setAddress.count(address)) @@ -2414,7 +2478,9 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) "\nResult:\n" "{\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n" - " \"balance\": xxxxxxx, (numeric) the total PIV balance of the wallet\n" + " \"balance\": xxxxxxx, (numeric) the total PIV balance of the wallet (cold balance excluded)\n" + " \"delegated_balance\": xxxxx, (numeric) the PIV balance held in P2CS (cold staking) contracts\n" + " \"cold_staking_balance\": xx, (numeric) the PIV balance held in cold staking addresses\n" " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in PIV\n" " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in PIV\n" " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" @@ -2433,6 +2499,8 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) UniValue obj(UniValue::VOBJ); obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); + obj.push_back(Pair("delegated_balance", ValueFromAmount(pwalletMain->GetDelegatedBalance()))); + obj.push_back(Pair("cold_staking_balance", ValueFromAmount(pwalletMain->GetColdStakingBalance()))); obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwalletMain->GetUnconfirmedBalance()))); obj.push_back(Pair("immature_balance", ValueFromAmount(pwalletMain->GetImmatureBalance()))); obj.push_back(Pair("txcount", (int)pwalletMain->mapWallet.size())); @@ -3288,7 +3356,7 @@ UniValue spendzerocoinmints(const UniValue& params, bool fHelp) // Destination address was supplied as params[4]. Optional parameters MUST be at the end // to avoid type confusion from the JSON interpreter address = CBitcoinAddress(params[3].get_str()); - if(!address.IsValid()) + if(!address.IsValid() || address.IsStakingAddress()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); } @@ -3311,7 +3379,7 @@ extern UniValue DoZpivSpend(const CAmount nAmount, bool fMintChange, bool fMinim std::list> outputs; if(address_str != "") { // Spend to supplied destination address address = CBitcoinAddress(address_str); - if(!address.IsValid()) + if(!address.IsValid() || address.IsStakingAddress()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); outputs.push_back(std::pair(&address, nAmount)); } From f30c2999cd34a84b467ece97e110a04249cd9b15 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Jul 2019 00:44:35 +0200 Subject: [PATCH 13/59] [Wallet] Use cold inputs to stake --- src/wallet/wallet.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0c5004dcc1f3..66239b068890 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2124,7 +2124,10 @@ bool CWallet::SelectStakeCoins(std::list >& listInp LOCK(cs_main); //Add PIV std::vector vCoins; - AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1); + + // include cold, exclude delegated + AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, true, false); + CAmount nAmountSelected = 0; if (GetBoolArg("-pivstake", true) && !fPrecompute) { for (const COutput &out : vCoins) { From 661083db3da0440888f7420d57a10c481ad27844 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 8 Jul 2019 11:40:57 +0200 Subject: [PATCH 14/59] [Core] Sign block with staking key if coinstake is 'cold' stake --- src/blocksignature.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/blocksignature.cpp b/src/blocksignature.cpp index a23b4acf3741..b2bf18388df8 100644 --- a/src/blocksignature.cpp +++ b/src/blocksignature.cpp @@ -22,8 +22,10 @@ bool GetKeyIDFromUTXO(const CTxOut& txout, CKeyID& keyID) return false; if (whichType == TX_PUBKEY) { keyID = CPubKey(vSolutions[0]).GetID(); - } else if (whichType == TX_PUBKEYHASH) { + } else if (whichType == TX_PUBKEYHASH || whichType == TX_COLDSTAKE) { keyID = CKeyID(uint160(vSolutions[0])); + } else { + return false; } return true; @@ -80,6 +82,12 @@ bool CheckBlockSignature(const CBlock& block) if (whichType == TX_PUBKEY || whichType == TX_PUBKEYHASH) { valtype& vchPubKey = vSolutions[0]; pubkey = CPubKey(vchPubKey); + } else if (whichType == TX_COLDSTAKE) { + // pick the public key from the P2CS input + const CTxIn& txin = block.vtx[1].vin[0]; + int start = 1 + (int) *txin.scriptSig.begin(); // skip sig + start += 1 + (int) *(txin.scriptSig.begin()+start); // skip flag + pubkey = CPubKey(txin.scriptSig.begin()+start+1, txin.scriptSig.end()); } } From 92515c343093bae4d1133ae8217365882245f755 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 8 Jul 2019 11:42:15 +0200 Subject: [PATCH 15/59] [Core] add main P2CS check in CheckTransaction --- src/main.cpp | 25 +++++++++++++++++++++++-- src/script/interpreter.cpp | 1 + 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 022c08d9e122..cc76f73bd01b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1175,6 +1175,23 @@ bool CheckZerocoinSpend(const CTransaction& tx, bool fVerifySignature, CValidati return fValidated; } + +bool CheckColdStake(const CTransaction& tx, CValidationState& state) +{ + CTxOut prevOut; + if(!GetOutput(tx.vin[0].prevout.hash, tx.vin[0].prevout.n, state, prevOut)) + return state.DoS(100, error("%s : invalid input", __func__), REJECT_INVALID, "bad-txns-inputs"); + + if (!prevOut.scriptPubKey.IsPayToColdStaking()) + return true; + + // spending to the same contract + if (prevOut.scriptPubKey != tx.vout[1].scriptPubKey) + return state.DoS(100, error("%s : invalid scripts", __func__), REJECT_INVALID, "bad-txns-cold-stake"); + + return true; +} + bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fRejectBadUTXO, CValidationState& state, bool fFakeSerialAttack) { // Basic checks that don't depend on any context @@ -1214,14 +1231,18 @@ bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fReject } } + // Additional check for cold staking + if(tx.IsCoinStake() && !tx.HasZerocoinSpendInputs() && !CheckColdStake(tx, state)) + return state.DoS(100, error("CheckTransaction() : invalid cold stake"), REJECT_INVALID, "bad-txns-cold-stake"); + std::set vInOutPoints; std::set vZerocoinSpendSerials; int nZCSpendCount = 0; + for (const CTxIn& txin : tx.vin) { // Check for duplicate inputs if (vInOutPoints.count(txin.prevout)) - return state.DoS(100, error("CheckTransaction() : duplicate inputs"), - REJECT_INVALID, "bad-txns-inputs-duplicate"); + return state.DoS(100, error("CheckTransaction() : duplicate inputs"), REJECT_INVALID, "bad-txns-inputs-duplicate"); //duplicate zcspend serials are checked in CheckZerocoinSpend() if (!txin.IsZerocoinSpend()) { diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 29053c04eff4..acb8b24550a1 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -965,6 +965,7 @@ bool EvalScript(std::vector >& stack, const CScript& if(!checker.CheckColdStake()) { return set_error(serror, SCRIPT_ERR_CHECKCOLDSTAKEVERIFY); } + // CheckTransaction verifies that prevout scripts match with out scripts } break; From e546ec655ae63c8375a1f51ebb8f9f47915698e9 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 10 Jul 2019 12:14:35 +0200 Subject: [PATCH 16/59] [Core][Tests] REGTEST: fix nStakeModifier --- test/functional/rpc_decodescript.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py index 92b5ad5194ba..f0ed69d3bd62 100755 --- a/test/functional/rpc_decodescript.py +++ b/test/functional/rpc_decodescript.py @@ -102,7 +102,7 @@ def decodescript_script_pub_key(self): # # lock until block 500,000 rpc_result = self.nodes[0].decodescript('63' + push_public_key + 'ad670320a107b17568' + push_public_key + 'ac') - assert_equal('OP_IF ' + public_key + ' OP_CHECKSIGVERIFY OP_ELSE 500000 OP_NOP2 OP_DROP OP_ENDIF ' + public_key + ' OP_CHECKSIG', rpc_result['asm']) + assert_equal('OP_IF ' + public_key + ' OP_CHECKSIGVERIFY OP_ELSE 500000 OP_CHECKLOCKTIMEVERIFY OP_DROP OP_ENDIF ' + public_key + ' OP_CHECKSIG', rpc_result['asm']) def decoderawtransaction_asm_sighashtype(self): """Test decoding scripts via RPC command "decoderawtransaction". From bff6c9e32a127478b8e324d60460560646d50108 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 10 Jul 2019 12:54:46 +0200 Subject: [PATCH 17/59] [RPC] signrawtransaction: select proper key for 'cold' inputs --- src/rpc/rawtransaction.cpp | 10 +++++++++- src/wallet/wallet.cpp | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index d1cad1f73d60..235d8a856b58 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -771,9 +771,17 @@ UniValue signrawtransaction(const UniValue& params, bool fHelp) const CScript& prevPubKey = (Params().NetworkID() == CBaseChainParams::REGTEST && mapPrevOut.count(txin.prevout) != 0 ? mapPrevOut[txin.prevout] : coins->vout[txin.prevout.n].scriptPubKey); txin.scriptSig.clear(); + + // if this is a P2CS script, select which key to use + bool fColdStake = false; + if (prevPubKey.IsPayToColdStaking()) { + // if we have both keys, sign with the spender key + fColdStake = !bool(IsMine(keystore, prevPubKey) & ISMINE_SPENDABLE_DELEGATED); + } + // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mergedTx.vout.size())) - SignSignature(keystore, prevPubKey, mergedTx, i, nHashType); + SignSignature(keystore, prevPubKey, mergedTx, i, nHashType, fColdStake); // ... and merge in other signatures: for (const CMutableTransaction& txv : txVariants) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 66239b068890..4e43fcbaf30b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -511,6 +511,7 @@ bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& // Find possible candidates (remove delegated) std::vector vPossibleCoins; AvailableCoins(vPossibleCoins, true, NULL, false, ONLY_10000, false, 1, false, false); + if (vPossibleCoins.empty()) { LogPrintf("CWallet::GetMasternodeVinAndKeys -- Could not locate any valid masternode vin\n"); return false; From 1d69ef3591311643294f0263c73a796539795473 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Jul 2019 06:52:06 +0200 Subject: [PATCH 18/59] [Wallet] Add -coldstaking flag to enable/disable feature --- src/init.cpp | 1 + src/wallet/wallet.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index 57cfedb0442f..b4fa391c787b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -546,6 +546,7 @@ std::string HelpMessage(HelpMessageMode mode) #ifdef ENABLE_WALLET strUsage += HelpMessageGroup(_("Staking options:")); strUsage += HelpMessageOpt("-staking=", strprintf(_("Enable staking functionality (0-1, default: %u)"), 1)); + strUsage += HelpMessageOpt("-coldstaking=", strprintf(_("Enable cold staking functionality (0-1, default: %u). Disabled if staking=0"), 1)); strUsage += HelpMessageOpt("-pivstake=", strprintf(_("Enable or disable staking functionality for PIV inputs (0-1, default: %u)"), 1)); strUsage += HelpMessageOpt("-zpivstake=", strprintf(_("Enable or disable staking functionality for zPIV inputs (0-1, default: %u)"), 1)); strUsage += HelpMessageOpt("-reservebalance=", _("Keep the specified amount available for spending at all times (default: 0)")); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4e43fcbaf30b..7877c2b262b3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2127,7 +2127,7 @@ bool CWallet::SelectStakeCoins(std::list >& listInp std::vector vCoins; // include cold, exclude delegated - AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, true, false); + AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, GetBoolArg("-coldstaking", true), false); CAmount nAmountSelected = 0; if (GetBoolArg("-pivstake", true) && !fPrecompute) { From 4064423ee7168a47a6dc398657c43dc06c838662 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Jul 2019 09:03:15 +0200 Subject: [PATCH 19/59] [Consensus] ColdStaking enforcement Set the ColdStaking enforcement by fixed height (placeholders now) --- src/chainparams.cpp | 12 ++++++++++++ src/chainparams.h | 3 +++ src/main.cpp | 23 +++++++++++++++-------- src/main.h | 7 ++++++- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index f858ade4c9c6..f56189cda8b1 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -187,6 +187,7 @@ class CMainParams : public CChainParams nEnforceNewSporkKey = 1566860400; //!> Sporks signed after Monday, August 26, 2019 11:00:00 PM GMT must use the new spork key nRejectOldSporkKey = 1569538800; //!> Fully reject old spork key after Thursday, September 26, 2019 11:00:00 PM GMT nBlockStakeModifierlV2 = 1967000; + // Public coin spend enforcement nPublicZCSpends = 1880000; nPublicZCSpendsV4 = 2880000; @@ -198,6 +199,9 @@ class CMainParams : public CChainParams nFakeSerialBlockheightEnd = 1686229; nSupplyBeforeFakeSerial = 4131563 * COIN; // zerocoin supply at block nFakeSerialBlockheightEnd + // Cold Staking enforcement + nColdStakingStart = 2880000; + /** * Build the genesis block. Note that the output of the genesis coinbase cannot * be spent as it did not originally exist in the database. @@ -327,6 +331,7 @@ class CTestNetParams : public CMainParams nEnforceNewSporkKey = 1566860400; //!> Sporks signed after Monday, August 26, 2019 11:00:00 PM GMT must use the new spork key nRejectOldSporkKey = 1569538800; //!> Reject old spork key after Thursday, September 26, 2019 11:00:00 PM GMT nBlockStakeModifierlV2 = 1214000; + // Public coin spend enforcement nPublicZCSpends = 1106100; nPublicZCSpendsV4 = 2106100; @@ -338,6 +343,9 @@ class CTestNetParams : public CMainParams nFakeSerialBlockheightEnd = -1; nSupplyBeforeFakeSerial = 0; + // Cold Staking enforcement + nColdStakingStart = 2106100; + //! Modify the testnet genesis block so the timestamp is valid for a later start. genesis.nTime = 1454124731; genesis.nNonce = 2402015; @@ -425,6 +433,7 @@ class CRegTestParams : public CTestNetParams nBlockFirstFraudulent = 999999999; //First block that bad serials emerged nBlockLastGoodCheckpoint = 999999999; //Last valid accumulator checkpoint nBlockStakeModifierlV2 = std::numeric_limits::max(); // max integer value (never switch on regtest) + // Public coin spend enforcement nPublicZCSpends = 350; nPublicZCSpendsV4 = 450; @@ -435,6 +444,9 @@ class CRegTestParams : public CTestNetParams // Fake Serial Attack nFakeSerialBlockheightEnd = -1; + // Cold Staking enforcement + nColdStakingStart = 251; + //! Modify the regtest genesis block so the timestamp is valid for a later start. genesis.nTime = 1454124731; genesis.nNonce = 2402015; diff --git a/src/chainparams.h b/src/chainparams.h index b19c0343129b..7c1331873f59 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -143,6 +143,8 @@ class CChainParams bool IsStakeModifierV2(const int nHeight) const { return nHeight >= nBlockStakeModifierlV2; } int NewSigsActive(const int nHeight) const { return nHeight >= nBlockEnforceNewMessageSignatures; } int Zerocoin_PublicSpendVersion(const int nHeight) const; + bool Cold_Staking_Enabled(const int height) const { return height >= nColdStakingStart; } + int Block_Enforce_Cold_Staking() const { return nColdStakingStart; } // fake serial attack int Zerocoin_Block_EndFakeSerial() const { return nFakeSerialBlockheightEnd; } @@ -228,6 +230,7 @@ class CChainParams int nPublicZCSpendsV4; int nBlockStakeModifierlV2; int nBlockEnforceNewMessageSignatures; + int nColdStakingStart; // fake serial attack int nFakeSerialBlockheightEnd = 0; diff --git a/src/main.cpp b/src/main.cpp index cc76f73bd01b..1d33b41f278a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1176,7 +1176,7 @@ bool CheckZerocoinSpend(const CTransaction& tx, bool fVerifySignature, CValidati } -bool CheckColdStake(const CTransaction& tx, CValidationState& state) +bool CheckColdStake(const CTransaction& tx, CValidationState& state, bool fColdStakingActive) { CTxOut prevOut; if(!GetOutput(tx.vin[0].prevout.hash, tx.vin[0].prevout.n, state, prevOut)) @@ -1185,6 +1185,9 @@ bool CheckColdStake(const CTransaction& tx, CValidationState& state) if (!prevOut.scriptPubKey.IsPayToColdStaking()) return true; + if (!fColdStakingActive) + return state.DoS(100, error("%s : invalid input", __func__), REJECT_INVALID, "coldstake-not-active"); + // spending to the same contract if (prevOut.scriptPubKey != tx.vout[1].scriptPubKey) return state.DoS(100, error("%s : invalid scripts", __func__), REJECT_INVALID, "bad-txns-cold-stake"); @@ -1192,7 +1195,7 @@ bool CheckColdStake(const CTransaction& tx, CValidationState& state) return true; } -bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fRejectBadUTXO, CValidationState& state, bool fFakeSerialAttack) +bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fRejectBadUTXO, CValidationState& state, bool fFakeSerialAttack, bool fColdStakingActive) { // Basic checks that don't depend on any context if (tx.vin.empty()) @@ -1214,7 +1217,8 @@ bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fReject for (const CTxOut& txout : tx.vout) { if (txout.IsEmpty() && !tx.IsCoinBase() && !tx.IsCoinStake()) return state.DoS(100, error("CheckTransaction(): txout empty for user transaction")); - + if (txout.scriptPubKey.IsPayToColdStaking() && !fColdStakingActive) + return state.DoS(100, error("CheckTransaction(): cold staking not active")); if (txout.nValue < 0) return state.DoS(100, error("CheckTransaction() : txout.nValue negative"), REJECT_INVALID, "bad-txns-vout-negative"); @@ -1232,7 +1236,7 @@ bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fReject } // Additional check for cold staking - if(tx.IsCoinStake() && !tx.HasZerocoinSpendInputs() && !CheckColdStake(tx, state)) + if (tx.IsCoinStake() && !tx.HasZerocoinSpendInputs() && !CheckColdStake(tx, state, fColdStakingActive)) return state.DoS(100, error("CheckTransaction() : invalid cold stake"), REJECT_INVALID, "bad-txns-cold-stake"); std::set vInOutPoints; @@ -1368,10 +1372,11 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa return state.DoS(10, error("%s : Zerocoin transactions are temporarily disabled for maintenance", __func__), REJECT_INVALID, "bad-tx"); + // Cold staking and zerocoin enforcement int chainHeight = chainActive.Height(); - if (!CheckTransaction(tx, chainHeight >= Params().Zerocoin_StartHeight(), true, state, isBlockBetweenFakeSerialAttackRange(chainHeight))) - return state.DoS(100, error("%s : CheckTransaction failed", - __func__), REJECT_INVALID, "bad-tx"); + bool fColdStakingActive = Params().Cold_Staking_Enabled(chainHeight); + if (!CheckTransaction(tx, chainHeight >= Params().Zerocoin_StartHeight(), true, state, isBlockBetweenFakeSerialAttackRange(chainHeight), fColdStakingActive)) + return state.DoS(100, error("%s : CheckTransaction failed", __func__), REJECT_INVALID, "bad-tx"); // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) @@ -4514,6 +4519,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo // Check transactions bool fZerocoinActive = block.GetBlockTime() > Params().Zerocoin_StartTime(); + bool fColdStakingActive = Params().Cold_Staking_Enabled(nHeight); std::vector vBlockSerials; // TODO: Check if this is ok... blockHeight is always the tip or should we look for the prevHash and get the height? int blockHeight = chainActive.Height() + 1; @@ -4523,7 +4529,8 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo fZerocoinActive, blockHeight >= Params().Zerocoin_Block_EnforceSerialRange(), state, - isBlockBetweenFakeSerialAttackRange(blockHeight) + isBlockBetweenFakeSerialAttackRange(blockHeight), + fColdStakingActive )) return error("%s : CheckTransaction failed", __func__); diff --git a/src/main.h b/src/main.h index 9c20277f0a7c..bdb6f61f3bfb 100644 --- a/src/main.h +++ b/src/main.h @@ -351,11 +351,16 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& ma */ bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& view, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector* pvChecks = NULL); +/* + * Check cold stakes P2CS script rules and enforcement activation + */ +bool CheckColdStake(const CTransaction& tx, CValidationState& state, bool fColdStakingActive); + /** Apply the effects of this transaction on the UTXO set represented by view */ void UpdateCoins(const CTransaction& tx, CValidationState& state, CCoinsViewCache& inputs, CTxUndo& txundo, int nHeight); /** Context-independent validity checks */ -bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fRejectBadUTXO, CValidationState& state, bool fFakeSerialAttack = false); +bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fRejectBadUTXO, CValidationState& state, bool fFakeSerialAttack = false, bool fColdStakingActive=false); bool CheckZerocoinMint(const uint256& txHash, const CTxOut& txout, CValidationState& state, bool fCheckOnly = false); bool CheckZerocoinSpend(const CTransaction& tx, bool fVerifySignature, CValidationState& state, bool fFakeSerialAttack = false); bool ContextualCheckZerocoinSpend(const CTransaction& tx, const libzerocoin::CoinSpend* spend, CBlockIndex* pindex, const uint256& hashBlock); From 8bc0893bbcb47ce89732de0c730974bb522376ef Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Jul 2019 06:53:32 +0200 Subject: [PATCH 20/59] [Wallet/DB] Add Delegators-whitelist protection for cold staker --- src/keystore.cpp | 28 ++++++++++++++++- src/keystore.h | 13 ++++++++ src/primitives/transaction.cpp | 8 +++++ src/primitives/transaction.h | 1 + src/wallet/wallet.cpp | 55 ++++++++++++++++++++++++++++++++-- src/wallet/wallet.h | 11 +++++-- src/wallet/wallet_ismine.cpp | 10 +++---- src/wallet/walletdb.cpp | 20 +++++++++++++ src/wallet/walletdb.h | 3 ++ 9 files changed, 139 insertions(+), 10 deletions(-) diff --git a/src/keystore.cpp b/src/keystore.cpp index 4cd5e549094a..aaf1ac5f9f21 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -113,6 +113,32 @@ bool CBasicKeyStore::HaveMultiSig() const return (!setMultiSig.empty()); } +bool CBasicKeyStore::AddDelegator(const CKeyID& keyID) +{ + LOCK(cs_KeyStore); + setDelegators.insert(keyID); + return true; +} + +bool CBasicKeyStore::RemoveDelegator(const CKeyID& keyID) +{ + LOCK(cs_KeyStore); + setDelegators.erase(keyID); + return true; +} + +bool CBasicKeyStore::HaveDelegator(const CKeyID& keyID) const +{ + LOCK(cs_KeyStore); + return setDelegators.count(keyID) > 0; +} + +bool CBasicKeyStore::HaveDelegator() const +{ + LOCK(cs_KeyStore); + return (!setDelegators.empty()); +} + bool CBasicKeyStore::HaveKey(const CKeyID& address) const { bool result; @@ -147,4 +173,4 @@ bool CBasicKeyStore::GetKey(const CKeyID& address, CKey& keyOut) const } } return false; -} \ No newline at end of file +} diff --git a/src/keystore.h b/src/keystore.h index 2a3e3bd052f0..42290b7f07e2 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -51,12 +51,19 @@ class CKeyStore virtual bool RemoveMultiSig(const CScript& dest) = 0; virtual bool HaveMultiSig(const CScript& dest) const = 0; virtual bool HaveMultiSig() const = 0; + + //! Support for ColdStaking + virtual bool AddDelegator(const CKeyID& dest) = 0; + virtual bool RemoveDelegator(const CKeyID& dest) = 0; + virtual bool HaveDelegator(const CKeyID& dest) const = 0; + virtual bool HaveDelegator() const = 0; }; typedef std::map KeyMap; typedef std::map ScriptMap; typedef std::set WatchOnlySet; typedef std::set MultiSigScriptSet; +typedef std::set DelegatorsSet; /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore @@ -66,6 +73,7 @@ class CBasicKeyStore : public CKeyStore ScriptMap mapScripts; WatchOnlySet setWatchOnly; MultiSigScriptSet setMultiSig; + DelegatorsSet setDelegators; public: bool AddKeyPubKey(const CKey& key, const CPubKey& pubkey); @@ -86,6 +94,11 @@ class CBasicKeyStore : public CKeyStore virtual bool RemoveMultiSig(const CScript& dest); virtual bool HaveMultiSig(const CScript& dest) const; virtual bool HaveMultiSig() const; + + virtual bool AddDelegator(const CKeyID& keyID); + virtual bool RemoveDelegator(const CKeyID& keyID); + virtual bool HaveDelegator(const CKeyID& keyID) const; + virtual bool HaveDelegator() const; }; typedef std::vector > CKeyingMaterial; diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 218ad72fb3bc..2fd6f88f6483 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -232,7 +232,15 @@ bool CTransaction::CheckColdStake() const // additional checks in CheckTransaction return true; +} +bool CTransaction::HasP2CSOutputs() const +{ + for(const CTxOut& txout : vout) { + if (txout.scriptPubKey.IsPayToColdStaking()) + return true; + } + return false; } CAmount CTransaction::GetValueOut() const diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 509e8f5529de..5e462d4a0d31 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -284,6 +284,7 @@ class CTransaction bool IsCoinStake() const; bool CheckColdStake() const; + bool HasP2CSOutputs() const; friend bool operator==(const CTransaction& a, const CTransaction& b) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7877c2b262b3..699e54870732 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -258,6 +258,36 @@ bool CWallet::RemoveWatchOnly(const CScript& dest) return true; } +bool CWallet::AddDelegator(const CKeyID& keyID) +{ + if (!CCryptoKeyStore::AddDelegator(keyID)) + return false; + nTimeFirstKey = 1; // No birthday information for cold keys. + NotifyDelegatorChanged(true); + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteDelegator(keyID); +} + +bool CWallet::RemoveDelegator(const CKeyID& keyID) +{ + AssertLockHeld(cs_wallet); + if (!CCryptoKeyStore::RemoveDelegator(keyID)) + return false; + if (!HaveDelegator()) + NotifyDelegatorChanged(false); + if (fFileBacked) + if (!CWalletDB(strWalletFile).EraseDelegator(keyID)) + return false; + + return true; +} + +bool CWallet::LoadDelegator(const CKeyID& keyID) +{ + return CCryptoKeyStore::AddDelegator(keyID); +} + bool CWallet::LoadWatchOnly(const CScript& dest) { return CCryptoKeyStore::AddWatchOnly(dest); @@ -762,8 +792,28 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD * pblock is optional, but should be provided if the transaction is known to be in a block. * If fUpdate is true, existing transactions will be updated. */ -bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate) -{ +bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fOnlyCold) +{ + // if fOnlyCold=true, skip stakes or txes with no P2CS script + if (fOnlyCold) { + bool hasP2CS = true; + if (!tx.HasP2CSOutputs()) { + hasP2CS = false; + for (const CTxIn& in : tx.vin) { + // check the inputs. if none is spending a P2CS, skip + CTransaction prevtx; + uint256 hashBlock = 0; + if (!GetTransaction(in.prevout.hash, prevtx, hashBlock)) + continue; + if (prevtx.HasP2CSOutputs()) { + hasP2CS = true; + break; + } + } + } + if (!hasP2CS) + return false; + } { AssertLockHeld(cs_wallet); @@ -3010,6 +3060,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address) for (const PAIRTYPE(std::string, std::string) & item : mapAddressBook[address].destdata) { CWalletDB(strWalletFile).EraseDestData(strAddress, item.first); } + } mapAddressBook.erase(address); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 98b44fa8f43f..06d4184eb634 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -399,6 +399,11 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) bool LoadWatchOnly(const CScript& dest); + //! Delegator addresseses for cold staking + bool AddDelegator(const CKeyID& keyID); + bool RemoveDelegator(const CKeyID& keyID); + bool LoadDelegator(const CKeyID& keyID); + //! Adds a MultiSig address to the store, and saves it to disk. bool AddMultiSig(const CScript& dest); bool RemoveMultiSig(const CScript& dest); @@ -421,7 +426,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void MarkDirty(); bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb); void SyncTransaction(const CTransaction& tx, const CBlock* pblock); - bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate); + bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fOnlyCold = false); void EraseFromWallet(const uint256& hash); int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false, bool fromStartup = false); void ReacceptWalletTransactions(bool fFirstLoad = false); @@ -508,7 +513,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface DBErrors ZapWalletTx(std::vector& vWtx); bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose); - bool DelAddressBook(const CTxDestination& address); bool UpdatedTransaction(const uint256& hashTx); @@ -553,6 +557,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /** MultiSig address added */ boost::signals2::signal NotifyMultiSigChanged; + /** Delegator address added */ + boost::signals2::signal NotifyDelegatorChanged; + /** zPIV reset */ boost::signals2::signal NotifyzPIVReset; diff --git a/src/wallet/wallet_ismine.cpp b/src/wallet/wallet_ismine.cpp index b9cf3a63a54b..a20f33126099 100644 --- a/src/wallet/wallet_ismine.cpp +++ b/src/wallet/wallet_ismine.cpp @@ -78,14 +78,14 @@ isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) break; } case TX_COLDSTAKE: { - keyID = CKeyID(uint160(vSolutions[0])); - bool stakeKeyIsMine = keystore.HaveKey(keyID); - keyID = CKeyID(uint160(vSolutions[1])); - bool spendKeyIsMine = keystore.HaveKey(keyID); + CKeyID stakeKeyID = CKeyID(uint160(vSolutions[0])); + bool stakeKeyIsMine = keystore.HaveKey(stakeKeyID); + CKeyID ownerKeyID = CKeyID(uint160(vSolutions[1])); + bool spendKeyIsMine = keystore.HaveKey(ownerKeyID); if (spendKeyIsMine && stakeKeyIsMine) return ISMINE_SPENDABLE_STAKEABLE; - else if (stakeKeyIsMine) + else if (stakeKeyIsMine && keystore.HaveDelegator(ownerKeyID)) return ISMINE_COLD; else if (spendKeyIsMine) return ISMINE_SPENDABLE_DELEGATED; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 51f64655e75b..5fe577dbdebf 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -194,6 +194,19 @@ bool CWalletDB::EraseMultiSig(const CScript& dest) return Erase(std::make_pair(std::string("multisig"), dest)); } +bool CWalletDB::WriteDelegator(const CKeyID& keyID) +{ + nWalletDBUpdated++; + return Write(std::make_pair(std::string("delegator"), keyID), '1'); +} + +bool CWalletDB::EraseDelegator(const CKeyID& keyID) +{ + nWalletDBUpdated++; + return Erase(std::make_pair(std::string("delegator"), keyID)); +} + + bool CWalletDB::WriteBestBlock(const CBlockLocator& locator) { nWalletDBUpdated++; @@ -546,6 +559,13 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW // MultiSig addresses have no birthday information for now, // so set the wallet birthday to the beginning of time. pwallet->nTimeFirstKey = 1; + } else if (strType == "delegator") { + CKeyID keyID; + ssKey >> keyID; + char fYes; + ssValue >> fYes; + if (fYes == '1') + pwallet->LoadDelegator(keyID); } else if (strType == "key" || strType == "wkey") { CPubKey vchPubKey; ssKey >> vchPubKey; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 3b24e2194a5d..3948da3b1ae3 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -113,6 +113,9 @@ class CWalletDB : public CDB bool WriteMultiSig(const CScript& script); bool EraseMultiSig(const CScript& script); + bool WriteDelegator(const CKeyID& keyID); + bool EraseDelegator(const CKeyID& keyID); + bool WriteBestBlock(const CBlockLocator& locator); bool ReadBestBlock(CBlockLocator& locator); From 8b56c33dd6180cf1effb31ba5af44930e908e290 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Jul 2019 06:55:07 +0200 Subject: [PATCH 21/59] [RPC] define: delegatoradd, delegatorremove --- src/rpc/client.cpp | 1 + src/rpc/server.cpp | 2 + src/rpc/server.h | 2 + src/wallet/rpcwallet.cpp | 99 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 459c5afffbb0..3801635151b2 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -42,6 +42,7 @@ static const CRPCConvertParam vRPCConvertParams[] = {"rawdelegatestake", 1}, {"rawdelegatestake", 3}, {"rawdelegatestake", 4}, + {"delegatoradd", 1}, {"sendtoaddress", 1}, {"sendtoaddressix", 1}, {"settxfee", 0}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index c92637d832ef..a576ac315a77 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -448,6 +448,8 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "walletlock", &walletlock, true, false, true}, {"wallet", "walletpassphrasechange", &walletpassphrasechange, true, false, true}, {"wallet", "walletpassphrase", &walletpassphrase, true, false, true}, + {"wallet", "delegatoradd", &delegatoradd, true, false, true}, + {"wallet", "delegatorremove", &delegatorremove, true, false, true}, {"zerocoin", "createrawzerocoinstake", &createrawzerocoinstake, false, false, true}, {"zerocoin", "createrawzerocoinpublicspend", &createrawzerocoinpublicspend, false, false, true}, diff --git a/src/rpc/server.h b/src/rpc/server.h index 770199c4a2ec..75d1e9a166ca 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -214,6 +214,8 @@ extern UniValue estimatepriority(const UniValue& params, bool fHelp); extern UniValue delegatestake(const UniValue& params, bool fHelp); // in rpcwallet.cpp extern UniValue rawdelegatestake(const UniValue& params, bool fHelp); +extern UniValue delegatoradd(const UniValue& params, bool fHelp); +extern UniValue delegatorremove(const UniValue& params, bool fHelp); extern UniValue getnewaddress(const UniValue& params, bool fHelp); extern UniValue getnewstakingaddress(const UniValue& params, bool fHelp); extern UniValue getaccountaddress(const UniValue& params, bool fHelp); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0497044f2f1a..8ccdc3142d02 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -146,6 +146,105 @@ UniValue getnewstakingaddress(const UniValue& params, bool fHelp) return GetNewAddressFromAccount("coldstaking", true, params).ToString(); } +int RescanStakeDelegations() +{ + int ret = 0; + LOCK2(cs_main, pwalletMain->cs_wallet); + if (!Params().Cold_Staking_Enabled(chainActive.Tip()->nHeight)) + return ret; + + CBlockIndex *pindex = chainActive[Params().Block_Enforce_Cold_Staking()]; + + while(pindex) { + CBlock block; + ReadBlockFromDisk(block, pindex); + for (CTransaction& tx : block.vtx) { + if (pwalletMain->AddToWalletIfInvolvingMe(tx, &block, true, true)) + ret++; + } + pindex = chainActive.Next(pindex); + } + return ret; + +} + +UniValue delegatoradd(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw std::runtime_error( + "delegatoradd \"addr\" ( fRescan )\n" + "\nAdd the provided address into the allowed delegators keystore.\n" + "This enables the staking of coins delegated to this wallet, owned by \n" + + "\nArguments:\n" + "1. \"addr\" (string, required) The address to whitelist\n" + "2. fRescan (bool, optional, default=false) rescan blockchain. Set to true only if\n" + " some stake-delegation from this address has already been sent.\n" + " If set to true, it might take few minutes to complete the scan.\n" + + "\nResult:\n" + "true|false (boolean) true if successful.\n" + + "\nExamples:\n" + + HelpExampleCli("delegatoradd", "DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6") + + HelpExampleCli("delegatoradd", "DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6 true") + + HelpExampleRpc("delegatoradd", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\"")); + + CBitcoinAddress address(params[0].get_str()); + if (!address.IsValid() || address.IsStakingAddress()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); + + bool fRescan = params.size() > 1 && !params[1].isNull() ? params[1].get_bool() : false; + + CKeyID keyID; + if (!address.GetKeyID(keyID)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to get KeyID from PIVX address"); + + if (pwalletMain->HaveDelegator(keyID)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Delegator already whitelisted"); + + bool res = pwalletMain->AddDelegator(keyID); + + if (res && fRescan) { + // trigger rescan + int ret = RescanStakeDelegations(); + LogPrintf("%s: Rescan for Stake delegation complete. %d inputs added/modified", __func__, ret); + } + + return res; +} + +UniValue delegatorremove(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw std::runtime_error( + "delegatorremove \"addr\"\n" + "\nRemoves the provided address from the allowed delegators keystore.\n" + "This disbles the staking of coins delegated to this wallet, owned by \n" + + "\nArguments:\n" + "1. \"addr\" (string, required) The address to whitelist\n" + + "\nResult:\n" + "true|false (boolean) true if successful.\n" + + "\nExamples:\n" + + HelpExampleCli("whitelistdelegator", "DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6") + + HelpExampleRpc("whitelistdelegator", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\"")); + + CBitcoinAddress address(params[0].get_str()); + if (!address.IsValid() || address.IsStakingAddress()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); + + CKeyID keyID; + if (!address.GetKeyID(keyID)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to get KeyID from PIVX address"); + + if (!pwalletMain->HaveDelegator(keyID)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Delegator not whitelisted"); + + return pwalletMain->RemoveDelegator(keyID); +} CBitcoinAddress GetAccountAddress(std::string strAccount, bool bForceNew = false) { From 1d071f4762c09ae4e0f6b7b172aa832be85312c6 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 10 Jul 2019 12:55:02 +0200 Subject: [PATCH 22/59] [Tests] Add Cold Staking functional testing --- test/functional/feature_coldStaking.py | 436 ++++++++++++++++++++ test/functional/test_framework/address.py | 125 +++++- test/functional/test_framework/pivx_node.py | 82 ++++ test/functional/test_runner.py | 2 + 4 files changed, 624 insertions(+), 21 deletions(-) create mode 100755 test/functional/feature_coldStaking.py create mode 100644 test/functional/test_framework/pivx_node.py diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py new file mode 100755 index 000000000000..ef48b3d70eca --- /dev/null +++ b/test/functional/feature_coldStaking.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The PIVX developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# -*- coding: utf-8 -*- + +from io import BytesIO +from struct import pack +import time + +from test_framework.address import key_to_p2pkh, wif_to_privkey +from test_framework.authproxy import JSONRPCException +from test_framework.blocktools import create_coinbase, create_block +from test_framework.key import CECKey +from test_framework.messages import CTransaction, CTxIn, CTxOut, COutPoint, COIN +from test_framework.mininode import network_thread_start +from test_framework.pivx_node import PivxTestNode +from test_framework.script import CScript, OP_CHECKSIG +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import hash256, connect_nodes_bi, p2p_port, bytes_to_hex_str, hex_str_to_bytes + +# filter utxos based on first 5 bytes of scriptPubKey +def getDelegatedUtxos(utxos): + return [x for x in utxos if x["scriptPubKey"][:10] == '76a97b63d1'] + + +class PIVX_ColdStakingTest(BitcoinTestFramework): + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + self.extra_args = [['-staking=1']]*self.num_nodes + + + def setup_network(self): + ''' Can't rely on syncing all the nodes when staking=1 + ''' + self.setup_nodes() + for i in range(self.num_nodes - 1): + for j in range(i+1, self.num_nodes): + connect_nodes_bi(self.nodes, i, j) + + + def init_test(self): + title = "*** Starting %s ***" % self.__class__.__name__ + underline = "-" * len(title) + self.log.info("\n\n%s\n%s\n%s\n", title, underline, self.description) + self.DEFAULT_FEE = 0.05 + # Setup the p2p connections and start up the network thread. + self.test_nodes = [] + for i in range(self.num_nodes): + self.test_nodes.append(PivxTestNode()) + self.test_nodes[i].peer_connect('127.0.0.1', p2p_port(i)) + + network_thread_start() # Start up network handling in another thread + + # Let the test nodes get in sync + for i in range(self.num_nodes): + self.test_nodes[i].wait_for_verack() + + + def run_test(self): + self.description = "Performs tests on the Cold Staking P2CS implementation" + self.init_test() + LAST_POW_BLOCK = 250 + NUM_OF_INPUTS = 20 + INPUT_VALUE = 50 + INITAL_MINED_BLOCKS = LAST_POW_BLOCK - 40 + + # nodes[0] - coin-owner + # nodes[1] - cold-staker + + # 1) nodes[0] mines first blocks. + # ----------------------------------- + print("*** 1 ***") + self.log.info("Mining %d blocks..." % INITAL_MINED_BLOCKS) + for i in range(1, INITAL_MINED_BLOCKS+1): + self.nodes[0].generate(1) + if i % 25 == 0: + self.log.info("%d Blocks mined." % i) + self.sync_all() + + + # 2) nodes[1] generates a cold-staking address. + # --------------------------------------------- + print("*** 2 ***") + owner_address = self.nodes[0].getnewaddress() + self.log.info("Owner Address: %s" % owner_address) + staker_address = self.nodes[1].getnewstakingaddress() + self.log.info("Staking Address: %s" % staker_address) + + + # 3) Check enforcement. + # --------------------- + print("*** 3 ***") + self.sync_all() + self.log.info("Creating a stake-delegation tx before cold staking enforcement...") + try: + self.nodes[0].delegatestake(staker_address, INPUT_VALUE, owner_address, False, False, True) + assert(False) + except JSONRPCException as e: + assert("The transaction was rejected!" in str(e)) + self.log.info("Good. Cold Staking NOT ACTIVE yet.") + pass + self.log.info("Mining 42 blocks to get to cold staking activation...") + for i in range(1, 43): + self.nodes[0].generate(1) + self.sync_all() + if i % 7 == 0: + self.log.info("%d Blocks mined." % i) + + + # 4) nodes[0] delegates a number of inputs for nodes[1] to stake em. + # ------------------------------------------------------------------ + print("*** 4 ***") + self.log.info("First check warning when using external addresses...") + try: + self.nodes[0].delegatestake(staker_address, INPUT_VALUE, "yCgCXC8N5VThhfiaVuKaNLkNnrWduzVnoT") + assert(False) + except JSONRPCException as e: + assert("Set 'fExternalOwner' argument to true, in order to force the stake delegation" in str(e)) + self.log.info("Good. Warning triggered.") + self.log.info("Now force the use of external address creating (but not sending) the delegation...") + res = self.nodes[0].rawdelegatestake(staker_address, INPUT_VALUE, "yCgCXC8N5VThhfiaVuKaNLkNnrWduzVnoT", True) + assert(res is not None and res != "") + self.log.info("Good. Warning NOT triggered.") + self.log.info("Now delegate with internal owner address..") + self.log.info("Creating %d stake-delegation txes..." % NUM_OF_INPUTS) + for i in range(NUM_OF_INPUTS): + res = self.nodes[0].delegatestake(staker_address, INPUT_VALUE, owner_address) + assert(res != None and res["txid"] != None and res["txid"] != "") + assert(res["owner_address"] == owner_address) + assert(res["staker_address"] == staker_address) + self.nodes[0].generate(1) + self.sync_all() + self.log.info("%d Txes created." % NUM_OF_INPUTS) + # check balances: + self.expected_balance = NUM_OF_INPUTS * INPUT_VALUE + self.checkBalances(0) + + + # 5) check that the owner (nodes[0]) can spend the coins. + # ------------------------------------------------------- + print("*** 5 ***") + self.log.info("Spending back one of the delegated UTXOs...") + delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) + assert(len(delegated_utxos) > 0) + u = delegated_utxos[0] + txhash = self.spendUTXOwithNode(u, 0) + assert(txhash != None) + self.log.info("Good. Owner was able to spend - tx: %s" % str(txhash)) + self.nodes[0].generate(1) + self.sync_all() + # check balances after spend. + self.expected_balance -= float(u["amount"]) + self.checkBalances(0) + self.log.info("Balances check out after spend") + + + # 6) check that the staker CANNOT use the coins to stake yet. + # He needs to whitelist the owner first. + # ----------------------------------------------------------- + self.log.info("Staking 15 blocks to mature P2CS stake delegations...") + for i in range(1, 16): + self.nodes[0].generate(1) + self.sync_all() + if i % 5 == 0: + self.log.info("%d Blocks added." % i) + print("*** 6 ***") + self.log.info("Trying to generate a cold-stake block before whitelisting the owner...") + try: + self.nodes[1].generate(1) + assert(False) + except JSONRPCException as e: + assert ("Couldn't create new block" in str(e)) + self.log.info("Nice. Cold staker was NOT able to create the block yet.") + pass + self.log.info("Whitelisting the owner...") + self.nodes[1].delegatoradd(owner_address, True) + self.log.info("Delegator address %s whitelisted" % owner_address) + # check that's been whitelisted for real + try: + self.nodes[1].delegatoradd(owner_address) + assert(False) + except JSONRPCException as e: + assert("Delegator already whitelisted" in str(e)) + # check balances (delegated in nodes[0] and cold in nodes[1]) + self.checkBalances() + + + # 7) check that the staker CANNOT spend the coins. + # ------------------------------------------------ + print("*** 7 ***") + self.log.info("Trying to spend one of the delegated UTXOs with the cold-staking key...") + delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) + assert(len(delegated_utxos) > 0) + u = delegated_utxos[0] + try: + self.spendUTXOwithNode(u, 1) + assert(False) + except JSONRPCException as e: + assert("Script failed an OP_CHECKCOLDSTAKEVERIFY operation" in str(e)) + self.log.info("Good. Cold staker was NOT able to spend (failed OP_CHECKCOLDSTAKEVERIFY)") + pass + self.nodes[0].generate(1) + self.sync_all() + + + # 8) check that the staker can use the coins to stake a block with internal miner. + # -------------------------------------------------------------------------------- + print("*** 8 ***") + self.log.info("Generating one valid cold-stake block...") + self.nodes[1].generate(1) + self.log.info("New block created by cold-staking. Trying to submit...") + newblockhash = self.nodes[1].getbestblockhash() + self.log.info("Block %s submitted" % newblockhash) + # Verify that nodes[0] accepts it + self.sync_all() + self.nodes[0].getblock(newblockhash) + self.log.info("Great. Cold-staked block was accepted!") + # check balances after staked block. + self.expected_balance += 250 + self.checkBalances() + self.log.info("Balances check out after staked block") + + + # 9) check that the staker can use the coins to stake a block with a rawtransaction. + # ---------------------------------------------------------------------------------- + print("*** 9 ***") + self.log.info("Generating another valid cold-stake block...") + stakeable_coins = getDelegatedUtxos(self.nodes[0].listunspent()) + block_n = self.nodes[1].getblockcount() + block_hash = self.nodes[1].getblockhash(block_n) + prevouts = self.get_prevouts(stakeable_coins, 1) + assert(len(prevouts) > 0) + # Create the block + new_block = self.create_block(block_hash, prevouts, block_n+1, 1, staker_address) + self.log.info("New block created (rawtx) by cold-staking. Trying to submit...") + # Try to submit the block + ret = self.nodes[1].submitblock(bytes_to_hex_str(new_block.serialize())) + self.log.info("Block %s submitted." % new_block.hash) + assert(ret is None) + # Verify that nodes[0] accepts it + self.sync_all() + self.nodes[0].getblock(new_block.hash) + self.log.info("Great. Cold-staked block was accepted!") + # check balances after staked block. + self.expected_balance += 250 + self.checkBalances() + self.log.info("Balances check out after staked block") + + + # 10) check that the staker cannot stake a block changing the coinstake scriptPubkey. + # ---------------------------------------------------------------------------------- + print("*** 10 ***") + self.log.info("Generating one invalid cold-stake block (changing coinstake output)...") + stakeable_coins = getDelegatedUtxos(self.nodes[0].listunspent()) + block_n = self.nodes[1].getblockcount() + block_hash = self.nodes[1].getblockhash(block_n) + prevouts = self.get_prevouts(stakeable_coins, 1) + assert(len(prevouts) > 0) + # Create the block + new_block = self.create_block(block_hash, prevouts, block_n+1, 1, staker_address, fInvalid=True) + self.log.info("New block created (rawtx) by cold-staking. Trying to submit...") + # Try to submit the block + ret = self.nodes[1].submitblock(bytes_to_hex_str(new_block.serialize())) + self.log.info("Block %s submitted." % new_block.hash) + assert("bad-txns-cold-stake" in ret) + # Verify that nodes[0] rejects it + self.sync_all() + try: + self.nodes[0].getblock(new_block.hash) + except JSONRPCException as e: + assert("Block not found" in str(e)) + self.log.info("Great. Malicious cold-staked block was NOT accepted!") + pass + + + # 11) Now node[0] gets mad and spends all the delegated coins, voiding the P2CS contracts. + # ---------------------------------------------------------------------------------------- + self.log.info("Let's void the contracts.") + self.nodes[0].generate(1) + self.sync_all() + print("*** 11 ***") + self.log.info("Cancel the stake delegation spending the cold stakes...") + delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) + txhash = self.spendUTXOsWithNode(delegated_utxos, 0) + assert(txhash != None) + self.log.info("Good. Owner was able to void the stake delegations - tx: %s" % str(txhash)) + self.nodes[0].generate(1) + self.sync_all() + # check balances after big spend. + self.expected_balance = 2 * (INPUT_VALUE + 250) + self.checkBalances() + self.log.info("Balances check out after the delegations have been voided.") + + + # 12) check that coinstaker is empty and can no longer stake. + # ----------------------------------------------------------- + print("*** 12 ***") + self.log.info("Trying to generate one cold-stake block again...") + try: + self.nodes[1].generate(1) + assert(False) + except JSONRPCException as e: + assert ("Couldn't create new block" in str(e)) + self.log.info("Cigar. Cold staker was NOT able to create any more blocks.\n") + pass + + + + + def checkBalances(self, cold=None): + w_info = self.nodes[0].getwalletinfo() + self.log.info("OWNER - Delegated %f / Cold %f" % ( + float(w_info["delegated_balance"]), w_info["cold_staking_balance"])) + assert(float(w_info["delegated_balance"]) == self.expected_balance) + assert (float(w_info["cold_staking_balance"]) == 0) + if cold != None: + coldbal = cold + else: + coldbal = self.expected_balance + w_info = self.nodes[1].getwalletinfo() + self.log.info("STAKER - Delegated %f / Cold %f" % ( + float(w_info["delegated_balance"]), w_info["cold_staking_balance"])) + assert(float(w_info["delegated_balance"]) == 0) + assert (float(w_info["cold_staking_balance"]) == coldbal) + + + + def spendUTXOwithNode(self, utxo, node_n): + new_addy = self.nodes[node_n].getnewaddress() + inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}] + out_amount = (float(utxo["amount"]) - self.DEFAULT_FEE) + outputs = {} + outputs[new_addy] = out_amount + spendingTx = self.nodes[node_n].createrawtransaction(inputs, outputs) + spendingTx_signed = self.nodes[node_n].signrawtransaction(spendingTx) + return self.nodes[node_n].sendrawtransaction(spendingTx_signed["hex"]) + + + def spendUTXOsWithNode(self, utxos, node_n): + new_addy = self.nodes[node_n].getnewaddress() + inputs = [] + outputs = {} + outputs[new_addy] = 0 + for utxo in utxos: + inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]}) + outputs[new_addy] += float(utxo["amount"]) + outputs[new_addy] -= self.DEFAULT_FEE + spendingTx = self.nodes[node_n].createrawtransaction(inputs, outputs) + spendingTx_signed = self.nodes[node_n].signrawtransaction(spendingTx) + return self.nodes[node_n].sendrawtransaction(spendingTx_signed["hex"]) + + + + def get_prevouts(self, coins, node_n, confs=15): + api = self.nodes[node_n] + prevouts = {} + for utxo in coins: + prevtx = api.getrawtransaction(utxo['txid'], 1) + prevScript = prevtx['vout'][utxo['vout']]['scriptPubKey']['hex'] + prevtx_btime = prevtx['blocktime'] + prevtx_bhash = prevtx['blockhash'] + modifier = 0 + if utxo['confirmations'] < confs: + continue + o = COutPoint(int(utxo['txid'], 16), utxo['vout']) + prevouts[o] = (int(utxo['amount']) * COIN, prevtx_btime, modifier, prevScript) + return prevouts + + + + def create_block(self, prev_hash, staking_prevouts, height, node_n, s_address, fInvalid=False): + api = self.nodes[node_n] + # Get current time + current_time = int(time.time()) + nTime = current_time & 0xfffffff0 + + # Create coinbase TX + coinbase = create_coinbase(height) + coinbase.vout[0].nValue = 0 + coinbase.vout[0].scriptPubKey = b"" + coinbase.nTime = nTime + coinbase.rehash() + + # Create Block with coinbase + block = create_block(int(prev_hash, 16), coinbase, nTime) + + # Find valid kernel hash - Create a new private key used for block signing. + if not block.solve_stake(staking_prevouts): + raise Exception("Not able to solve for any prev_outpoint") + + # Create coinstake TX + amount, prev_time, modifier, prevScript = staking_prevouts[block.prevoutStake] + outNValue = int(amount + 250 * COIN) + stake_tx_unsigned = CTransaction() + stake_tx_unsigned.nTime = block.nTime + stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake)) + stake_tx_unsigned.vin[0].nSequence = 0xffffffff + stake_tx_unsigned.vout.append(CTxOut()) + stake_tx_unsigned.vout.append(CTxOut(outNValue, hex_str_to_bytes(prevScript))) + + if fInvalid: + # Create a new private key and get the corresponding public key + block_sig_key = CECKey() + block_sig_key.set_secretbytes(hash256(pack('= 256: + div, mod = divmod(long_value, 256) + result = _bchr(mod) + result + long_value = div + result = _bchr(long_value) + result + + nPad = 0 + for c in v: + if c == __b58chars[0]: + nPad += 1 + else: + break + + result = _bchr(0) * nPad + result + if length is not None and len(result) != length: + return None -def byte_to_base58(b, version): - result = '' - str = bytes_to_hex_str(b) - str = bytes_to_hex_str(chr(version).encode('latin-1')) + str - checksum = bytes_to_hex_str(hash256(hex_str_to_bytes(str))) - str += checksum[:8] - value = int('0x'+str,0) - while value > 0: - result = chars[value % 58] + result - value //= 58 - while (str[:2] == '00'): - result = chars[0] + result - str = str[2:] return result -# TODO: def base58_decode +def b58encode(v): + """ + encode v, which is a string of bytes, to base58. + """ + long_value = 0 + for (i, c) in enumerate(v[::-1]): + long_value += (256**i) * _bord(c) + + result = '' + while long_value >= __b58base: + div, mod = divmod(long_value, __b58base) + result = __b58chars[mod] + result + long_value = div + result = __b58chars[long_value] + result + # Bitcoin does a little leading-zero-compression: + # leading 0-bytes in the input become leading-1s + nPad = 0 + for c in v: + # if c == '\0': nPad += 1 + if c == 0: + nPad += 1 + else: + break + + return (__b58chars[0] * nPad) + result + +WIF_PREFIX = 212 #d4 +TESTNET_WIF_PREFIX = 239 #ef + +def wif_to_privkey(secretString): + wif_compressed = 52 == len(secretString) + pvkeyencoded = b58decode(secretString).hex() + wifversion = pvkeyencoded[:2] + checksum = pvkeyencoded[-8:] + vs = bytes.fromhex(pvkeyencoded[:-8]) + check = hash256(vs)[0:4] + + if (wifversion == WIF_PREFIX.to_bytes(1, byteorder='big').hex() and checksum == check.hex()) \ + or (wifversion == TESTNET_WIF_PREFIX.to_bytes(1, byteorder='big').hex() and checksum == check.hex()): + + if wif_compressed: + privkey = pvkeyencoded[2:-10] + + else: + privkey = pvkeyencoded[2:-8] + + return privkey, wif_compressed + + else: + return None + +PK_ADD_VERSION = 30 +COLD_ADD_VERSION = 63 +TNET_PK_ADD_VERSION = 139 +TNET_COLD_ADD_VERSION = 73 +SCRIPT_VERSION = 13 +TNET_SCRIPT_VERSION = 19 + +def byte_to_base58(b, version): + data = bytes([version]) + b + checksum = hash256(data)[0:4] + return b58encode(data + checksum) -def keyhash_to_p2pkh(hash, main = False): +def keyhash_to_p2pkh(hash, main=False, isCold=False): assert (len(hash) == 20) - version = 0 if main else 111 + if isCold: + version = COLD_ADD_VERSION if main else TNET_COLD_ADD_VERSION + else: + version = PK_ADD_VERSION if main else COLD_ADD_VERSION return byte_to_base58(hash, version) def scripthash_to_p2sh(hash, main = False): assert (len(hash) == 20) - version = 5 if main else 196 + version = SCRIPT_VERSION if main else TNET_SCRIPT_VERSION return byte_to_base58(hash, version) -def key_to_p2pkh(key, main = False): +def key_to_p2pkh(key, main = False, isCold=False): key = check_key(key) - return keyhash_to_p2pkh(hash160(key), main) + return keyhash_to_p2pkh(hash160(key), main, isCold) def script_to_p2sh(script, main = False): script = check_script(script) diff --git a/test/functional/test_framework/pivx_node.py b/test/functional/test_framework/pivx_node.py new file mode 100644 index 000000000000..8d757d066309 --- /dev/null +++ b/test/functional/test_framework/pivx_node.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2017 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from .messages import msg_getheaders, msg_headers, CBlockHeader +from .mininode import P2PInterface, mininode_lock +from .util import wait_until + +## PIVX Test Node +class PivxTestNode(P2PInterface): + def __init__(self): + super().__init__() + self.last_sendcmpct = [] + self.block_announced = False + # Store the hashes of blocks we've seen announced. + # This is for synchronizing the p2p message traffic, + # so we can eg wait until a particular block is announced. + self.announced_blockhashes = set() + + def on_sendcmpct(self, message): + self.last_sendcmpct.append(message) + + def on_cmpctblock(self, message): + self.block_announced = True + self.last_message["cmpctblock"].header_and_shortids.header.calc_sha256() + self.announced_blockhashes.add(self.last_message["cmpctblock"].header_and_shortids.header.sha256) + + def on_headers(self, message): + self.block_announced = True + for x in self.last_message["headers"].headers: + x.calc_sha256() + self.announced_blockhashes.add(x.sha256) + + def on_inv(self, message): + for x in self.last_message["inv"].inv: + if x.type == 2: + self.block_announced = True + self.announced_blockhashes.add(x.hash) + + # Requires caller to hold mininode_lock + def received_block_announcement(self): + return self.block_announced + + def clear_block_announcement(self): + with mininode_lock: + self.block_announced = False + self.last_message.pop("inv", None) + self.last_message.pop("headers", None) + self.last_message.pop("cmpctblock", None) + + def get_headers(self, locator, hashstop): + msg = msg_getheaders() + msg.locator.vHave = locator + msg.hashstop = hashstop + self.connection.send_message(msg) + + def send_header_for_blocks(self, new_blocks): + headers_message = msg_headers() + headers_message.headers = [CBlockHeader(b) for b in new_blocks] + self.send_message(headers_message) + + def request_headers_and_sync(self, locator, hashstop=0): + self.clear_block_announcement() + self.get_headers(locator, hashstop) + wait_until(self.received_block_announcement, timeout=30, lock=mininode_lock) + self.clear_block_announcement() + + # Block until a block announcement for a particular block hash is received. + def wait_for_block_announcement(self, block_hash, timeout=30): + def received_hash(): + return (block_hash in self.announced_blockhashes) + wait_until(received_hash, timeout=timeout, lock=mininode_lock) + + def send_await_disconnect(self, message, timeout=30): + """Sends a message to the node and wait for disconnect. + + This is used when we want to send a message into the node that we expect + will get us disconnected, eg an invalid block.""" + self.send_message(message) + wait_until(lambda: not self.connected, timeout=timeout, lock=mininode_lock) + diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 2aae3fcc0e41..bddde627360d 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -55,6 +55,7 @@ BASE_SCRIPTS= [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel + 'feature_coldStaking.py', 'wallet_basic.py', 'wallet_backup.py', @@ -69,6 +70,7 @@ 'wallet_txn_clone.py --mineblock', 'interface_rest.py', 'feature_proxy.py', + 'p2p_pos_fakestake.py', 'p2p_pos_fakestake_accepted.py', #'p2p_zpos_fakestake.py', From c5de40f05756499ebb3c1d6166214189a7a79a17 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 18 Jul 2019 16:09:15 +0200 Subject: [PATCH 23/59] [Cleanup] Fix linter --- src/main.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 1d33b41f278a..c28bb5942ca4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1074,7 +1074,6 @@ bool ContextualCheckZerocoinSpendNoSerialCheck(const CTransaction& tx, const lib return true; } - bool CheckZerocoinSpend(const CTransaction& tx, bool fVerifySignature, CValidationState& state, bool fFakeSerialAttack) { //max needed non-mint outputs should be 2 - one for redemption address and a possible 2nd for change @@ -1118,7 +1117,7 @@ bool CheckZerocoinSpend(const CTransaction& tx, bool fVerifySignature, CValidati return state.DoS(100, error("CheckZerocoinSpend(): public zerocoin spend parse failed")); } newSpend = publicSpend; - }else { + } else { newSpend = TxInToZerocoinSpend(txin); } @@ -1175,7 +1174,6 @@ bool CheckZerocoinSpend(const CTransaction& tx, bool fVerifySignature, CValidati return fValidated; } - bool CheckColdStake(const CTransaction& tx, CValidationState& state, bool fColdStakingActive) { CTxOut prevOut; @@ -1183,13 +1181,13 @@ bool CheckColdStake(const CTransaction& tx, CValidationState& state, bool fColdS return state.DoS(100, error("%s : invalid input", __func__), REJECT_INVALID, "bad-txns-inputs"); if (!prevOut.scriptPubKey.IsPayToColdStaking()) - return true; + return true; if (!fColdStakingActive) return state.DoS(100, error("%s : invalid input", __func__), REJECT_INVALID, "coldstake-not-active"); // spending to the same contract - if (prevOut.scriptPubKey != tx.vout[1].scriptPubKey) + if (prevOut.scriptPubKey != tx.vout[1].scriptPubKey) return state.DoS(100, error("%s : invalid scripts", __func__), REJECT_INVALID, "bad-txns-cold-stake"); return true; From 6c1d3993d37c674c2c6c321b1726729462098e88 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 19 Jul 2019 03:32:13 +0200 Subject: [PATCH 24/59] [Trivial] Fix spelling in delegatorremove RPC help --- src/wallet/rpcwallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 8ccdc3142d02..ead4d1a29e02 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -220,7 +220,7 @@ UniValue delegatorremove(const UniValue& params, bool fHelp) throw std::runtime_error( "delegatorremove \"addr\"\n" "\nRemoves the provided address from the allowed delegators keystore.\n" - "This disbles the staking of coins delegated to this wallet, owned by \n" + "This disables the staking of coins delegated to this wallet, owned by \n" "\nArguments:\n" "1. \"addr\" (string, required) The address to whitelist\n" From ad6bee1cd4c6246f14891b61646b9d03c799ad16 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 29 Jul 2019 23:41:05 +0200 Subject: [PATCH 25/59] [Wallet][DB] Refactor Delegator whitelist as AddressBook entry whitelisted delegations are filtered in 'AVailableCoins' now --- src/keystore.cpp | 26 -------- src/keystore.h | 13 ---- src/wallet/rpcwallet.cpp | 49 ++------------ src/wallet/wallet.cpp | 88 +++++++++----------------- src/wallet/wallet.h | 12 +--- src/wallet/wallet_ismine.cpp | 2 +- src/wallet/walletdb.cpp | 20 ------ src/wallet/walletdb.h | 3 - test/functional/feature_coldStaking.py | 23 ++----- 9 files changed, 45 insertions(+), 191 deletions(-) diff --git a/src/keystore.cpp b/src/keystore.cpp index aaf1ac5f9f21..d3466c48d4cf 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -113,32 +113,6 @@ bool CBasicKeyStore::HaveMultiSig() const return (!setMultiSig.empty()); } -bool CBasicKeyStore::AddDelegator(const CKeyID& keyID) -{ - LOCK(cs_KeyStore); - setDelegators.insert(keyID); - return true; -} - -bool CBasicKeyStore::RemoveDelegator(const CKeyID& keyID) -{ - LOCK(cs_KeyStore); - setDelegators.erase(keyID); - return true; -} - -bool CBasicKeyStore::HaveDelegator(const CKeyID& keyID) const -{ - LOCK(cs_KeyStore); - return setDelegators.count(keyID) > 0; -} - -bool CBasicKeyStore::HaveDelegator() const -{ - LOCK(cs_KeyStore); - return (!setDelegators.empty()); -} - bool CBasicKeyStore::HaveKey(const CKeyID& address) const { bool result; diff --git a/src/keystore.h b/src/keystore.h index 42290b7f07e2..2a3e3bd052f0 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -51,19 +51,12 @@ class CKeyStore virtual bool RemoveMultiSig(const CScript& dest) = 0; virtual bool HaveMultiSig(const CScript& dest) const = 0; virtual bool HaveMultiSig() const = 0; - - //! Support for ColdStaking - virtual bool AddDelegator(const CKeyID& dest) = 0; - virtual bool RemoveDelegator(const CKeyID& dest) = 0; - virtual bool HaveDelegator(const CKeyID& dest) const = 0; - virtual bool HaveDelegator() const = 0; }; typedef std::map KeyMap; typedef std::map ScriptMap; typedef std::set WatchOnlySet; typedef std::set MultiSigScriptSet; -typedef std::set DelegatorsSet; /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore @@ -73,7 +66,6 @@ class CBasicKeyStore : public CKeyStore ScriptMap mapScripts; WatchOnlySet setWatchOnly; MultiSigScriptSet setMultiSig; - DelegatorsSet setDelegators; public: bool AddKeyPubKey(const CKey& key, const CPubKey& pubkey); @@ -94,11 +86,6 @@ class CBasicKeyStore : public CKeyStore virtual bool RemoveMultiSig(const CScript& dest); virtual bool HaveMultiSig(const CScript& dest) const; virtual bool HaveMultiSig() const; - - virtual bool AddDelegator(const CKeyID& keyID); - virtual bool RemoveDelegator(const CKeyID& keyID); - virtual bool HaveDelegator(const CKeyID& keyID) const; - virtual bool HaveDelegator() const; }; typedef std::vector > CKeyingMaterial; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ead4d1a29e02..1137f216e274 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -146,41 +146,16 @@ UniValue getnewstakingaddress(const UniValue& params, bool fHelp) return GetNewAddressFromAccount("coldstaking", true, params).ToString(); } -int RescanStakeDelegations() -{ - int ret = 0; - LOCK2(cs_main, pwalletMain->cs_wallet); - if (!Params().Cold_Staking_Enabled(chainActive.Tip()->nHeight)) - return ret; - - CBlockIndex *pindex = chainActive[Params().Block_Enforce_Cold_Staking()]; - - while(pindex) { - CBlock block; - ReadBlockFromDisk(block, pindex); - for (CTransaction& tx : block.vtx) { - if (pwalletMain->AddToWalletIfInvolvingMe(tx, &block, true, true)) - ret++; - } - pindex = chainActive.Next(pindex); - } - return ret; - -} - UniValue delegatoradd(const UniValue& params, bool fHelp) { - if (fHelp || params.size() < 1 || params.size() > 2) + if (fHelp || params.size() != 1) throw std::runtime_error( "delegatoradd \"addr\" ( fRescan )\n" - "\nAdd the provided address into the allowed delegators keystore.\n" + "\nAdd the provided address into the allowed delegators AddressBook.\n" "This enables the staking of coins delegated to this wallet, owned by \n" "\nArguments:\n" "1. \"addr\" (string, required) The address to whitelist\n" - "2. fRescan (bool, optional, default=false) rescan blockchain. Set to true only if\n" - " some stake-delegation from this address has already been sent.\n" - " If set to true, it might take few minutes to complete the scan.\n" "\nResult:\n" "true|false (boolean) true if successful.\n" @@ -194,24 +169,11 @@ UniValue delegatoradd(const UniValue& params, bool fHelp) if (!address.IsValid() || address.IsStakingAddress()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); - bool fRescan = params.size() > 1 && !params[1].isNull() ? params[1].get_bool() : false; - CKeyID keyID; if (!address.GetKeyID(keyID)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to get KeyID from PIVX address"); - if (pwalletMain->HaveDelegator(keyID)) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Delegator already whitelisted"); - - bool res = pwalletMain->AddDelegator(keyID); - - if (res && fRescan) { - // trigger rescan - int ret = RescanStakeDelegations(); - LogPrintf("%s: Rescan for Stake delegation complete. %d inputs added/modified", __func__, ret); - } - - return res; + return pwalletMain->SetAddressBook(keyID, "", "delegator"); } UniValue delegatorremove(const UniValue& params, bool fHelp) @@ -240,10 +202,7 @@ UniValue delegatorremove(const UniValue& params, bool fHelp) if (!address.GetKeyID(keyID)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to get KeyID from PIVX address"); - if (!pwalletMain->HaveDelegator(keyID)) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Delegator not whitelisted"); - - return pwalletMain->RemoveDelegator(keyID); + return pwalletMain->DelAddressBook(keyID); } CBitcoinAddress GetAccountAddress(std::string strAccount, bool bForceNew = false) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 699e54870732..5d09c8d4d5d4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -258,36 +258,6 @@ bool CWallet::RemoveWatchOnly(const CScript& dest) return true; } -bool CWallet::AddDelegator(const CKeyID& keyID) -{ - if (!CCryptoKeyStore::AddDelegator(keyID)) - return false; - nTimeFirstKey = 1; // No birthday information for cold keys. - NotifyDelegatorChanged(true); - if (!fFileBacked) - return true; - return CWalletDB(strWalletFile).WriteDelegator(keyID); -} - -bool CWallet::RemoveDelegator(const CKeyID& keyID) -{ - AssertLockHeld(cs_wallet); - if (!CCryptoKeyStore::RemoveDelegator(keyID)) - return false; - if (!HaveDelegator()) - NotifyDelegatorChanged(false); - if (fFileBacked) - if (!CWalletDB(strWalletFile).EraseDelegator(keyID)) - return false; - - return true; -} - -bool CWallet::LoadDelegator(const CKeyID& keyID) -{ - return CCryptoKeyStore::AddDelegator(keyID); -} - bool CWallet::LoadWatchOnly(const CScript& dest) { return CCryptoKeyStore::AddWatchOnly(dest); @@ -792,28 +762,8 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD * pblock is optional, but should be provided if the transaction is known to be in a block. * If fUpdate is true, existing transactions will be updated. */ -bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fOnlyCold) -{ - // if fOnlyCold=true, skip stakes or txes with no P2CS script - if (fOnlyCold) { - bool hasP2CS = true; - if (!tx.HasP2CSOutputs()) { - hasP2CS = false; - for (const CTxIn& in : tx.vin) { - // check the inputs. if none is spending a P2CS, skip - CTransaction prevtx; - uint256 hashBlock = 0; - if (!GetTransaction(in.prevout.hash, prevtx, hashBlock)) - continue; - if (prevtx.HasP2CSOutputs()) { - hasP2CS = true; - break; - } - } - } - if (!hasP2CS) - return false; - } +bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate) +{ { AssertLockHeld(cs_wallet); @@ -2050,8 +2000,12 @@ void CWallet::AvailableCoins( continue; // skip cold coins - if (!fIncludeColdStaking && mine == ISMINE_COLD) - continue; + if (mine == ISMINE_COLD) { + if (!fIncludeColdStaking) + continue; + if (!HasDelegator(pcoin->vout[i])) + continue; + } // skip delegated coins if (!fIncludeDelegated && mine == ISMINE_SPENDABLE_DELEGATED) @@ -3031,11 +2985,9 @@ DBErrors CWallet::ZapWalletTx(std::vector& vWtx) bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& strPurpose) { - bool fUpdated = false; + bool fUpdated = HasAddressBook(address); { LOCK(cs_wallet); // mapAddressBook - std::map::iterator mi = mapAddressBook.find(address); - fUpdated = mi != mapAddressBook.end(); mapAddressBook[address].name = strName; if (!strPurpose.empty()) /* update purpose only if requested */ mapAddressBook[address].purpose = strPurpose; @@ -3073,6 +3025,28 @@ bool CWallet::DelAddressBook(const CTxDestination& address) return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString()); } +bool CWallet::HasAddressBook(const CTxDestination& address) const +{ + LOCK(cs_wallet); // mapAddressBook + std::map::const_iterator mi = mapAddressBook.find(address); + return mi != mapAddressBook.end(); +} + +bool CWallet::HasDelegator(const CTxOut& out) const +{ + CTxDestination delegator; + if (!ExtractDestination(out.scriptPubKey, delegator, false)) + return false; + { + LOCK(cs_wallet); // mapAddressBook + std::map::const_iterator mi = mapAddressBook.find(delegator); + if (mi == mapAddressBook.end()) + return false; + return (*mi).second.purpose == "delegator"; + } +} + + /** * Mark old keypool keys as used, * and generate all new keys diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 06d4184eb634..df4f2d5031ed 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -399,11 +399,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) bool LoadWatchOnly(const CScript& dest); - //! Delegator addresseses for cold staking - bool AddDelegator(const CKeyID& keyID); - bool RemoveDelegator(const CKeyID& keyID); - bool LoadDelegator(const CKeyID& keyID); - //! Adds a MultiSig address to the store, and saves it to disk. bool AddMultiSig(const CScript& dest); bool RemoveMultiSig(const CScript& dest); @@ -426,7 +421,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void MarkDirty(); bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb); void SyncTransaction(const CTransaction& tx, const CBlock* pblock); - bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fOnlyCold = false); + bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate); void EraseFromWallet(const uint256& hash); int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false, bool fromStartup = false); void ReacceptWalletTransactions(bool fFirstLoad = false); @@ -514,6 +509,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose); bool DelAddressBook(const CTxDestination& address); + bool HasAddressBook(const CTxDestination& address) const; + bool HasDelegator(const CTxOut& out) const; bool UpdatedTransaction(const uint256& hashTx); @@ -557,9 +554,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /** MultiSig address added */ boost::signals2::signal NotifyMultiSigChanged; - /** Delegator address added */ - boost::signals2::signal NotifyDelegatorChanged; - /** zPIV reset */ boost::signals2::signal NotifyzPIVReset; diff --git a/src/wallet/wallet_ismine.cpp b/src/wallet/wallet_ismine.cpp index a20f33126099..c5e3bd3780bc 100644 --- a/src/wallet/wallet_ismine.cpp +++ b/src/wallet/wallet_ismine.cpp @@ -85,7 +85,7 @@ isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) if (spendKeyIsMine && stakeKeyIsMine) return ISMINE_SPENDABLE_STAKEABLE; - else if (stakeKeyIsMine && keystore.HaveDelegator(ownerKeyID)) + else if (stakeKeyIsMine) return ISMINE_COLD; else if (spendKeyIsMine) return ISMINE_SPENDABLE_DELEGATED; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 5fe577dbdebf..51f64655e75b 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -194,19 +194,6 @@ bool CWalletDB::EraseMultiSig(const CScript& dest) return Erase(std::make_pair(std::string("multisig"), dest)); } -bool CWalletDB::WriteDelegator(const CKeyID& keyID) -{ - nWalletDBUpdated++; - return Write(std::make_pair(std::string("delegator"), keyID), '1'); -} - -bool CWalletDB::EraseDelegator(const CKeyID& keyID) -{ - nWalletDBUpdated++; - return Erase(std::make_pair(std::string("delegator"), keyID)); -} - - bool CWalletDB::WriteBestBlock(const CBlockLocator& locator) { nWalletDBUpdated++; @@ -559,13 +546,6 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW // MultiSig addresses have no birthday information for now, // so set the wallet birthday to the beginning of time. pwallet->nTimeFirstKey = 1; - } else if (strType == "delegator") { - CKeyID keyID; - ssKey >> keyID; - char fYes; - ssValue >> fYes; - if (fYes == '1') - pwallet->LoadDelegator(keyID); } else if (strType == "key" || strType == "wkey") { CPubKey vchPubKey; ssKey >> vchPubKey; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 3948da3b1ae3..3b24e2194a5d 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -113,9 +113,6 @@ class CWalletDB : public CDB bool WriteMultiSig(const CScript& script); bool EraseMultiSig(const CScript& script); - bool WriteDelegator(const CKeyID& keyID); - bool EraseDelegator(const CKeyID& keyID); - bool WriteBestBlock(const CBlockLocator& locator); bool ReadBestBlock(CBlockLocator& locator); diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py index ef48b3d70eca..6af08658d671 100755 --- a/test/functional/feature_coldStaking.py +++ b/test/functional/feature_coldStaking.py @@ -136,7 +136,7 @@ def run_test(self): self.log.info("%d Txes created." % NUM_OF_INPUTS) # check balances: self.expected_balance = NUM_OF_INPUTS * INPUT_VALUE - self.checkBalances(0) + self.checkBalances() # 5) check that the owner (nodes[0]) can spend the coins. @@ -153,7 +153,7 @@ def run_test(self): self.sync_all() # check balances after spend. self.expected_balance -= float(u["amount"]) - self.checkBalances(0) + self.checkBalances() self.log.info("Balances check out after spend") @@ -176,16 +176,9 @@ def run_test(self): self.log.info("Nice. Cold staker was NOT able to create the block yet.") pass self.log.info("Whitelisting the owner...") - self.nodes[1].delegatoradd(owner_address, True) + ret = self.nodes[1].delegatoradd(owner_address) + assert(ret) self.log.info("Delegator address %s whitelisted" % owner_address) - # check that's been whitelisted for real - try: - self.nodes[1].delegatoradd(owner_address) - assert(False) - except JSONRPCException as e: - assert("Delegator already whitelisted" in str(e)) - # check balances (delegated in nodes[0] and cold in nodes[1]) - self.checkBalances() # 7) check that the staker CANNOT spend the coins. @@ -310,21 +303,17 @@ def run_test(self): - def checkBalances(self, cold=None): + def checkBalances(self): w_info = self.nodes[0].getwalletinfo() self.log.info("OWNER - Delegated %f / Cold %f" % ( float(w_info["delegated_balance"]), w_info["cold_staking_balance"])) assert(float(w_info["delegated_balance"]) == self.expected_balance) assert (float(w_info["cold_staking_balance"]) == 0) - if cold != None: - coldbal = cold - else: - coldbal = self.expected_balance w_info = self.nodes[1].getwalletinfo() self.log.info("STAKER - Delegated %f / Cold %f" % ( float(w_info["delegated_balance"]), w_info["cold_staking_balance"])) assert(float(w_info["delegated_balance"]) == 0) - assert (float(w_info["cold_staking_balance"]) == coldbal) + assert (float(w_info["cold_staking_balance"]) == self.expected_balance) From 6e28ec132fa994148eb96004f5f9544cf348a411 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 30 Jul 2019 00:57:14 +0200 Subject: [PATCH 26/59] [Core] prevent abuse on mn output when spork 8 is disabled --- src/main.cpp | 8 ++++++++ src/masternode-payments.cpp | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index c28bb5942ca4..263787aea1c5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4461,6 +4461,14 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo for (unsigned int i = 2; i < block.vtx.size(); i++) if (block.vtx[i].IsCoinStake()) return state.DoS(100, error("%s : more than one coinstake", __func__)); + + // if MN payment is disabled. Check that the last output is not abused by cold staker + if (!IsInitialBlockDownload() && block.vtx[1].HasP2CSOutputs() && !sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) { + const int outputs = block.vtx[1].vout.size(); + if (outputs >=3 && block.vtx[1].vout[outputs-1].scriptPubKey != block.vtx[1].vout[outputs-2].scriptPubKey) + return state.DoS(100, error("%s: Wrong cold staking outputs when masternode payment enforcement is disabled on tx %s", + __func__, block.vtx[1].ToString().c_str())); + } } // ----------- swiftTX transaction scanning ----------- diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 09b2ace72250..daed43e33de5 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -317,7 +317,6 @@ bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight) if (sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) return false; LogPrint("masternode","Masternode payment enforcement is disabled, accepting block\n"); - return true; } From c723ab69d4504fb522746919a4378ed656765ac7 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 30 Jul 2019 00:58:28 +0200 Subject: [PATCH 27/59] [Core] Add threshold for P2CS value (hardcoded to 10 PIV) --- src/chainparams.cpp | 1 + src/chainparams.h | 3 +++ src/main.cpp | 14 +++++++++++--- src/wallet/rpcwallet.cpp | 5 +++-- test/functional/feature_coldStaking.py | 7 +++++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index f56189cda8b1..dd9c99c9bb20 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -168,6 +168,7 @@ class CMainParams : public CChainParams nFutureTimeDriftPoS = 180; nMasternodeCountDrift = 20; nMaxMoneyOut = 21000000 * COIN; + nMinColdStakingAmount = 10 * COIN; /** Height or Time Based Activations **/ nLastPOWBlock = 259200; diff --git a/src/chainparams.h b/src/chainparams.h index 7c1331873f59..1497d4319582 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -104,6 +104,8 @@ class CChainParams int GetBudgetCycleBlocks() const { return nBudgetCycleBlocks; } int64_t GetProposalEstablishmentTime() const { return nProposalEstablishmentTime; } + CAmount GetMinColdStakingAmount() const { return nMinColdStakingAmount; } + /** Spork key and Masternode Handling **/ std::string SporkPubKey() const { return strSporkPubKey; } std::string SporkPubKeyOld() const { return strSporkPubKeyOld; } @@ -231,6 +233,7 @@ class CChainParams int nBlockStakeModifierlV2; int nBlockEnforceNewMessageSignatures; int nColdStakingStart; + CAmount nMinColdStakingAmount; // fake serial attack int nFakeSerialBlockheightEnd = 0; diff --git a/src/main.cpp b/src/main.cpp index 263787aea1c5..72fb5380d7b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1210,13 +1210,13 @@ bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fReject return state.DoS(100, error("CheckTransaction() : size limits failed"), REJECT_INVALID, "bad-txns-oversize"); + const CAmount minColdStakingAmount = Params().GetMinColdStakingAmount(); + // Check for negative or overflow output values CAmount nValueOut = 0; for (const CTxOut& txout : tx.vout) { if (txout.IsEmpty() && !tx.IsCoinBase() && !tx.IsCoinStake()) return state.DoS(100, error("CheckTransaction(): txout empty for user transaction")); - if (txout.scriptPubKey.IsPayToColdStaking() && !fColdStakingActive) - return state.DoS(100, error("CheckTransaction(): cold staking not active")); if (txout.nValue < 0) return state.DoS(100, error("CheckTransaction() : txout.nValue negative"), REJECT_INVALID, "bad-txns-vout-negative"); @@ -1231,11 +1231,19 @@ bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fReject if(!CheckZerocoinMint(tx.GetHash(), txout, state, true)) return state.DoS(100, error("CheckTransaction() : invalid zerocoin mint")); } + // check cold staking enforcement and value out + if (txout.scriptPubKey.IsPayToColdStaking()) { + if (!fColdStakingActive) + return state.DoS(100, error("%s: cold staking not active", __func__), REJECT_INVALID, "bad-txns-cold-stake"); + if (txout.nValue < minColdStakingAmount) + return state.DoS(100, error("%s: dust amount (%d) not allowed for cold staking. Min amount: %d", + __func__, txout.nValue, minColdStakingAmount), REJECT_INVALID, "bad-txns-cold-stake"); + } } // Additional check for cold staking if (tx.IsCoinStake() && !tx.HasZerocoinSpendInputs() && !CheckColdStake(tx, state, fColdStakingActive)) - return state.DoS(100, error("CheckTransaction() : invalid cold stake"), REJECT_INVALID, "bad-txns-cold-stake"); + return state.DoS(100, error("%s: invalid cold stake", __func__), REJECT_INVALID, "bad-txns-cold-stake"); std::set vInOutPoints; std::set vZerocoinSpendSerials; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 1137f216e274..9154ee85e335 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -512,8 +512,9 @@ UniValue CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CR // Get Amount CAmount nValue = AmountFromValue(params[1]); - if (nValue <= 0) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); + if (nValue <= Params().GetMinColdStakingAmount()) + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid amount (%d). Min amount: %d", + nValue, Params().GetMinColdStakingAmount())); // Get Owner Address CBitcoinAddress ownerAddr; diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py index 6af08658d671..559ec56e08c0 100755 --- a/test/functional/feature_coldStaking.py +++ b/test/functional/feature_coldStaking.py @@ -125,6 +125,13 @@ def run_test(self): assert(res is not None and res != "") self.log.info("Good. Warning NOT triggered.") self.log.info("Now delegate with internal owner address..") + self.log.info("Try first with a value (5) below the threshold") + try: + self.nodes[0].delegatestake(staker_address, 5, owner_address) + assert(False) + except JSONRPCException as e: + assert("Invalid amount" in str(e)) + self.log.info("Nice. it was not possible.") self.log.info("Creating %d stake-delegation txes..." % NUM_OF_INPUTS) for i in range(NUM_OF_INPUTS): res = self.nodes[0].delegatestake(staker_address, INPUT_VALUE, owner_address) From 374a78cfe27cf6b5811fc6cff9062d3648c1169a Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 3 Aug 2019 02:07:18 +0200 Subject: [PATCH 28/59] [Tests] Fix Script UnitTests data --- src/test/data/script_invalid.json | 6 +++--- src/test/data/script_valid.json | 6 +++--- src/test/data/tx_invalid.json | 32 +++++++++++++++---------------- src/test/data/tx_valid.json | 20 +++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/test/data/script_invalid.json b/src/test/data/script_invalid.json index aa59dce3fc60..a194193965ef 100644 --- a/src/test/data/script_invalid.json +++ b/src/test/data/script_invalid.json @@ -158,12 +158,12 @@ ["2 2 LSHIFT", "8 EQUAL", "P2SH,STRICTENC", "disabled"], ["2 1 RSHIFT", "1 EQUAL", "P2SH,STRICTENC", "disabled"], -["1","NOP1 NOP2 NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 2 EQUAL", "P2SH,STRICTENC"], -["'NOP_1_to_10' NOP1 NOP2 NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_11' EQUAL", "P2SH,STRICTENC"], +["1","NOP1 CHECKLOCKTIMEVERIFY NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 2 EQUAL", "P2SH,STRICTENC"], +["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_11' EQUAL", "P2SH,STRICTENC"], ["Ensure 100% coverage of discouraged NOPS"], ["1", "NOP1", "P2SH,DISCOURAGE_UPGRADABLE_NOPS"], -["1", "NOP2", "P2SH,DISCOURAGE_UPGRADABLE_NOPS"], +["1", "CHECKLOCKTIMEVERIFY", "P2SH,DISCOURAGE_UPGRADABLE_NOPS"], ["1", "NOP3", "P2SH,DISCOURAGE_UPGRADABLE_NOPS"], ["1", "NOP4", "P2SH,DISCOURAGE_UPGRADABLE_NOPS"], ["1", "NOP5", "P2SH,DISCOURAGE_UPGRADABLE_NOPS"], diff --git a/src/test/data/script_valid.json b/src/test/data/script_valid.json index beef16d7924f..0701a1b52c90 100644 --- a/src/test/data/script_valid.json +++ b/src/test/data/script_valid.json @@ -230,8 +230,8 @@ ["'abcdefghijklmnopqrstuvwxyz'", "HASH256 0x4c 0x20 0xca139bc10c2f660da42666f72e89a225936fc60f193c161124a672050c434671 EQUAL", "P2SH,STRICTENC"], -["1","NOP1 NOP2 NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 1 EQUAL", "P2SH,STRICTENC"], -["'NOP_1_to_10' NOP1 NOP2 NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_10' EQUAL", "P2SH,STRICTENC"], +["1","NOP1 CHECKLOCKTIMEVERIFY NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 1 EQUAL", "P2SH,STRICTENC"], +["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_10' EQUAL", "P2SH,STRICTENC"], ["1", "NOP", "P2SH,STRICTENC,DISCOURAGE_UPGRADABLE_NOPS", "Discourage NOPx flag allows OP_NOP"], @@ -439,7 +439,7 @@ ["NOP", "CODESEPARATOR 1", "P2SH,STRICTENC"], ["NOP", "NOP1 1", "P2SH,STRICTENC"], -["NOP", "NOP2 1", "P2SH,STRICTENC"], +["NOP", "CHECKLOCKTIMEVERIFY 1", "P2SH,STRICTENC"], ["NOP", "NOP3 1", "P2SH,STRICTENC"], ["NOP", "NOP4 1", "P2SH,STRICTENC"], ["NOP", "NOP5 1", "P2SH,STRICTENC"], diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index e12512f82fec..5b4067f716e8 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -103,58 +103,58 @@ ["CHECKLOCKTIMEVERIFY tests"], ["By-height locks, with argument just beyond tx nLockTime"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "1 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "1 CHECKLOCKTIMEVERIFY 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000fe64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], ["By-time locks, with argument just beyond tx nLockTime (but within numerical boundries)"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000001 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000001 CHECKLOCKTIMEVERIFY 1"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000feffffff", "P2SH,CHECKLOCKTIMEVERIFY"], ["Argument missing"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "CHECKLOCKTIMEVERIFY 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], ["Argument negative with by-blockheight nLockTime=0"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "-1 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "-1 CHECKLOCKTIMEVERIFY 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], ["Argument negative with by-blocktime nLockTime=500,000,000"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "-1 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "-1 CHECKLOCKTIMEVERIFY 1"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], ["Input locked"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKLOCKTIMEVERIFY 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], ["Another input being unlocked isn't sufficient; the CHECKLOCKTIMEVERIFY-using input must be unlocked"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP2 1"] , +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKLOCKTIMEVERIFY 1"] , ["0000000000000000000000000000000000000000000000000000000000000200", 1, "1"]], "010000000200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00020000000000000000000000000000000000000000000000000000000000000100000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], ["Argument/tx height/time mismatch, both versions"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKLOCKTIMEVERIFY 1"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 CHECKLOCKTIMEVERIFY 1"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 CHECKLOCKTIMEVERIFY 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], ["Argument 2^32 with nLockTime=2^32-1"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967296 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967296 CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff", "P2SH,CHECKLOCKTIMEVERIFY"], ["Same, but with nLockTime=2^31-1"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffff7f", "P2SH,CHECKLOCKTIMEVERIFY"], ["6 byte non-minimally-encoded arguments are invalid even in their contents are valid"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x06 0x000000000000 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x06 0x000000000000 CHECKLOCKTIMEVERIFY 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], ["Failure due to failing CHECKLOCKTIMEVERIFY in scriptSig"], diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index 5c3cf04d5b02..369deb2b2c39 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -181,35 +181,35 @@ ["CHECKLOCKTIMEVERIFY tests"], ["By-height locks, with argument == 0 and == tx nLockTime"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKLOCKTIMEVERIFY 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000001000000000000ff64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000001000000000000ff64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], ["By-time locks, with argument just beyond tx nLockTime (but within numerical boundries)"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 CHECKLOCKTIMEVERIFY 1"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000010000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000001000000000000ffffffff", "P2SH,CHECKLOCKTIMEVERIFY"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000001000000000000ffffffff", "P2SH,CHECKLOCKTIMEVERIFY"], ["Any non-maxint nSequence is fine"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKLOCKTIMEVERIFY 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000100000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], ["The argument can be calculated rather than created directly by a PUSHDATA"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 1ADD NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 1ADD CHECKLOCKTIMEVERIFY 1"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000010000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], ["Perhaps even by an ADD producing a 5-byte result that is out of bounds for other opcodes"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483647 2147483647 ADD NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483647 2147483647 ADD CHECKLOCKTIMEVERIFY 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000001000000000000feffffff", "P2SH,CHECKLOCKTIMEVERIFY"], ["5 byte non-minimally-encoded arguments are valid"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x05 0x0000000000 NOP2 1"]], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x05 0x0000000000 CHECKLOCKTIMEVERIFY 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], ["Valid CHECKLOCKTIMEVERIFY in scriptSig"], From 0f09fcee38416b2887cec894c9a68c9a2af88103 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 6 Aug 2019 22:45:38 +0200 Subject: [PATCH 29/59] [Core] Init CBitcoinAddress and Visitor with base58 prefix instead of bool flag --- src/base58.cpp | 25 +++++++++++-------------- src/base58.h | 8 ++++---- src/core_write.cpp | 4 ++-- src/wallet/rpcwallet.cpp | 11 ++++++----- src/wallet/wallet.cpp | 4 ++-- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/base58.cpp b/src/base58.cpp index c9e42ae621b9..3c974367ca41 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -224,39 +224,36 @@ class CBitcoinAddressVisitor : public boost::static_visitor { private: CBitcoinAddress* addr; - bool IsColdStakingAddr = false; + CChainParams::Base58Type type; public: - CBitcoinAddressVisitor(CBitcoinAddress* addrIn, const bool fStakingAddr = false) : + CBitcoinAddressVisitor(CBitcoinAddress* addrIn, + const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS) : addr(addrIn), - IsColdStakingAddr(fStakingAddr){}; + type(addrType){}; - bool operator()(const CKeyID& id) const { return addr->Set(id, IsColdStakingAddr); } - bool operator()(const CScriptID& id) const { return addr->Set(id, IsColdStakingAddr); } + bool operator()(const CKeyID& id) const { return addr->Set(id, type); } + bool operator()(const CScriptID& id) const { return addr->Set(id); } bool operator()(const CNoDestination& no) const { return false; } }; } // anon namespace -bool CBitcoinAddress::Set(const CKeyID& id, const bool fStakingAddr) +bool CBitcoinAddress::Set(const CKeyID& id, const CChainParams::Base58Type addrType) { - if (fStakingAddr) { - SetData(Params().Base58Prefix(CChainParams::STAKING_ADDRESS), &id, 20); - } else { - SetData(Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS), &id, 20); - } + SetData(Params().Base58Prefix(addrType), &id, 20); return true; } -bool CBitcoinAddress::Set(const CScriptID& id, const bool fStakingAddr) +bool CBitcoinAddress::Set(const CScriptID& id) { SetData(Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS), &id, 20); return true; } -bool CBitcoinAddress::Set(const CTxDestination& dest, const bool fStakingAddr) +bool CBitcoinAddress::Set(const CTxDestination& dest, const CChainParams::Base58Type addrType) { - return boost::apply_visitor(CBitcoinAddressVisitor(this, fStakingAddr), dest); + return boost::apply_visitor(CBitcoinAddressVisitor(this, addrType), dest); } bool CBitcoinAddress::IsValid() const diff --git a/src/base58.h b/src/base58.h index 7dc03d063d7a..1df40dd61930 100644 --- a/src/base58.h +++ b/src/base58.h @@ -110,14 +110,14 @@ class CBase58Data class CBitcoinAddress : public CBase58Data { public: - bool Set(const CKeyID& id, const bool fStakingAddr = false); - bool Set(const CScriptID& id, const bool fStakingAddr = false); - bool Set(const CTxDestination& dest, const bool fStakingAddr = false); + bool Set(const CKeyID& id, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); + bool Set(const CScriptID& id); + bool Set(const CTxDestination& dest, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); bool IsValid() const; bool IsValid(const CChainParams& params) const; CBitcoinAddress() {} - CBitcoinAddress(const CTxDestination& dest, const bool fStakingAddr = false) { Set(dest, fStakingAddr); } + CBitcoinAddress(const CTxDestination& dest, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS) { Set(dest, addrType); } CBitcoinAddress(const std::string& strAddress) { SetString(strAddress); } CBitcoinAddress(const char* pszAddress) { SetString(pszAddress); } diff --git a/src/core_write.cpp b/src/core_write.cpp index cd4a3211238d..30fc44fad6c5 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -82,8 +82,8 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue a(UniValue::VARR); if (type == TX_COLDSTAKE && addresses.size() == 2) { - a.push_back(CBitcoinAddress(addresses[0], true).ToString()); - a.push_back(CBitcoinAddress(addresses[1], false).ToString()); + a.push_back(CBitcoinAddress(addresses[0], CChainParams::STAKING_ADDRESS).ToString()); + a.push_back(CBitcoinAddress(addresses[1], CChainParams::PUBKEY_ADDRESS).ToString()); } else { for (const CTxDestination& addr : addresses) a.push_back(CBitcoinAddress(addr).ToString()); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 9154ee85e335..ec0694065729 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -80,7 +80,8 @@ std::string AccountFromValue(const UniValue& value) return strAccount; } -CBitcoinAddress GetNewAddressFromAccount(const std::string name, const bool fStakeAddr, const UniValue ¶ms) +CBitcoinAddress GetNewAddressFromAccount(const std::string name, const UniValue ¶ms, + const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS) { LOCK2(cs_main, pwalletMain->cs_wallet); // Parse the account first so we don't generate a key if there's an error @@ -99,7 +100,7 @@ CBitcoinAddress GetNewAddressFromAccount(const std::string name, const bool fSta pwalletMain->SetAddressBook(keyID, strAccount, name); - return CBitcoinAddress(keyID, fStakeAddr); + return CBitcoinAddress(keyID, addrType); } UniValue getnewaddress(const UniValue& params, bool fHelp) @@ -121,7 +122,7 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "\"\"") + HelpExampleCli("getnewaddress", "\"myaccount\"") + HelpExampleRpc("getnewaddress", "\"myaccount\"")); - return GetNewAddressFromAccount("receive", false, params).ToString(); + return GetNewAddressFromAccount("receive", params).ToString(); } UniValue getnewstakingaddress(const UniValue& params, bool fHelp) @@ -143,7 +144,7 @@ UniValue getnewstakingaddress(const UniValue& params, bool fHelp) HelpExampleCli("getnewstakingaddress", "") + HelpExampleRpc("getnewstakingaddress", "\"\"") + HelpExampleCli("getnewstakingaddress", "\"myaccount\"") + HelpExampleRpc("getnewstakingaddress", "\"myaccount\"")); - return GetNewAddressFromAccount("coldstaking", true, params).ToString(); + return GetNewAddressFromAccount("coldstaking", params, CChainParams::STAKING_ADDRESS).ToString(); } UniValue delegatoradd(const UniValue& params, bool fHelp) @@ -539,7 +540,7 @@ UniValue CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CR } else { // Get new owner address from keypool - ownerAddr = GetNewAddressFromAccount("delegated", false, NullUniValue); + ownerAddr = GetNewAddressFromAccount("delegated", NullUniValue); if (!ownerAddr.GetKeyID(ownerKey)) throw JSONRPCError(RPC_WALLET_ERROR, "Unable to get spend pubkey hash from owneraddress"); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5d09c8d4d5d4..016ec62bc5a8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2053,14 +2053,14 @@ std::map > CWallet::AvailableCoinsByAddres CTxDestination address; bool fColdStakeAddr = false; if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address, fColdStakeAddr)) { - // check if we have the staking key + // if this is a P2CS we don't have the owner key - check if we have the staking key fColdStakeAddr = true; if ( !out.tx->vout[out.i].scriptPubKey.IsPayToColdStaking() || !ExtractDestination(out.tx->vout[out.i].scriptPubKey, address, fColdStakeAddr) ) continue; } - mapCoins[CBitcoinAddress(address, fColdStakeAddr)].push_back(out); + mapCoins[CBitcoinAddress(address, fColdStakeAddr ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS)].push_back(out); } return mapCoins; From 61e9d6e9370d97c41bedef4eef935142ee41f70d Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 6 Aug 2019 22:55:40 +0200 Subject: [PATCH 30/59] [Consensus] Fix output index in CheckColdStake() fix OP_CHECKCOLDSTAKE output scripts verification exlcuding the coinstake marker --- src/primitives/transaction.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 2fd6f88f6483..054d836c6f80 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -224,9 +224,9 @@ bool CTransaction::CheckColdStake() const // all outputs except first (coinstake marker) and last (masternode payout) // have the same pubKeyScript - firstScript = vout[0].scriptPubKey; + firstScript = vout[1].scriptPubKey; if (vout.size() > 3) { - for (unsigned int i=1; i Date: Wed, 7 Aug 2019 00:08:06 +0200 Subject: [PATCH 31/59] [Wallet] Speed up ColdStaking and Delegated balance computation using HasP2CSOutputs to skip unrelevant txes --- src/wallet/wallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 016ec62bc5a8..913f2240f620 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1734,7 +1734,7 @@ CAmount CWallet::GetColdStakingBalance() const for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (pcoin->IsTrusted()) + if (pcoin->HasP2CSOutputs() && pcoin->IsTrusted()) nTotal += pcoin->GetColdStakingCredit(); } } @@ -1750,7 +1750,7 @@ CAmount CWallet::GetDelegatedBalance() const for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (pcoin->IsTrusted()) + if (pcoin->HasP2CSOutputs() && pcoin->IsTrusted()) nTotal += pcoin->GetStakeDelegationCredit(); } } From f20e1a63a0d4c3ea8005edd65b24a1b797eddba1 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 7 Aug 2019 00:09:38 +0200 Subject: [PATCH 32/59] [RPC] CreateColdStakeDelegation move checks before creating address so a new address is not created if any of the check fails --- src/wallet/rpcwallet.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ec0694065729..91b1205316b9 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -517,6 +517,19 @@ UniValue CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CR throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid amount (%d). Min amount: %d", nValue, Params().GetMinColdStakingAmount())); + // include already delegated coins + bool fUseDelegated = false; + if (params.size() > 4 && !params[4].isNull()) + fUseDelegated = params[4].get_bool(); + + // Check amount + CAmount currBalance = pwalletMain->GetBalance() + (fUseDelegated ? pwalletMain->GetDelegatedBalance() : 0); + if (nValue > currBalance) + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); + + std::string strError; + EnsureWalletIsUnlocked(); + // Get Owner Address CBitcoinAddress ownerAddr; CKeyID ownerKey; @@ -548,19 +561,6 @@ UniValue CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CR // Get P2CS script for addresses CScript scriptPubKey = GetScriptForStakeDelegation(stakeKey, ownerKey); - // include already delegated coins - bool fUseDelegated = false; - if (params.size() > 4 && !params[4].isNull()) - fUseDelegated = params[4].get_bool(); - - // Check amount - CAmount currBalance = pwalletMain->GetBalance() + (fUseDelegated ? pwalletMain->GetDelegatedBalance() : 0); - if (nValue > currBalance) - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); - - std::string strError; - EnsureWalletIsUnlocked(); - // Create the transaction CAmount nFeeRequired; if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError, NULL, ALL_COINS, /*fUseIX*/ false, (CAmount)0, fUseDelegated)) { From e67c152dfad928a60ffecf0e2c0d1680a38c38c3 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 7 Aug 2019 01:27:18 +0200 Subject: [PATCH 33/59] [Cleanup] minor styling in standard.cpp and remove fStakeDelegationVoided in wallet fStakeDelegationVoided is not needed right now and it will be addressed with the GUI --- src/script/standard.cpp | 3 +-- src/wallet/wallet.cpp | 4 ---- src/wallet/wallet.h | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 96ca2dc33288..9fb1066d46bc 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -239,8 +239,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet, addressRet = CScriptID(uint160(vSolutions[0])); return true; } else if (whichType == TX_COLDSTAKE) { - int sol = fColdStake ? 0 : 1; - addressRet = CKeyID(uint160(vSolutions[sol])); + addressRet = CKeyID(uint160(vSolutions[!fColdStake])); return true; } // Multisig txns have more than one address... diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 913f2240f620..30bb283fdbf7 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2516,9 +2516,6 @@ bool CWallet::CreateTransaction(const std::vector >& for (PAIRTYPE(const CWalletTx*, unsigned int) pcoin : setCoins) { - if(pcoin.first->vout[pcoin.second].scriptPubKey.IsPayToColdStaking()) - // P2CS contract is being spent - wtxNew.fStakeDelegationVoided = true; CAmount nCredit = pcoin.first->vout[pcoin.second].nValue; //The coin age after the next block (depth+1) is used instead of the current, //reflecting an assumption the user would accept a bit more delay for @@ -5699,7 +5696,6 @@ void CWalletTx::Init(const CWallet* pwalletIn) fColdCreditCached = false; fDelegatedDebitCached = false; fDelegatedCreditCached = false; - fStakeDelegationVoided = false; nDebitCached = 0; nCreditCached = 0; nImmatureCreditCached = 0; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index df4f2d5031ed..647c644578e8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -725,7 +725,6 @@ class CWalletTx : public CMerkleTx mutable bool fColdCreditCached; mutable bool fDelegatedDebitCached; mutable bool fDelegatedCreditCached; - mutable bool fStakeDelegationVoided; mutable CAmount nDebitCached; mutable CAmount nCreditCached; mutable CAmount nImmatureCreditCached; From 2a5bae7a5f799b1d0e90bbaef0ab4c956f63c00d Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 7 Aug 2019 01:27:55 +0200 Subject: [PATCH 34/59] [Script] Guard against GetPubKey() failures in Solver() --- src/script/sign.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 84768d67c05f..7d643c37c722 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -93,7 +93,8 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash else { CPubKey vch; - keystore.GetPubKey(keyID, vch); + if (!keystore.GetPubKey(keyID, vch)) + return error("%s : Unable to get public key from keyID", __func__); scriptSigRet << ToByteVector(vch); } return true; @@ -116,7 +117,8 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash return error("*** %s: failed to sign with the %s key.", __func__, fColdStake ? "cold staker" : "owner"); CPubKey vch; - keystore.GetPubKey(keyID, vch); + if (!keystore.GetPubKey(keyID, vch)) + return error("%s : Unable to get public key from keyID", __func__); scriptSigRet << (fColdStake ? (int)OP_TRUE : OP_FALSE) << ToByteVector(vch); return true; } From 75b8861bf17fb2a408e33d2c5590a6d45949944c Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 7 Aug 2019 06:40:11 +0200 Subject: [PATCH 35/59] [Script] Unify OP_CHECKCOLDSTAKEVERIFY checks Pass the script that we are spending to CTransaction::CheckColdStake(), directly from the interpreter. This way, there's no need for additional verifications in CheckTransaction (except for enforcement/activation). --- src/main.cpp | 28 +++------------------------- src/main.h | 5 ----- src/primitives/transaction.cpp | 14 +++++++------- src/primitives/transaction.h | 2 +- src/script/interpreter.cpp | 3 +-- src/script/interpreter.h | 6 +++--- 6 files changed, 15 insertions(+), 43 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 72fb5380d7b3..1b4af4c268d5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1174,25 +1174,6 @@ bool CheckZerocoinSpend(const CTransaction& tx, bool fVerifySignature, CValidati return fValidated; } -bool CheckColdStake(const CTransaction& tx, CValidationState& state, bool fColdStakingActive) -{ - CTxOut prevOut; - if(!GetOutput(tx.vin[0].prevout.hash, tx.vin[0].prevout.n, state, prevOut)) - return state.DoS(100, error("%s : invalid input", __func__), REJECT_INVALID, "bad-txns-inputs"); - - if (!prevOut.scriptPubKey.IsPayToColdStaking()) - return true; - - if (!fColdStakingActive) - return state.DoS(100, error("%s : invalid input", __func__), REJECT_INVALID, "coldstake-not-active"); - - // spending to the same contract - if (prevOut.scriptPubKey != tx.vout[1].scriptPubKey) - return state.DoS(100, error("%s : invalid scripts", __func__), REJECT_INVALID, "bad-txns-cold-stake"); - - return true; -} - bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fRejectBadUTXO, CValidationState& state, bool fFakeSerialAttack, bool fColdStakingActive) { // Basic checks that don't depend on any context @@ -1241,10 +1222,6 @@ bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fReject } } - // Additional check for cold staking - if (tx.IsCoinStake() && !tx.HasZerocoinSpendInputs() && !CheckColdStake(tx, state, fColdStakingActive)) - return state.DoS(100, error("%s: invalid cold stake", __func__), REJECT_INVALID, "bad-txns-cold-stake"); - std::set vInOutPoints; std::set vZerocoinSpendSerials; int nZCSpendCount = 0; @@ -4474,8 +4451,9 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo if (!IsInitialBlockDownload() && block.vtx[1].HasP2CSOutputs() && !sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) { const int outputs = block.vtx[1].vout.size(); if (outputs >=3 && block.vtx[1].vout[outputs-1].scriptPubKey != block.vtx[1].vout[outputs-2].scriptPubKey) - return state.DoS(100, error("%s: Wrong cold staking outputs when masternode payment enforcement is disabled on tx %s", - __func__, block.vtx[1].ToString().c_str())); + return state.DoS(100, error("%s: Wrong cold staking outputs when masternode payment enforcement is disabled: " + "script[-1] (%s) != script[-2] (%s)", __func__, + HexStr(block.vtx[1].vout[outputs-1].scriptPubKey), HexStr(block.vtx[1].vout[outputs-2].scriptPubKey))); } } diff --git a/src/main.h b/src/main.h index bdb6f61f3bfb..a7473c0476c7 100644 --- a/src/main.h +++ b/src/main.h @@ -351,11 +351,6 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& ma */ bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& view, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector* pvChecks = NULL); -/* - * Check cold stakes P2CS script rules and enforcement activation - */ -bool CheckColdStake(const CTransaction& tx, CValidationState& state, bool fColdStakingActive); - /** Apply the effects of this transaction on the UTXO set represented by view */ void UpdateCoins(const CTransaction& tx, CValidationState& state, CCoinsViewCache& inputs, CTxUndo& txundo, int nHeight); diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 054d836c6f80..1eab8fc29a73 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -209,13 +209,14 @@ bool CTransaction::IsCoinStake() const return (vout.size() >= 2 && vout[0].IsEmpty()); } -bool CTransaction::CheckColdStake() const +bool CTransaction::CheckColdStake(const CScript& script) const { + // tx is a coinstake tx if (!IsCoinStake()) return false; - // all inputs have the same pubKeyScript + // all inputs have the same scriptSig CScript firstScript = vin[0].scriptSig; if (vin.size() > 1) { for (unsigned int i=1; i 3) { + // have the same pubKeyScript and it matches the script we are spending + if (vout[1].scriptPubKey != script) return false; + if (vin.size() > 3) { for (unsigned int i=2; i >& stack, const CScript& case OP_CHECKCOLDSTAKEVERIFY: { // check it is used in a valid cold stake transaction. - if(!checker.CheckColdStake()) { + if(!checker.CheckColdStake(script)) { return set_error(serror, SCRIPT_ERR_CHECKCOLDSTAKEVERIFY); } - // CheckTransaction verifies that prevout scripts match with out scripts } break; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index f4a49fd9e2a6..137814da118b 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -98,7 +98,7 @@ class BaseSignatureChecker return false; } - virtual bool CheckColdStake() const + virtual bool CheckColdStake(const CScript& script) const { return false; } @@ -119,8 +119,8 @@ class TransactionSignatureChecker : public BaseSignatureChecker TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn) : txTo(txToIn), nIn(nInIn) {} bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode) const; bool CheckLockTime(const CScriptNum& nLockTime) const; - bool CheckColdStake() const override { - return txTo->CheckColdStake(); + bool CheckColdStake(const CScript& script) const override { + return txTo->CheckColdStake(script); } }; From 618afdbf4ff22394fbcb7387967c1942997e9260 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 7 Aug 2019 06:41:35 +0200 Subject: [PATCH 36/59] [Tests] Additional case for Cold Staking functional test and add a better log message to CheckProofOfStake --- src/kernel.cpp | 9 ++++- test/functional/feature_coldStaking.py | 51 +++++++++++++++++++++----- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/kernel.cpp b/src/kernel.cpp index 7cf67cd81bd3..bfb515c89064 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -449,8 +449,13 @@ bool initStakeInput(const CBlock block, std::unique_ptr& stake, int __func__, txin.prevout.hash.GetHex(), block.GetHash().GetHex()); //verify signature and script - if (!VerifyScript(txin.scriptSig, txPrev.vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&tx, 0))) - return error("%s : VerifySignature failed on coinstake %s", __func__, tx.GetHash().ToString().c_str()); + ScriptError serror; + if (!VerifyScript(txin.scriptSig, txPrev.vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&tx, 0), &serror)) { + std::string strErr = ""; + if (serror && ScriptErrorString(serror)) + strErr = strprintf("with the following error: %s", ScriptErrorString(serror)); + return error("%s : VerifyScript failed on coinstake %s %s", __func__, tx.GetHash().ToString(), strErr); + } CPivStake* pivInput = new CPivStake(); pivInput->SetInput(txPrev, txin.prevout.n); diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py index 559ec56e08c0..5fbb595dda95 100755 --- a/test/functional/feature_coldStaking.py +++ b/test/functional/feature_coldStaking.py @@ -253,19 +253,19 @@ def run_test(self): # 10) check that the staker cannot stake a block changing the coinstake scriptPubkey. # ---------------------------------------------------------------------------------- print("*** 10 ***") - self.log.info("Generating one invalid cold-stake block (changing coinstake output)...") + self.log.info("Generating one invalid cold-stake block (changing first coinstake output)...") stakeable_coins = getDelegatedUtxos(self.nodes[0].listunspent()) block_n = self.nodes[1].getblockcount() block_hash = self.nodes[1].getblockhash(block_n) prevouts = self.get_prevouts(stakeable_coins, 1) assert(len(prevouts) > 0) # Create the block - new_block = self.create_block(block_hash, prevouts, block_n+1, 1, staker_address, fInvalid=True) + new_block = self.create_block(block_hash, prevouts, block_n+1, 1, staker_address, fInvalid=1) self.log.info("New block created (rawtx) by cold-staking. Trying to submit...") # Try to submit the block ret = self.nodes[1].submitblock(bytes_to_hex_str(new_block.serialize())) self.log.info("Block %s submitted." % new_block.hash) - assert("bad-txns-cold-stake" in ret) + assert("rejected" in ret) # Verify that nodes[0] rejects it self.sync_all() try: @@ -276,12 +276,38 @@ def run_test(self): pass - # 11) Now node[0] gets mad and spends all the delegated coins, voiding the P2CS contracts. + # 11) neither adding different outputs to the coinstake. + # ------------------------------------------------------ + print("*** 11 ***") + self.log.info("Generating another invalid cold-stake block (adding coinstake output)...") + stakeable_coins = getDelegatedUtxos(self.nodes[0].listunspent()) + block_n = self.nodes[1].getblockcount() + block_hash = self.nodes[1].getblockhash(block_n) + prevouts = self.get_prevouts(stakeable_coins, 1) + assert(len(prevouts) > 0) + # Create the block + new_block = self.create_block(block_hash, prevouts, block_n+1, 1, staker_address, fInvalid=2) + self.log.info("New block created (rawtx) by cold-staking. Trying to submit...") + # Try to submit the block + ret = self.nodes[1].submitblock(bytes_to_hex_str(new_block.serialize())) + self.log.info("Block %s submitted." % new_block.hash) + assert("rejected" in ret) + # Verify that nodes[0] rejects it + self.sync_all() + try: + self.nodes[0].getblock(new_block.hash) + except JSONRPCException as e: + assert("Block not found" in str(e)) + self.log.info("Great. Malicious cold-staked block was NOT accepted!") + pass + + + # 12) Now node[0] gets mad and spends all the delegated coins, voiding the P2CS contracts. # ---------------------------------------------------------------------------------------- self.log.info("Let's void the contracts.") self.nodes[0].generate(1) self.sync_all() - print("*** 11 ***") + print("*** 12 ***") self.log.info("Cancel the stake delegation spending the cold stakes...") delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) txhash = self.spendUTXOsWithNode(delegated_utxos, 0) @@ -295,9 +321,9 @@ def run_test(self): self.log.info("Balances check out after the delegations have been voided.") - # 12) check that coinstaker is empty and can no longer stake. + # 13) check that coinstaker is empty and can no longer stake. # ----------------------------------------------------------- - print("*** 12 ***") + print("*** 13 ***") self.log.info("Trying to generate one cold-stake block again...") try: self.nodes[1].generate(1) @@ -367,7 +393,7 @@ def get_prevouts(self, coins, node_n, confs=15): - def create_block(self, prev_hash, staking_prevouts, height, node_n, s_address, fInvalid=False): + def create_block(self, prev_hash, staking_prevouts, height, node_n, s_address, fInvalid=0): api = self.nodes[node_n] # Get current time current_time = int(time.time()) @@ -397,7 +423,7 @@ def create_block(self, prev_hash, staking_prevouts, height, node_n, s_address, f stake_tx_unsigned.vout.append(CTxOut()) stake_tx_unsigned.vout.append(CTxOut(outNValue, hex_str_to_bytes(prevScript))) - if fInvalid: + if fInvalid == 1: # Create a new private key and get the corresponding public key block_sig_key = CECKey() block_sig_key.set_secretbytes(hash256(pack(' Date: Wed, 4 Sep 2019 02:44:25 +0200 Subject: [PATCH 37/59] [Tests] Fix coldstaking functional test --- src/chainparams.cpp | 7 ++- src/kernel.cpp | 2 +- test/functional/feature_coldStaking.py | 86 ++++++++++++++++---------- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index dd9c99c9bb20..7c278f8582e1 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -121,9 +121,12 @@ libzerocoin::ZerocoinParams* CChainParams::Zerocoin_Params(bool useModulusV1) co bool CChainParams::HasStakeMinAgeOrDepth(const int contextHeight, const uint32_t contextTime, const int utxoFromBlockHeight, const uint32_t utxoFromBlockTime) const { + if (NetworkID() == CBaseChainParams::REGTEST) + return true; + // before stake modifier V2, the age required was 60 * 60 (1 hour) / not required on regtest if (!IsStakeModifierV2(contextHeight)) - return (NetworkID() == CBaseChainParams::REGTEST || (utxoFromBlockTime + 3600 <= contextTime)); + return (utxoFromBlockTime + 3600 <= contextTime); // after stake modifier V2, we require the utxo to be nStakeMinDepth deep in the chain return (contextHeight - utxoFromBlockHeight >= nStakeMinDepth); @@ -242,7 +245,7 @@ class CMainParams : public CChainParams base58Prefixes[SECRET_KEY] = std::vector(1, 212); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x02)(0x2D)(0x25)(0x33).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x02)(0x21)(0x31)(0x2B).convert_to_container >(); - // BIP44 coin type is from https://github.com/satoshilabs/slips/blob/master/slip-0044.md + // BIP44 coin type is from https://github.com/satoshilabs/slips/blob/master/slip-0044.md base58Prefixes[EXT_COIN_TYPE] = boost::assign::list_of(0x80)(0x00)(0x00)(0x77).convert_to_container >(); convertSeed6(vFixedSeeds, pnSeed6_main, ARRAYLEN(pnSeed6_main)); diff --git a/src/kernel.cpp b/src/kernel.cpp index bfb515c89064..b93e01ab3055 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -376,7 +376,7 @@ bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int while (nTryTime < maxTime) { //new block came in, move on - if (chainActive.Height() != prevHeight) + if (chainActive.Height() != prevHeight && Params().NetworkID() == CBaseChainParams::REGTEST) break; ++nTryTime; diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py index 5fbb595dda95..527efadfe5c8 100755 --- a/test/functional/feature_coldStaking.py +++ b/test/functional/feature_coldStaking.py @@ -17,7 +17,8 @@ from test_framework.pivx_node import PivxTestNode from test_framework.script import CScript, OP_CHECKSIG from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import hash256, connect_nodes_bi, p2p_port, bytes_to_hex_str, hex_str_to_bytes +from test_framework.util import hash256, connect_nodes_bi, p2p_port, bytes_to_hex_str, \ + hex_str_to_bytes, assert_equal, assert_greater_than # filter utxos based on first 5 bytes of scriptPubKey def getDelegatedUtxos(utxos): @@ -28,8 +29,8 @@ class PIVX_ColdStakingTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 2 - self.extra_args = [['-staking=1']]*self.num_nodes + self.num_nodes = 3 + self.extra_args = [['-staking=1']] * self.num_nodes def setup_network(self): @@ -65,25 +66,32 @@ def run_test(self): LAST_POW_BLOCK = 250 NUM_OF_INPUTS = 20 INPUT_VALUE = 50 - INITAL_MINED_BLOCKS = LAST_POW_BLOCK - 40 + INITAL_MINED_BLOCKS = 200 # nodes[0] - coin-owner # nodes[1] - cold-staker - # 1) nodes[0] mines first blocks. - # ----------------------------------- + # 1) nodes[0] mines 20 blocks. nodes[2] mines all the others. + # ----------------------------------------------------------- print("*** 1 ***") self.log.info("Mining %d blocks..." % INITAL_MINED_BLOCKS) for i in range(1, INITAL_MINED_BLOCKS+1): - self.nodes[0].generate(1) + if i % 10 == 0: + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + else: + self.generateBlock() if i % 25 == 0: self.log.info("%d Blocks mined." % i) - self.sync_all() - # 2) nodes[1] generates a cold-staking address. + + # 2) nodes[0] generates a owner address + # nodes[1] generates a cold-staking address. # --------------------------------------------- print("*** 2 ***") + self.sync_all() owner_address = self.nodes[0].getnewaddress() self.log.info("Owner Address: %s" % owner_address) staker_address = self.nodes[1].getnewstakingaddress() @@ -102,11 +110,11 @@ def run_test(self): assert("The transaction was rejected!" in str(e)) self.log.info("Good. Cold Staking NOT ACTIVE yet.") pass - self.log.info("Mining 42 blocks to get to cold staking activation...") - for i in range(1, 43): - self.nodes[0].generate(1) + self.log.info("Mining 51 blocks to get to cold staking activation...") + for i in range(1, 52): + self.generateBlock() self.sync_all() - if i % 7 == 0: + if i % 10 == 0: self.log.info("%d Blocks mined." % i) @@ -136,9 +144,9 @@ def run_test(self): for i in range(NUM_OF_INPUTS): res = self.nodes[0].delegatestake(staker_address, INPUT_VALUE, owner_address) assert(res != None and res["txid"] != None and res["txid"] != "") - assert(res["owner_address"] == owner_address) - assert(res["staker_address"] == staker_address) - self.nodes[0].generate(1) + assert_equal(res["owner_address"], owner_address) + assert_equal(res["staker_address"], staker_address) + self.generateBlock() self.sync_all() self.log.info("%d Txes created." % NUM_OF_INPUTS) # check balances: @@ -151,12 +159,12 @@ def run_test(self): print("*** 5 ***") self.log.info("Spending back one of the delegated UTXOs...") delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) - assert(len(delegated_utxos) > 0) + assert_greater_than(len(delegated_utxos), 0) u = delegated_utxos[0] txhash = self.spendUTXOwithNode(u, 0) assert(txhash != None) self.log.info("Good. Owner was able to spend - tx: %s" % str(txhash)) - self.nodes[0].generate(1) + self.generateBlock() self.sync_all() # check balances after spend. self.expected_balance -= float(u["amount"]) @@ -169,7 +177,7 @@ def run_test(self): # ----------------------------------------------------------- self.log.info("Staking 15 blocks to mature P2CS stake delegations...") for i in range(1, 16): - self.nodes[0].generate(1) + self.generateBlock() self.sync_all() if i % 5 == 0: self.log.info("%d Blocks added." % i) @@ -193,7 +201,7 @@ def run_test(self): print("*** 7 ***") self.log.info("Trying to spend one of the delegated UTXOs with the cold-staking key...") delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) - assert(len(delegated_utxos) > 0) + assert_greater_than(len(delegated_utxos), 0) u = delegated_utxos[0] try: self.spendUTXOwithNode(u, 1) @@ -202,7 +210,7 @@ def run_test(self): assert("Script failed an OP_CHECKCOLDSTAKEVERIFY operation" in str(e)) self.log.info("Good. Cold staker was NOT able to spend (failed OP_CHECKCOLDSTAKEVERIFY)") pass - self.nodes[0].generate(1) + self.generateBlock() self.sync_all() @@ -216,7 +224,7 @@ def run_test(self): self.log.info("Block %s submitted" % newblockhash) # Verify that nodes[0] accepts it self.sync_all() - self.nodes[0].getblock(newblockhash) + assert_equal(newblockhash, self.nodes[0].getbestblockhash()) self.log.info("Great. Cold-staked block was accepted!") # check balances after staked block. self.expected_balance += 250 @@ -232,7 +240,7 @@ def run_test(self): block_n = self.nodes[1].getblockcount() block_hash = self.nodes[1].getblockhash(block_n) prevouts = self.get_prevouts(stakeable_coins, 1) - assert(len(prevouts) > 0) + assert_greater_than(len(prevouts), 0) # Create the block new_block = self.create_block(block_hash, prevouts, block_n+1, 1, staker_address) self.log.info("New block created (rawtx) by cold-staking. Trying to submit...") @@ -242,7 +250,7 @@ def run_test(self): assert(ret is None) # Verify that nodes[0] accepts it self.sync_all() - self.nodes[0].getblock(new_block.hash) + assert_equal(new_block.hash, self.nodes[0].getbestblockhash()) self.log.info("Great. Cold-staked block was accepted!") # check balances after staked block. self.expected_balance += 250 @@ -258,7 +266,7 @@ def run_test(self): block_n = self.nodes[1].getblockcount() block_hash = self.nodes[1].getblockhash(block_n) prevouts = self.get_prevouts(stakeable_coins, 1) - assert(len(prevouts) > 0) + assert_greater_than(len(prevouts), 0) # Create the block new_block = self.create_block(block_hash, prevouts, block_n+1, 1, staker_address, fInvalid=1) self.log.info("New block created (rawtx) by cold-staking. Trying to submit...") @@ -284,7 +292,7 @@ def run_test(self): block_n = self.nodes[1].getblockcount() block_hash = self.nodes[1].getblockhash(block_n) prevouts = self.get_prevouts(stakeable_coins, 1) - assert(len(prevouts) > 0) + assert_greater_than(len(prevouts), 0) # Create the block new_block = self.create_block(block_hash, prevouts, block_n+1, 1, staker_address, fInvalid=2) self.log.info("New block created (rawtx) by cold-staking. Trying to submit...") @@ -305,7 +313,7 @@ def run_test(self): # 12) Now node[0] gets mad and spends all the delegated coins, voiding the P2CS contracts. # ---------------------------------------------------------------------------------------- self.log.info("Let's void the contracts.") - self.nodes[0].generate(1) + self.generateBlock() self.sync_all() print("*** 12 ***") self.log.info("Cancel the stake delegation spending the cold stakes...") @@ -313,7 +321,7 @@ def run_test(self): txhash = self.spendUTXOsWithNode(delegated_utxos, 0) assert(txhash != None) self.log.info("Good. Owner was able to void the stake delegations - tx: %s" % str(txhash)) - self.nodes[0].generate(1) + self.generateBlock() self.sync_all() # check balances after big spend. self.expected_balance = 2 * (INPUT_VALUE + 250) @@ -334,19 +342,33 @@ def run_test(self): pass + def generateBlock(self): + fStaked = False + while (not fStaked): + try: + self.nodes[2].generate(1) + fStaked = True + except JSONRPCException as e: + if ("Couldn't create new block" in str(e)): + # Sleep two seconds and retry + time.sleep(2) + else: + raise e + + def checkBalances(self): w_info = self.nodes[0].getwalletinfo() self.log.info("OWNER - Delegated %f / Cold %f" % ( float(w_info["delegated_balance"]), w_info["cold_staking_balance"])) - assert(float(w_info["delegated_balance"]) == self.expected_balance) - assert (float(w_info["cold_staking_balance"]) == 0) + assert_equal(float(w_info["delegated_balance"]), self.expected_balance) + assert_equal(float(w_info["cold_staking_balance"]), 0) w_info = self.nodes[1].getwalletinfo() self.log.info("STAKER - Delegated %f / Cold %f" % ( float(w_info["delegated_balance"]), w_info["cold_staking_balance"])) - assert(float(w_info["delegated_balance"]) == 0) - assert (float(w_info["cold_staking_balance"]) == self.expected_balance) + assert_equal(float(w_info["delegated_balance"]), 0) + assert_equal(float(w_info["cold_staking_balance"]), self.expected_balance) From 5383f5258652f5dad2c68eb23c81541dc4609466 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 2 Oct 2019 02:08:48 +0200 Subject: [PATCH 38/59] [Fix] MintableCoins include cold staking balance --- src/wallet/wallet.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 30bb283fdbf7..f5473b937087 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2197,7 +2197,7 @@ bool CWallet::SelectStakeCoins(std::list >& listInp bool CWallet::MintableCoins() { LOCK(cs_main); - CAmount nBalance = GetBalance(); + CAmount nBalance = GetBalance() + GetColdStakingBalance(); CAmount nZpivBalance = GetZerocoinBalance(false); int chainHeight = chainActive.Height(); @@ -2210,7 +2210,8 @@ bool CWallet::MintableCoins() return false; std::vector vCoins; - AvailableCoins(vCoins, true); + // include cold, exclude delegated + AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, GetBoolArg("-coldstaking", true), false); int64_t time = GetAdjustedTime(); for (const COutput& out : vCoins) { From f771f86dcebf835ea68bcb7737c5c59586c183e7 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 2 Oct 2019 12:20:40 +0200 Subject: [PATCH 39/59] [Trivial] define GetSakingBalance and update it in miner --- src/miner.cpp | 7 ++++++- src/wallet/wallet.cpp | 9 +++++++-- src/wallet/wallet.h | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/miner.cpp b/src/miner.cpp index fcb34af79c9c..f8474f0a44fe 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -660,6 +660,9 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) CReserveKey reservekey(pwallet); unsigned int nExtraNonce = 0; bool fLastLoopOrphan = false; + bool fColdStake = GetBoolArg("-coldstaking", true); + CAmount stakingBalance = 0; + while (fGenerateBitcoins || fProofOfStake) { if (fProofOfStake) { if (chainActive.Tip()->nHeight < Params().LAST_POW_BLOCK()) { @@ -673,10 +676,11 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) { nMintableLastCheck = GetTime(); fMintableCoins = pwallet->MintableCoins(); + stakingBalance = pwallet->GetStakingBalance(fColdStake); } while (vNodes.empty() || pwallet->IsLocked() || !fMintableCoins || - (pwallet->GetBalance() > 0 && nReserveBalance >= pwallet->GetBalance()) || !masternodeSync.IsSynced()) { + (stakingBalance > 0 && nReserveBalance >= stakingBalance) || !masternodeSync.IsSynced()) { nLastCoinStakeSearchInterval = 0; MilliSleep(5000); // Do a separate 1 minute check here to ensure fMintableCoins is updated @@ -684,6 +688,7 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) { nMintableLastCheck = GetTime(); fMintableCoins = pwallet->MintableCoins(); + stakingBalance = pwallet->GetStakingBalance(fColdStake); } } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f5473b937087..cdb41347c676 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1742,6 +1742,11 @@ CAmount CWallet::GetColdStakingBalance() const return nTotal; } +CAmount CWallet::GetStakingBalance(const bool fIncludeColdStaking) const +{ + return GetBalance() + (fIncludeColdStaking ? GetColdStakingBalance() : 0); +} + CAmount CWallet::GetDelegatedBalance() const { CAmount nTotal = 0; @@ -2197,7 +2202,7 @@ bool CWallet::SelectStakeCoins(std::list >& listInp bool CWallet::MintableCoins() { LOCK(cs_main); - CAmount nBalance = GetBalance() + GetColdStakingBalance(); + CAmount nBalance = GetStakingBalance(GetBoolArg("-coldstaking", true)); CAmount nZpivBalance = GetZerocoinBalance(false); int chainHeight = chainActive.Height(); @@ -2684,7 +2689,7 @@ bool CWallet::CreateCoinStake( txNew.vout.push_back(CTxOut(0, scriptEmpty)); // Choose coins to use - CAmount nBalance = GetBalance() + GetColdStakingBalance(); + CAmount nBalance = GetStakingBalance(); if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) return error("CreateCoinStake : invalid reserve balance amount"); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 647c644578e8..a339bdd39317 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -428,7 +428,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void ResendWalletTransactions(); CAmount GetBalance() const; CAmount GetColdStakingBalance() const; // delegated coins for which we have the staking key - CAmount GetDelegatedBalance() const; // delegated coins for which we have the spending key + CAmount GetStakingBalance(const bool fIncludeColdStaking = true) const; + CAmount GetDelegatedBalance() const; // delegated coins for which we have the spending key CAmount GetZerocoinBalance(bool fMatureOnly) const; CAmount GetUnconfirmedZerocoinBalance() const; CAmount GetImmatureZerocoinBalance() const; From 4000b6e766ecf880e7470e4e2b685dd008207e86 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 2 Oct 2019 12:21:34 +0200 Subject: [PATCH 40/59] [RPC] Define listcoldutxos function to list all P2CS utxos belonging to the wallet (either as cold-staker or coin-owner) --- src/rpc/client.cpp | 1 + src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + src/wallet/rpcwallet.cpp | 67 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 3801635151b2..14d2cda79354 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -48,6 +48,7 @@ static const CRPCConvertParam vRPCConvertParams[] = {"settxfee", 0}, {"getreceivedbyaddress", 1}, {"getreceivedbyaccount", 1}, + {"listcoldutxos", 0}, {"listreceivedbyaddress", 0}, {"listreceivedbyaddress", 1}, {"listreceivedbyaddress", 2}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index a576ac315a77..a9bb670a895f 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -427,6 +427,7 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "keypoolrefill", &keypoolrefill, true, false, true}, {"wallet", "listaccounts", &listaccounts, false, false, true}, {"wallet", "listaddressgroupings", &listaddressgroupings, false, false, true}, + {"wallet", "listcoldutxos", &listcoldutxos, false, false, true}, {"wallet", "listlockunspent", &listlockunspent, false, false, true}, {"wallet", "listreceivedbyaccount", &listreceivedbyaccount, false, false, true}, {"wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, false, true}, diff --git a/src/rpc/server.h b/src/rpc/server.h index 75d1e9a166ca..6c66ef45a97d 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -235,6 +235,7 @@ extern UniValue movecmd(const UniValue& params, bool fHelp); extern UniValue sendfrom(const UniValue& params, bool fHelp); extern UniValue sendmany(const UniValue& params, bool fHelp); extern UniValue addmultisigaddress(const UniValue& params, bool fHelp); +extern UniValue listcoldutxos(const UniValue& params, bool fHelp); extern UniValue listreceivedbyaddress(const UniValue& params, bool fHelp); extern UniValue listreceivedbyaccount(const UniValue& params, bool fHelp); extern UniValue listtransactions(const UniValue& params, bool fHelp); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 91b1205316b9..ce2c1b52c127 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1609,6 +1609,73 @@ UniValue listreceivedbyaccount(const UniValue& params, bool fHelp) return ListReceived(params, true); } +UniValue listcoldutxos(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw std::runtime_error( + "listcoldutxos ( nonWhitelistedOnly )\n" + "\nList P2CS unspent outputs received by this wallet as cold-staker-\n" + + "\nArguments:\n" + "1. nonWhitelistedOnly (boolean, optional, default=false) Whether to exclude P2CS from whitelisted delegators.\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"txid\" : \"true\", (string) The transaction id of the P2CS utxo\n" + " \"txidn\" : \"accountname\", (string) The output number of the P2CS utxo\n" + " \"amount\" : x.xxx, (numeric) The amount of the P2CS utxo\n" + " \"confirmations\" : n (numeric) The number of confirmations of the P2CS utxo\n" + " \"cold-staker\" : n (string) The cold-staker address of the P2CS utxo\n" + " \"coin-owner\" : n (string) The coin-owner address of the P2CS utxo\n" + " \"whitelisted\" : n (string) \"true\"/\"false\" coin-owner in delegator whitelist\n" + " }\n" + " ,...\n" + "]\n" + + "\nExamples:\n" + + HelpExampleCli("listcoldutxos", "") + HelpExampleCli("listcoldutxos", "true")); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + bool fExcludeWhitelisted = false; + if (params.size() > 0) + fExcludeWhitelisted = params[0].get_bool(); + UniValue results(UniValue::VARR); + + for (std::map::const_iterator it = + pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { + const uint256& wtxid = it->first; + const CWalletTx* pcoin = &(*it).second; + + for (unsigned int i = 0; i < pcoin->vout.size(); i++) { + const CTxOut& out = pcoin->vout[i]; + isminetype mine = pwalletMain->IsMine(out); + if (!bool(mine & ISMINE_COLD) && !bool(mine & ISMINE_SPENDABLE_DELEGATED)) + continue; + txnouttype type; + std::vector addresses; + int nRequired; + if (!ExtractDestinations(out.scriptPubKey, type, addresses, nRequired)) + continue; + const bool fWhitelisted = pwalletMain->mapAddressBook.count(addresses[1]) > 0; + if (fExcludeWhitelisted && fWhitelisted) + continue; + UniValue entry(UniValue::VOBJ); + entry.push_back(Pair("txid", wtxid.GetHex())); + entry.push_back(Pair("txidn", (int)i)); + entry.push_back(Pair("amount", ValueFromAmount(out.nValue))); + entry.push_back(Pair("confirmations", pcoin->GetDepthInMainChain(false))); + entry.push_back(Pair("cold-staker", CBitcoinAddress(addresses[0]).ToString())); + entry.push_back(Pair("coin-owner", CBitcoinAddress(addresses[1]).ToString())); + entry.push_back(Pair("whitelisted", fWhitelisted ? "true" : "false")); + results.push_back(entry); + } + } + + return results; +} + static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) { CBitcoinAddress addr; From 968931e6cdf557a29829bed255541b412770ffc6 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 2 Oct 2019 13:15:05 +0200 Subject: [PATCH 41/59] [RPC] listcoldutxos: fix staking addresses base58 prefix --- src/wallet/rpcwallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ce2c1b52c127..a138f3e9e6ca 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1666,7 +1666,7 @@ UniValue listcoldutxos(const UniValue& params, bool fHelp) entry.push_back(Pair("txidn", (int)i)); entry.push_back(Pair("amount", ValueFromAmount(out.nValue))); entry.push_back(Pair("confirmations", pcoin->GetDepthInMainChain(false))); - entry.push_back(Pair("cold-staker", CBitcoinAddress(addresses[0]).ToString())); + entry.push_back(Pair("cold-staker", CBitcoinAddress(addresses[0], CChainParams::STAKING_ADDRESS).ToString())); entry.push_back(Pair("coin-owner", CBitcoinAddress(addresses[1]).ToString())); entry.push_back(Pair("whitelisted", fWhitelisted ? "true" : "false")); results.push_back(entry); From 5209c3fc9fc9c7a9d88e83d590fcc46d7cf07e8c Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 2 Oct 2019 15:03:44 +0200 Subject: [PATCH 42/59] [DB] Fix unserialization check for cold staking txes --- src/wallet/walletdb.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 51f64655e75b..a652fa1a996e 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -486,8 +486,9 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW CWalletTx wtx; ssValue >> wtx; CValidationState state; - // false because there is no reason to go through the zerocoin checks for our own wallet - if (!(CheckTransaction(wtx, false, false, state) && (wtx.GetHash() == hash) && state.IsValid())) + // fZerocoinActive false because there is no reason to go through the zerocoin checks for our own wallet + // fColdStakingActive true to unserialize old P2CS outputs. + if (!(CheckTransaction(wtx, false, false, state, false, true) && (wtx.GetHash() == hash) && state.IsValid())) return false; // Undo serialize changes in 31600 From 87327fc63a810b63ba1c7be1827916cfaac47b02 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 2 Oct 2019 21:06:43 +0200 Subject: [PATCH 43/59] [RPC] listcoldutxos: include only final, trusted, unspent coins --- src/wallet/rpcwallet.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a138f3e9e6ca..4b08889de504 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1647,8 +1647,12 @@ UniValue listcoldutxos(const UniValue& params, bool fHelp) pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const uint256& wtxid = it->first; const CWalletTx* pcoin = &(*it).second; + if (!CheckFinalTx(*pcoin) || !pcoin->IsTrusted()) + continue; for (unsigned int i = 0; i < pcoin->vout.size(); i++) { + if(pwalletMain->IsSpent(wtxid, i)) + continue; const CTxOut& out = pcoin->vout[i]; isminetype mine = pwalletMain->IsMine(out); if (!bool(mine & ISMINE_COLD) && !bool(mine & ISMINE_SPENDABLE_DELEGATED)) From f7f7119d5a330f5cf19612a223a3df4d57d2e7f2 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 7 Oct 2019 01:34:51 +0200 Subject: [PATCH 44/59] [Trivial][RPC] Fix delegatoradd help text and remove extra arg fRescan --- src/rpc/client.cpp | 1 - src/wallet/rpcwallet.cpp | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 14d2cda79354..0a784aef0bd0 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -42,7 +42,6 @@ static const CRPCConvertParam vRPCConvertParams[] = {"rawdelegatestake", 1}, {"rawdelegatestake", 3}, {"rawdelegatestake", 4}, - {"delegatoradd", 1}, {"sendtoaddress", 1}, {"sendtoaddressix", 1}, {"settxfee", 0}, diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 4b08889de504..1c58ef207ab6 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -151,7 +151,7 @@ UniValue delegatoradd(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 1) throw std::runtime_error( - "delegatoradd \"addr\" ( fRescan )\n" + "delegatoradd \"addr\"\n" "\nAdd the provided address into the allowed delegators AddressBook.\n" "This enables the staking of coins delegated to this wallet, owned by \n" @@ -163,7 +163,6 @@ UniValue delegatoradd(const UniValue& params, bool fHelp) "\nExamples:\n" + HelpExampleCli("delegatoradd", "DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6") + - HelpExampleCli("delegatoradd", "DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6 true") + HelpExampleRpc("delegatoradd", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\"")); CBitcoinAddress address(params[0].get_str()); From d81ecdc8aaafea186a3aaf7b52e9424fefb975a6 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 7 Oct 2019 19:09:45 +0200 Subject: [PATCH 45/59] [Wallet] don't include P2CS in SelectStakeCoins if CS is not enabled --- src/wallet/wallet.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cdb41347c676..7a2bccb3ba21 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2136,7 +2136,8 @@ bool CWallet::SelectStakeCoins(std::list >& listInp std::vector vCoins; // include cold, exclude delegated - AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, GetBoolArg("-coldstaking", true), false); + const bool fIncludeCold = Params().Cold_Staking_Enabled(blockHeight) && GetBoolArg("-coldstaking", true); + AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, fIncludeCold, false); CAmount nAmountSelected = 0; if (GetBoolArg("-pivstake", true) && !fPrecompute) { @@ -2216,7 +2217,8 @@ bool CWallet::MintableCoins() std::vector vCoins; // include cold, exclude delegated - AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, GetBoolArg("-coldstaking", true), false); + const bool fIncludeCold = Params().Cold_Staking_Enabled(chainHeight) && GetBoolArg("-coldstaking", true); + AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, fIncludeCold, false); int64_t time = GetAdjustedTime(); for (const COutput& out : vCoins) { From b20bc528bbdab9fe2b35c3a003b79d3f396173f6 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 14 Oct 2019 16:51:26 +0200 Subject: [PATCH 46/59] [RPC] define getdelegatedbalance --- src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + src/wallet/rpcwallet.cpp | 126 ++++++++++++++++++--------------------- 3 files changed, 61 insertions(+), 67 deletions(-) diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index a9bb670a895f..4420f2527c90 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -410,6 +410,7 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "getaddressesbyaccount", &getaddressesbyaccount, true, false, true}, {"wallet", "getbalance", &getbalance, false, false, true}, {"wallet", "getcoldstakingbalance", &getcoldstakingbalance, false, false, true}, + {"wallet", "getdelegatedbalance", &getdelegatedbalance, false, false, true}, {"wallet", "getnewaddress", &getnewaddress, true, false, true}, {"wallet", "getnewstakingaddress", &getnewstakingaddress, true, false, true}, {"wallet", "getrawchangeaddress", &getrawchangeaddress, true, false, true}, diff --git a/src/rpc/server.h b/src/rpc/server.h index 6c66ef45a97d..3ae3a9bcff20 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -230,6 +230,7 @@ extern UniValue getreceivedbyaddress(const UniValue& params, bool fHelp); extern UniValue getreceivedbyaccount(const UniValue& params, bool fHelp); extern UniValue getbalance(const UniValue& params, bool fHelp); extern UniValue getcoldstakingbalance(const UniValue& params, bool fHelp); +extern UniValue getdelegatedbalance(const UniValue& params, bool fHelp); extern UniValue getunconfirmedbalance(const UniValue& params, bool fHelp); extern UniValue movecmd(const UniValue& params, bool fHelp); extern UniValue sendfrom(const UniValue& params, bool fHelp); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 1c58ef207ab6..b15bf565cbe6 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -963,16 +963,36 @@ CAmount GetAccountBalance(CWalletDB& walletdb, const std::string& strAccount, in if (!IsFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || depth < 0 || fConflicted) continue; - CAmount nReceived, nSent, nFee; - wtx.GetAccountAmounts(strAccount, nReceived, nSent, nFee, filter); + if (strAccount == "*") { + // Calculate total balance a different way from GetBalance() + // (GetBalance() sums up all unspent TxOuts) + CAmount allFee; + std::string strSentAccount; + std::list listReceived; + std::list listSent; + wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, filter); + if (wtx.GetDepthInMainChain() >= nMinDepth) { + for (const COutputEntry& r : listReceived) + nBalance += r.amount; + } + for (const COutputEntry& s : listSent) + nBalance -= s.amount; + nBalance -= allFee; + + } else { + + CAmount nReceived, nSent, nFee; + wtx.GetAccountAmounts(strAccount, nReceived, nSent, nFee, filter); - if (nReceived != 0 && depth >= nMinDepth) - nBalance += nReceived; - nBalance -= nSent + nFee; + if (nReceived != 0 && depth >= nMinDepth) + nBalance += nReceived; + nBalance -= nSent + nFee; + } } // Tally internal accounting entries - nBalance += walletdb.GetAccountCreditDebit(strAccount); + if (strAccount != "*") + nBalance += walletdb.GetAccountCreditDebit(strAccount); return nBalance; } @@ -1029,40 +1049,8 @@ UniValue getbalance(const UniValue& params, bool fHelp) if ( !(params.size() > 3) || params[3].get_bool() ) filter = filter | ISMINE_SPENDABLE_DELEGATED; - if (params[0].get_str() == "*") { - // Calculate total balance a different way from GetBalance() - // (GetBalance() sums up all unspent TxOuts) - // getbalance and "getbalance * 1 true" should return the same number - CAmount nBalance = 0; - for (std::map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { - const CWalletTx& wtx = (*it).second; - bool fConflicted; - int depth = wtx.GetDepthAndMempool(fConflicted); - - if (!IsFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || depth < 0 || fConflicted) - continue; - - CAmount allFee; - std::string strSentAccount; - std::list listReceived; - std::list listSent; - wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, filter); - if (depth >= nMinDepth) { - for (const COutputEntry& r : listReceived) - nBalance += r.amount; - } - for (const COutputEntry& s : listSent) - nBalance -= s.amount; - nBalance -= allFee; - } - return ValueFromAmount(nBalance); - } - - std::string strAccount = AccountFromValue(params[0]); - - CAmount nBalance = GetAccountBalance(strAccount, nMinDepth, filter); - - return ValueFromAmount(nBalance); + std::string strAccount = params[0].get_str(); + return ValueFromAmount(GetAccountBalance(strAccount, nMinDepth, filter)); } UniValue getcoldstakingbalance(const UniValue& params, bool fHelp) @@ -1094,38 +1082,42 @@ UniValue getcoldstakingbalance(const UniValue& params, bool fHelp) if (params.size() == 0) return ValueFromAmount(pwalletMain->GetColdStakingBalance()); - const int nMinDepth = 1; + std::string strAccount = params[0].get_str(); + return ValueFromAmount(GetAccountBalance(strAccount, /*nMinDepth*/ 1, ISMINE_COLD)); +} - if (params[0].get_str() == "*") { - // Calculate total balance a different way from GetBalance() - // (GetBalance() sums up all unspent TxOuts) - CAmount nBalance = 0; - for (std::map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { - const CWalletTx& wtx = (*it).second; - if (!IsFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0) - continue; +UniValue getdelegatedbalance(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw std::runtime_error( + "getdelegatedbalance ( \"account\" )\n" + "\nIf account is not specified, returns the server's total available delegated balance (sum of all utxos delegated\n" + "to a cold staking address to stake on behalf of addresses of this wallet).\n" + "If account is specified, returns the cold balance in the account.\n" + "Note that the account \"\" is not the same as leaving the parameter out.\n" + "The server total may be different to the balance in the default \"\" account.\n" - CAmount allFee; - std::string strSentAccount; - std::list listReceived; - std::list listSent; - wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, ISMINE_COLD); - if (wtx.GetDepthInMainChain() >= nMinDepth) { - for (const COutputEntry& r : listReceived) - nBalance += r.amount; - } - for (const COutputEntry& s : listSent) - nBalance -= s.amount; - nBalance -= allFee; - } - return ValueFromAmount(nBalance); - } + "\nArguments:\n" + "1. \"account\" (string, optional) The selected account, or \"*\" for entire wallet. It may be the default account using \"\".\n" - std::string strAccount = AccountFromValue(params[0]); + "\nResult:\n" + "amount (numeric) The total amount in PIV received for this account in P2CS contracts.\n" - CAmount nBalance = GetAccountBalance(strAccount, nMinDepth, ISMINE_COLD); + "\nExamples:\n" + "\nThe total amount in the server across all accounts\n" + + HelpExampleCli("getdelegatedbalance", "") + + "\nThe total amount in the account named tabby\n" + + HelpExampleCli("getdelegatedbalance", "\"tabby\"") + + "\nAs a json rpc call\n" + + HelpExampleRpc("getdelegatedbalance", "\"tabby\"")); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + if (params.size() == 0) + return ValueFromAmount(pwalletMain->GetDelegatedBalance()); - return ValueFromAmount(nBalance); + std::string strAccount = params[0].get_str(); + return ValueFromAmount(GetAccountBalance(strAccount, /*nMinDepth*/ 1, ISMINE_SPENDABLE_DELEGATED)); } UniValue getunconfirmedbalance(const UniValue ¶ms, bool fHelp) From 2f246d2e3f13e4a66c076732334b6a3ddc56a9ae Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 14 Oct 2019 17:02:21 +0200 Subject: [PATCH 47/59] [Wallet] fix IsValid and validateaddress for Staking Addresses --- src/base58.cpp | 6 ++++-- src/rpc/misc.cpp | 11 +++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/base58.cpp b/src/base58.cpp index 3c974367ca41..7c3f74a64ab6 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -299,12 +299,14 @@ bool CBitcoinAddress::GetKeyID(CKeyID& keyID) const bool CBitcoinAddress::IsScript() const { - return IsValid() && vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS); + bool fCorrectSize = vchData.size() == 20; + return fCorrectSize && vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS); } bool CBitcoinAddress::IsStakingAddress() const { - return IsValid() && vchVersion == Params().Base58Prefix(CChainParams::STAKING_ADDRESS); + bool fCorrectSize = vchData.size() == 20; + return fCorrectSize && vchVersion == Params().Base58Prefix(CChainParams::STAKING_ADDRESS); } void CBitcoinSecret::SetKey(const CKey& vchSecret) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 530e03be9994..9210da38f57e 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -267,7 +267,6 @@ class DescribeAddressVisitor : public boost::static_visitor UniValue operator()(const CScriptID &scriptID) const { UniValue obj(UniValue::VOBJ); obj.push_back(Pair("isscript", true)); - obj.push_back(Pair("iscoldstaking", false)); CScript subscript; pwalletMain->GetCScript(scriptID, subscript); std::vector addresses; @@ -363,11 +362,11 @@ UniValue validateaddress(const UniValue& params, bool fHelp) "\nResult:\n" "{\n" " \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n" - " \"address\" : \"pivxaddress\", (string) The pivx address validated\n" + " \"address\" : \"pivxaddress\", (string) The pivx address validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n" " \"ismine\" : true|false, (boolean) If the address is yours or not\n" - " \"iscoldstaking\" : true|false, (boolean) If the address is a PIVX cold staking address\n" - " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" + " \"isstaking\" : true|false, (boolean) If the address is a staking address for PIVX cold staking\n" + " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" " \"isscript\" : true|false, (boolean) If the key is a script\n" " \"hex\" : \"hex\", (string, optional) The redeemscript for the P2SH address\n" " \"pubkey\" : \"publickeyhex\", (string) The hex value of the raw public key\n" @@ -398,8 +397,8 @@ UniValue validateaddress(const UniValue& params, bool fHelp) #ifdef ENABLE_WALLET isminetype mine = pwalletMain ? IsMine(*pwalletMain, dest) : ISMINE_NO; - ret.push_back(Pair("ismine", bool(mine & ISMINE_SPENDABLE_ALL))); - ret.push_back(Pair("iscoldstaking", bool(mine & ISMINE_COLD))); + ret.push_back(Pair("ismine", bool(mine & (ISMINE_SPENDABLE_ALL | ISMINE_COLD)))); + ret.push_back(Pair("isstaking", address.IsStakingAddress())); ret.push_back(Pair("iswatchonly", bool(mine & ISMINE_WATCH_ONLY))); UniValue detail = boost::apply_visitor(DescribeAddressVisitor(mine), dest); ret.pushKVs(detail); From 23ecdeeec0b48942e7f61534143bd57019363dc4 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 14 Oct 2019 17:39:45 +0200 Subject: [PATCH 48/59] [RPC] define listdelegators --- src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + src/wallet/rpcwallet.cpp | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 4420f2527c90..001c627ecd21 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -427,6 +427,7 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "importaddress", &importaddress, true, false, true}, {"wallet", "keypoolrefill", &keypoolrefill, true, false, true}, {"wallet", "listaccounts", &listaccounts, false, false, true}, + {"wallet", "listdelegators", &listdelegators, false, false, true}, {"wallet", "listaddressgroupings", &listaddressgroupings, false, false, true}, {"wallet", "listcoldutxos", &listcoldutxos, false, false, true}, {"wallet", "listlockunspent", &listlockunspent, false, false, true}, diff --git a/src/rpc/server.h b/src/rpc/server.h index 3ae3a9bcff20..f13bedc035c6 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -242,6 +242,7 @@ extern UniValue listreceivedbyaccount(const UniValue& params, bool fHelp); extern UniValue listtransactions(const UniValue& params, bool fHelp); extern UniValue listaddressgroupings(const UniValue& params, bool fHelp); extern UniValue listaccounts(const UniValue& params, bool fHelp); +extern UniValue listdelegators(const UniValue& params, bool fHelp); extern UniValue listsinceblock(const UniValue& params, bool fHelp); extern UniValue gettransaction(const UniValue& params, bool fHelp); extern UniValue abandontransaction(const UniValue& params, bool fHelp); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index b15bf565cbe6..a62715302583 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -205,6 +205,39 @@ UniValue delegatorremove(const UniValue& params, bool fHelp) return pwalletMain->DelAddressBook(keyID); } +UniValue listdelegators(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw std::runtime_error( + "listdelegators \"addr\"\n" + "\nShows the list of allowed delegator addresses for cold staking.\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"label\": \"yyy\", (string) account label\n" + " \"address\": \"xxx\", (string) PIVX address string\n" + " }\n" + " ...\n" + "]\n" + + "\nExamples:\n" + + HelpExampleCli("listdelegators" , "") + + HelpExampleRpc("listdelegators", "")); + + UniValue ret(UniValue::VARR); + LOCK(pwalletMain->cs_wallet); + for (const auto& addr : pwalletMain->mapAddressBook) { + if (addr.second.purpose != "delegator") continue; + UniValue entry(UniValue::VOBJ); + entry.push_back(Pair("label", addr.second.name)); + entry.push_back(Pair("address", CBitcoinAddress(addr.first).ToString())); + ret.push_back(entry); + } + + return ret; +} + CBitcoinAddress GetAccountAddress(std::string strAccount, bool bForceNew = false) { CWalletDB walletdb(pwalletMain->strWalletFile); From 16fa605d1b8fb261b9a4d6efad0930b457842d4d Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 14 Oct 2019 18:00:59 +0200 Subject: [PATCH 49/59] [RPC] define liststakingaddresses --- src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + src/wallet/rpcwallet.cpp | 53 ++++++++++++++++++++++++++++++++-------- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 001c627ecd21..1de4e30755c0 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -428,6 +428,7 @@ static const CRPCCommand vRPCCommands[] = {"wallet", "keypoolrefill", &keypoolrefill, true, false, true}, {"wallet", "listaccounts", &listaccounts, false, false, true}, {"wallet", "listdelegators", &listdelegators, false, false, true}, + {"wallet", "liststakingaddresses", &liststakingaddresses, false, false, true}, {"wallet", "listaddressgroupings", &listaddressgroupings, false, false, true}, {"wallet", "listcoldutxos", &listcoldutxos, false, false, true}, {"wallet", "listlockunspent", &listlockunspent, false, false, true}, diff --git a/src/rpc/server.h b/src/rpc/server.h index f13bedc035c6..6fc4a95da78f 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -243,6 +243,7 @@ extern UniValue listtransactions(const UniValue& params, bool fHelp); extern UniValue listaddressgroupings(const UniValue& params, bool fHelp); extern UniValue listaccounts(const UniValue& params, bool fHelp); extern UniValue listdelegators(const UniValue& params, bool fHelp); +extern UniValue liststakingaddresses(const UniValue& params, bool fHelp); extern UniValue listsinceblock(const UniValue& params, bool fHelp); extern UniValue gettransaction(const UniValue& params, bool fHelp); extern UniValue abandontransaction(const UniValue& params, bool fHelp); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a62715302583..b2d523e59554 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -205,6 +205,26 @@ UniValue delegatorremove(const UniValue& params, bool fHelp) return pwalletMain->DelAddressBook(keyID); } +UniValue ListaddressesForPurpose(const std::string strPurpose) +{ + const CChainParams::Base58Type addrType = + strPurpose == "coldstaking" ? + CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS; + UniValue ret(UniValue::VARR); + { + LOCK(pwalletMain->cs_wallet); + for (const auto& addr : pwalletMain->mapAddressBook) { + if (addr.second.purpose != strPurpose) continue; + UniValue entry(UniValue::VOBJ); + entry.push_back(Pair("label", addr.second.name)); + entry.push_back(Pair("address", CBitcoinAddress(addr.first, addrType).ToString())); + ret.push_back(entry); + } + } + + return ret; +} + UniValue listdelegators(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 0) @@ -225,17 +245,30 @@ UniValue listdelegators(const UniValue& params, bool fHelp) HelpExampleCli("listdelegators" , "") + HelpExampleRpc("listdelegators", "")); - UniValue ret(UniValue::VARR); - LOCK(pwalletMain->cs_wallet); - for (const auto& addr : pwalletMain->mapAddressBook) { - if (addr.second.purpose != "delegator") continue; - UniValue entry(UniValue::VOBJ); - entry.push_back(Pair("label", addr.second.name)); - entry.push_back(Pair("address", CBitcoinAddress(addr.first).ToString())); - ret.push_back(entry); - } + return ListaddressesForPurpose("delegator"); +} - return ret; +UniValue liststakingaddresses(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw std::runtime_error( + "liststakingaddresses \"addr\"\n" + "\nShows the list of staking addresses for this wallet.\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"label\": \"yyy\", (string) account label\n" + " \"address\": \"xxx\", (string) PIVX address string\n" + " }\n" + " ...\n" + "]\n" + + "\nExamples:\n" + + HelpExampleCli("liststakingaddresses" , "") + + HelpExampleRpc("liststakingaddresses", "")); + + return ListaddressesForPurpose("coldstaking"); } CBitcoinAddress GetAccountAddress(std::string strAccount, bool bForceNew = false) From b33c6bcb4a67fc30729dd9a7836129d3d163a9ab Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 14 Oct 2019 18:46:36 +0200 Subject: [PATCH 50/59] [Wallet] define GetColdStakingDebit and GetStakeDelegationDebit --- src/wallet/wallet.cpp | 62 ++++++++++++++++++++++++++++--------------- src/wallet/wallet.h | 7 +++-- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7a2bccb3ba21..41e011048944 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1130,6 +1130,42 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const return debit; } +CAmount CWalletTx::GetColdStakingDebit(bool fUseCache) const +{ + if (pwallet == 0) + return 0; + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + if (fUseCache && fColdDebitCached) + return nColdDebitCached; + + CAmount nDebit = GetDebit(ISMINE_COLD); + nColdDebitCached = nDebit; + fColdDebitCached = true; + return nDebit; +} + +CAmount CWalletTx::GetStakeDelegationDebit(bool fUseCache) const +{ + if (pwallet == 0) + return 0; + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + if (fUseCache && fDelegatedDebitCached) + return nDelegatedDebitCached; + + CAmount nDebit = GetDebit(ISMINE_SPENDABLE_DELEGATED); + nDelegatedDebitCached = nDebit; + fDelegatedDebitCached = true; + return nDebit; +} + CAmount CWalletTx::GetCredit(const isminefilter& filter) const { // Must wait until coinbase is safely deep enough in the chain before valuing it @@ -1191,22 +1227,6 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const return 0; } -// Helper function for GetAvailableCredit / GetColdStakingCredit / GetStakeDelegationCredit -CAmount CWalletTx::CreditFor(const isminetype& minetype) const -{ - const uint256 hashTx = GetHash(); - CAmount nCredit = 0; - for (unsigned int i = 0; i < vout.size(); i++) { - if (!pwallet->IsSpent(hashTx, i)) { - const CTxOut& txout = vout[i]; - nCredit += pwallet->GetCredit(txout, minetype); - if (!MoneyRange(nCredit)) - throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range"); - } - } - return nCredit; -} - CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const { if (pwallet == 0) @@ -1219,7 +1239,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const if (fUseCache && fAvailableCreditCached) return nAvailableCreditCached; - CAmount nCredit = CreditFor(ISMINE_SPENDABLE); + CAmount nCredit = GetCredit(ISMINE_SPENDABLE); nAvailableCreditCached = nCredit; fAvailableCreditCached = true; return nCredit; @@ -1237,7 +1257,7 @@ CAmount CWalletTx::GetColdStakingCredit(bool fUseCache) const if (fUseCache && fColdCreditCached) return nColdCreditCached; - CAmount nCredit = CreditFor(ISMINE_COLD); + CAmount nCredit = GetCredit(ISMINE_COLD); nColdCreditCached = nCredit; fColdCreditCached = true; return nCredit; @@ -1255,7 +1275,7 @@ CAmount CWalletTx::GetStakeDelegationCredit(bool fUseCache) const if (fUseCache && fDelegatedCreditCached) return nDelegatedCreditCached; - CAmount nCredit = CreditFor(ISMINE_SPENDABLE_DELEGATED); + CAmount nCredit = GetCredit(ISMINE_SPENDABLE_DELEGATED); nDelegatedCreditCached = nCredit; fDelegatedCreditCached = true; return nCredit; @@ -5618,9 +5638,9 @@ CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) c CAmount nCredit = 0; for (const CTxOut& txout : tx.vout) { nCredit += GetCredit(txout, filter); - if (!MoneyRange(nCredit)) - throw std::runtime_error("CWallet::GetCredit() : value out of range"); } + if (!MoneyRange(nCredit)) + throw std::runtime_error("CWallet::GetCredit() : value out of range"); return nCredit; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a339bdd39317..f34acdad83a8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -814,9 +814,11 @@ class CWalletTx : public CMerkleTx CAmount GetLockedWatchOnlyCredit() const; CAmount GetChange() const; - // Cold staking contracts credit + // Cold staking contracts credit/debit CAmount GetColdStakingCredit(bool fUseCache = true) const; + CAmount GetColdStakingDebit(bool fUseCache = true) const; CAmount GetStakeDelegationCredit(bool fUseCache = true) const; + CAmount GetStakeDelegationDebit(bool fUseCache = true) const; void GetAmounts(std::list& listReceived, std::list& listSent, @@ -839,9 +841,6 @@ class CWalletTx : public CMerkleTx int GetRequestCount() const; void RelayWalletTransaction(std::string strCommand = "tx"); std::set GetConflicts() const; - -protected: - CAmount CreditFor(const isminetype& minetype) const; }; From 436f3830a1ed417f325dbd55781fe714c6227646 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Jul 2019 09:03:15 +0200 Subject: [PATCH 51/59] [Wallet] Add fUnspent flag to GetCredit --- src/wallet/wallet.cpp | 23 ++++++++++++----------- src/wallet/wallet.h | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 41e011048944..e7d37fc3d174 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1166,7 +1166,7 @@ CAmount CWalletTx::GetStakeDelegationDebit(bool fUseCache) const return nDebit; } -CAmount CWalletTx::GetCredit(const isminefilter& filter) const +CAmount CWalletTx::GetCredit(const isminefilter& filter, const bool fUnspent) const { // Must wait until coinbase is safely deep enough in the chain before valuing it if (IsCoinBase() && GetBlocksToMaturity() > 0) @@ -1178,7 +1178,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const if (fCreditCached) credit += nCreditCached; else { - nCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE); + nCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE, fUnspent); fCreditCached = true; credit += nCreditCached; } @@ -1187,7 +1187,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const if (fWatchCreditCached) credit += nWatchCreditCached; else { - nWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY); + nWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY, fUnspent); fWatchCreditCached = true; credit += nWatchCreditCached; } @@ -1196,7 +1196,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const if (fColdCreditCached) credit += nColdCreditCached; else { - nColdCreditCached = pwallet->GetCredit(*this, ISMINE_COLD); + nColdCreditCached = pwallet->GetCredit(*this, ISMINE_COLD, fUnspent); fColdCreditCached = true; credit += nColdCreditCached; } @@ -1205,7 +1205,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const if (fDelegatedCreditCached) credit += nDelegatedCreditCached; else { - nDelegatedCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE_DELEGATED); + nDelegatedCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE_DELEGATED, fUnspent); fDelegatedCreditCached = true; credit += nDelegatedCreditCached; } @@ -1239,7 +1239,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const if (fUseCache && fAvailableCreditCached) return nAvailableCreditCached; - CAmount nCredit = GetCredit(ISMINE_SPENDABLE); + CAmount nCredit = GetCredit(ISMINE_SPENDABLE, true); nAvailableCreditCached = nCredit; fAvailableCreditCached = true; return nCredit; @@ -1257,7 +1257,7 @@ CAmount CWalletTx::GetColdStakingCredit(bool fUseCache) const if (fUseCache && fColdCreditCached) return nColdCreditCached; - CAmount nCredit = GetCredit(ISMINE_COLD); + CAmount nCredit = GetCredit(ISMINE_COLD, true); nColdCreditCached = nCredit; fColdCreditCached = true; return nCredit; @@ -1275,7 +1275,7 @@ CAmount CWalletTx::GetStakeDelegationCredit(bool fUseCache) const if (fUseCache && fDelegatedCreditCached) return nDelegatedCreditCached; - CAmount nCredit = GetCredit(ISMINE_SPENDABLE_DELEGATED); + CAmount nCredit = GetCredit(ISMINE_SPENDABLE_DELEGATED, true); nDelegatedCreditCached = nCredit; fDelegatedCreditCached = true; return nCredit; @@ -5633,11 +5633,12 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co return nDebit; } -CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const +CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter, const bool fUnspent) const { CAmount nCredit = 0; - for (const CTxOut& txout : tx.vout) { - nCredit += GetCredit(txout, filter); + for (int i = 0; i < tx.vout.size(); i++) { + if (fUnspent && IsSpent(tx.GetHash(), i)) continue; + nCredit += GetCredit(tx.vout[i], filter); } if (!MoneyRange(nCredit)) throw std::runtime_error("CWallet::GetCredit() : value out of range"); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f34acdad83a8..5543ae0cc81c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -501,7 +501,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; - CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const; + CAmount GetCredit(const CTransaction& tx, const isminefilter& filter, const bool fUnspent = false) const; CAmount GetChange(const CTransaction& tx) const; void SetBestChain(const CBlockLocator& loc); @@ -802,7 +802,7 @@ class CWalletTx : public CMerkleTx //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const; - CAmount GetCredit(const isminefilter& filter) const; + CAmount GetCredit(const isminefilter& filter, const bool fUnspent = false) const; CAmount GetImmatureCredit(bool fUseCache = true) const; CAmount GetAvailableCredit(bool fUseCache = true) const; // Return sum of unlocked coins From bf27e8492ee7f04d5e12486b7d058d988bca0a18 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 2 Oct 2019 20:57:10 -0300 Subject: [PATCH 52/59] [Wallet] Stop duplicating cached amount update. Single method doing it instead of 3 doing exactly the same. --- src/wallet/wallet.cpp | 84 +++++++++++-------------------------------- src/wallet/wallet.h | 3 ++ 2 files changed, 23 insertions(+), 64 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e7d37fc3d174..a95a00ee92e9 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1132,38 +1132,12 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const CAmount CWalletTx::GetColdStakingDebit(bool fUseCache) const { - if (pwallet == 0) - return 0; - - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) - return 0; - - if (fUseCache && fColdDebitCached) - return nColdDebitCached; - - CAmount nDebit = GetDebit(ISMINE_COLD); - nColdDebitCached = nDebit; - fColdDebitCached = true; - return nDebit; + return UpdateAmount(nColdDebitCached, fColdDebitCached, fUseCache, ISMINE_SPENDABLE_DELEGATED, false); } CAmount CWalletTx::GetStakeDelegationDebit(bool fUseCache) const { - if (pwallet == 0) - return 0; - - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) - return 0; - - if (fUseCache && fDelegatedDebitCached) - return nDelegatedDebitCached; - - CAmount nDebit = GetDebit(ISMINE_SPENDABLE_DELEGATED); - nDelegatedDebitCached = nDebit; - fDelegatedDebitCached = true; - return nDebit; + return UpdateAmount(nDelegatedDebitCached, fDelegatedDebitCached, fUseCache, ISMINE_SPENDABLE_DELEGATED, false); } CAmount CWalletTx::GetCredit(const isminefilter& filter, const bool fUnspent) const @@ -1229,41 +1203,19 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const { - if (pwallet == 0) - return 0; - - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) - return 0; - - if (fUseCache && fAvailableCreditCached) - return nAvailableCreditCached; - - CAmount nCredit = GetCredit(ISMINE_SPENDABLE, true); - nAvailableCreditCached = nCredit; - fAvailableCreditCached = true; - return nCredit; -} + return UpdateAmount(nAvailableCreditCached, fAvailableCreditCached, fUseCache, ISMINE_SPENDABLE);} CAmount CWalletTx::GetColdStakingCredit(bool fUseCache) const { - if (pwallet == 0) - return 0; - - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) - return 0; - - if (fUseCache && fColdCreditCached) - return nColdCreditCached; - - CAmount nCredit = GetCredit(ISMINE_COLD, true); - nColdCreditCached = nCredit; - fColdCreditCached = true; - return nCredit; + return UpdateAmount(nColdCreditCached, fColdCreditCached, fUseCache, ISMINE_COLD); } CAmount CWalletTx::GetStakeDelegationCredit(bool fUseCache) const +{ + return UpdateAmount(nDelegatedCreditCached, fDelegatedCreditCached, fUseCache, ISMINE_SPENDABLE_DELEGATED); +} + +CAmount CWalletTx::UpdateAmount(CAmount& amountToUpdate, bool& cacheFlagToUpdate, bool fUseCache, isminetype mimeType, bool fCredit) const { if (pwallet == 0) return 0; @@ -1272,13 +1224,17 @@ CAmount CWalletTx::GetStakeDelegationCredit(bool fUseCache) const if (IsCoinBase() && GetBlocksToMaturity() > 0) return 0; - if (fUseCache && fDelegatedCreditCached) - return nDelegatedCreditCached; - - CAmount nCredit = GetCredit(ISMINE_SPENDABLE_DELEGATED, true); - nDelegatedCreditCached = nCredit; - fDelegatedCreditCached = true; - return nCredit; + if (fUseCache && cacheFlagToUpdate) + return amountToUpdate; + + CAmount amount; + if (!fCredit) + amount = GetDebit(mimeType); + else + amount = GetCredit(mimeType, true); + amountToUpdate = amount; + cacheFlagToUpdate = true; + return amount; } // Return sum of unlocked coins diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 5543ae0cc81c..770a25803ac4 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -820,6 +820,9 @@ class CWalletTx : public CMerkleTx CAmount GetStakeDelegationCredit(bool fUseCache = true) const; CAmount GetStakeDelegationDebit(bool fUseCache = true) const; + // Helper method to update the amount and cacheFlag. + CAmount UpdateAmount(CAmount& amountToUpdate, bool& cacheFlagToUpdate, bool fUseCache, isminetype mimeType, bool fCredit = true) const; + void GetAmounts(std::list& listReceived, std::list& listSent, CAmount& nFee, From 80678b75a76def81669188a302965358524eeac6 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 14 Oct 2019 22:23:55 -0300 Subject: [PATCH 53/59] [Wallet][Refactor] Inline getDebit/getCredit if else in updateAmount method. [Wallet][Refactor] Inline getDebit/getCredit if else in updateAmount method. --- src/wallet/wallet.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a95a00ee92e9..ddd29158aac8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1227,14 +1227,9 @@ CAmount CWalletTx::UpdateAmount(CAmount& amountToUpdate, bool& cacheFlagToUpdate if (fUseCache && cacheFlagToUpdate) return amountToUpdate; - CAmount amount; - if (!fCredit) - amount = GetDebit(mimeType); - else - amount = GetCredit(mimeType, true); - amountToUpdate = amount; + amountToUpdate = (fCredit) ? GetCredit(mimeType, true) : GetDebit(mimeType); cacheFlagToUpdate = true; - return amount; + return amountToUpdate; } // Return sum of unlocked coins From 6cadfea26d92726a7e1e9c4d9bdb808d6309b7c4 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 16 Oct 2019 03:56:16 +0200 Subject: [PATCH 54/59] [Wallet] Add fStakeDelegation voided in-memory flag --- src/wallet/wallet.cpp | 3 +++ src/wallet/wallet.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ddd29158aac8..bc19ba84f625 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2495,6 +2495,8 @@ bool CWallet::CreateTransaction(const std::vector >& for (PAIRTYPE(const CWalletTx*, unsigned int) pcoin : setCoins) { + if(pcoin.first->vout[pcoin.second].scriptPubKey.IsPayToColdStaking()) + wtxNew.fStakeDelegationVoided = true; CAmount nCredit = pcoin.first->vout[pcoin.second].nValue; //The coin age after the next block (depth+1) is used instead of the current, //reflecting an assumption the user would accept a bit more delay for @@ -5676,6 +5678,7 @@ void CWalletTx::Init(const CWallet* pwalletIn) fColdCreditCached = false; fDelegatedDebitCached = false; fDelegatedCreditCached = false; + fStakeDelegationVoided = false; nDebitCached = 0; nCreditCached = 0; nImmatureCreditCached = 0; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 770a25803ac4..f62795f506de 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -726,6 +726,7 @@ class CWalletTx : public CMerkleTx mutable bool fColdCreditCached; mutable bool fDelegatedDebitCached; mutable bool fDelegatedCreditCached; + mutable bool fStakeDelegationVoided; mutable CAmount nDebitCached; mutable CAmount nCreditCached; mutable CAmount nImmatureCreditCached; From 620bc1982c2e0b99887d8869072903027a9f7ef2 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 19 Oct 2019 01:08:32 +0200 Subject: [PATCH 55/59] [Bug] Fix isMine type in GetColdStakingDebit --- src/wallet/wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index bc19ba84f625..3f199c4a668a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1132,7 +1132,7 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const CAmount CWalletTx::GetColdStakingDebit(bool fUseCache) const { - return UpdateAmount(nColdDebitCached, fColdDebitCached, fUseCache, ISMINE_SPENDABLE_DELEGATED, false); + return UpdateAmount(nColdDebitCached, fColdDebitCached, fUseCache, ISMINE_COLD, false); } CAmount CWalletTx::GetStakeDelegationDebit(bool fUseCache) const From 244fecafa501c863bbaad3e07a4d2e45b825ba68 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 16 Oct 2019 01:43:02 +0200 Subject: [PATCH 56/59] [Wallet] Fix spending P2CS delegations with coin control --- src/wallet/wallet.cpp | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3f199c4a668a..5b2f705b01f0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1921,6 +1921,10 @@ void CWallet::AvailableCoins( bool fIncludeDelegated) const { vCoins.clear(); + const bool fCoinsSelected = (coinControl != nullptr) && coinControl->HasSelected(); + // include delegated coins when coinControl is active + if (!fIncludeDelegated && fCoinsSelected) + fIncludeDelegated = true; { LOCK2(cs_main, cs_wallet); @@ -1975,22 +1979,6 @@ void CWallet::AvailableCoins( if (mine == ISMINE_NO) continue; - // skip cold coins - if (mine == ISMINE_COLD) { - if (!fIncludeColdStaking) - continue; - if (!HasDelegator(pcoin->vout[i])) - continue; - } - - // skip delegated coins - if (!fIncludeDelegated && mine == ISMINE_SPENDABLE_DELEGATED) - continue; - - // skip auto-delegated coins - if (!fIncludeColdStaking && !fIncludeDelegated && mine == ISMINE_SPENDABLE_STAKEABLE) - continue; - if ((mine == ISMINE_MULTISIG || mine == ISMINE_SPENDABLE) && nWatchonlyConfig == 2) continue; @@ -2001,9 +1989,17 @@ void CWallet::AvailableCoins( continue; if (pcoin->vout[i].nValue <= 0 && !fIncludeZeroValue) continue; - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected((*it).first, i)) + if (fCoinsSelected && !coinControl->fAllowOtherInputs && !coinControl->IsSelected((*it).first, i)) continue; + // --Skip P2CS outputs + // skip cold coins + if (mine == ISMINE_COLD && (!fIncludeColdStaking || !HasDelegator(pcoin->vout[i]))) continue; + // skip delegated coins + if (mine == ISMINE_SPENDABLE_DELEGATED && !fIncludeDelegated) continue; + // skip auto-delegated coins + if (mine == ISMINE_SPENDABLE_STAKEABLE && !fIncludeColdStaking && !fIncludeDelegated) continue; + bool fIsValid = ( ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || ((mine & (ISMINE_MULTISIG | (fIncludeColdStaking ? ISMINE_COLD : ISMINE_NO) | From 3bfc380cbe51d0ad0084991143788023239f9682 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 19 Oct 2019 16:03:16 +0200 Subject: [PATCH 57/59] [Tests] Speed up feature_coldStaking + check 'listcoldutxos' --- src/wallet/rpcwallet.cpp | 6 +- test/functional/feature_coldStaking.py | 91 +++++++++++--------------- test/functional/test_runner.py | 2 +- 3 files changed, 43 insertions(+), 56 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index b2d523e59554..e08606c08c08 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1707,9 +1707,11 @@ UniValue listcoldutxos(const UniValue& params, bool fHelp) if (!CheckFinalTx(*pcoin) || !pcoin->IsTrusted()) continue; + // if this tx has no unspent P2CS outputs for us, skip it + if(pcoin->GetColdStakingCredit() == 0 && pcoin->GetStakeDelegationCredit() == 0) + continue; + for (unsigned int i = 0; i < pcoin->vout.size(); i++) { - if(pwalletMain->IsSpent(wtxid, i)) - continue; const CTxOut& out = pcoin->vout[i]; isminetype mine = pwalletMain->IsMine(out); if (!bool(mine & ISMINE_COLD) && !bool(mine & ISMINE_SPENDABLE_DELEGATED)) diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py index 527efadfe5c8..fa164a2a7344 100755 --- a/test/functional/feature_coldStaking.py +++ b/test/functional/feature_coldStaking.py @@ -18,7 +18,7 @@ from test_framework.script import CScript, OP_CHECKSIG from test_framework.test_framework import BitcoinTestFramework from test_framework.util import hash256, connect_nodes_bi, p2p_port, bytes_to_hex_str, \ - hex_str_to_bytes, assert_equal, assert_greater_than + hex_str_to_bytes, assert_equal, assert_greater_than, sync_chain # filter utxos based on first 5 bytes of scriptPubKey def getDelegatedUtxos(utxos): @@ -71,27 +71,22 @@ def run_test(self): # nodes[0] - coin-owner # nodes[1] - cold-staker - # 1) nodes[0] mines 20 blocks. nodes[2] mines all the others. + # 1) nodes[0] mines 20 blocks. nodes[2] mines 180 blocks. # ----------------------------------------------------------- print("*** 1 ***") self.log.info("Mining %d blocks..." % INITAL_MINED_BLOCKS) - for i in range(1, INITAL_MINED_BLOCKS+1): - if i % 10 == 0: - self.sync_all() - self.nodes[0].generate(1) - self.sync_all() - else: - self.generateBlock() - if i % 25 == 0: - self.log.info("%d Blocks mined." % i) - + self.nodes[0].generate(20) + sync_chain(self.nodes) + self.log.info("20 Blocks mined.") + self.generateBlock(180) + sync_chain(self.nodes) + self.log.info("200 Blocks mined.") # 2) nodes[0] generates a owner address # nodes[1] generates a cold-staking address. # --------------------------------------------- print("*** 2 ***") - self.sync_all() owner_address = self.nodes[0].getnewaddress() self.log.info("Owner Address: %s" % owner_address) staker_address = self.nodes[1].getnewstakingaddress() @@ -101,7 +96,6 @@ def run_test(self): # 3) Check enforcement. # --------------------- print("*** 3 ***") - self.sync_all() self.log.info("Creating a stake-delegation tx before cold staking enforcement...") try: self.nodes[0].delegatestake(staker_address, INPUT_VALUE, owner_address, False, False, True) @@ -111,11 +105,8 @@ def run_test(self): self.log.info("Good. Cold Staking NOT ACTIVE yet.") pass self.log.info("Mining 51 blocks to get to cold staking activation...") - for i in range(1, 52): - self.generateBlock() - self.sync_all() - if i % 10 == 0: - self.log.info("%d Blocks mined." % i) + self.generateBlock(51) + sync_chain(self.nodes) # 4) nodes[0] delegates a number of inputs for nodes[1] to stake em. @@ -147,7 +138,7 @@ def run_test(self): assert_equal(res["owner_address"], owner_address) assert_equal(res["staker_address"], staker_address) self.generateBlock() - self.sync_all() + sync_chain(self.nodes) self.log.info("%d Txes created." % NUM_OF_INPUTS) # check balances: self.expected_balance = NUM_OF_INPUTS * INPUT_VALUE @@ -159,37 +150,28 @@ def run_test(self): print("*** 5 ***") self.log.info("Spending back one of the delegated UTXOs...") delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) - assert_greater_than(len(delegated_utxos), 0) + assert_equal(20, len(delegated_utxos)) + assert_equal(len(delegated_utxos), len(self.nodes[0].listcoldutxos())) u = delegated_utxos[0] txhash = self.spendUTXOwithNode(u, 0) assert(txhash != None) self.log.info("Good. Owner was able to spend - tx: %s" % str(txhash)) self.generateBlock() - self.sync_all() + sync_chain(self.nodes) # check balances after spend. self.expected_balance -= float(u["amount"]) self.checkBalances() self.log.info("Balances check out after spend") + assert_equal(19, len(self.nodes[0].listcoldutxos())) # 6) check that the staker CANNOT use the coins to stake yet. # He needs to whitelist the owner first. # ----------------------------------------------------------- - self.log.info("Staking 15 blocks to mature P2CS stake delegations...") - for i in range(1, 16): - self.generateBlock() - self.sync_all() - if i % 5 == 0: - self.log.info("%d Blocks added." % i) print("*** 6 ***") self.log.info("Trying to generate a cold-stake block before whitelisting the owner...") - try: - self.nodes[1].generate(1) - assert(False) - except JSONRPCException as e: - assert ("Couldn't create new block" in str(e)) - self.log.info("Nice. Cold staker was NOT able to create the block yet.") - pass + assert_equal(self.nodes[1].getstakingstatus()["mintablecoins"], False) + self.log.info("Nice. Cold staker was NOT able to create the block yet.") self.log.info("Whitelisting the owner...") ret = self.nodes[1].delegatoradd(owner_address) assert(ret) @@ -211,19 +193,22 @@ def run_test(self): self.log.info("Good. Cold staker was NOT able to spend (failed OP_CHECKCOLDSTAKEVERIFY)") pass self.generateBlock() - self.sync_all() + sync_chain(self.nodes) + # 8) check that the staker can use the coins to stake a block with internal miner. # -------------------------------------------------------------------------------- print("*** 8 ***") + assert_equal(self.nodes[1].getstakingstatus()["mintablecoins"], True) self.log.info("Generating one valid cold-stake block...") self.nodes[1].generate(1) self.log.info("New block created by cold-staking. Trying to submit...") newblockhash = self.nodes[1].getbestblockhash() self.log.info("Block %s submitted" % newblockhash) # Verify that nodes[0] accepts it - self.sync_all() + sync_chain(self.nodes) + assert_equal(self.nodes[0].getblockcount(), self.nodes[1].getblockcount()) assert_equal(newblockhash, self.nodes[0].getbestblockhash()) self.log.info("Great. Cold-staked block was accepted!") # check balances after staked block. @@ -249,7 +234,8 @@ def run_test(self): self.log.info("Block %s submitted." % new_block.hash) assert(ret is None) # Verify that nodes[0] accepts it - self.sync_all() + sync_chain(self.nodes) + assert_equal(self.nodes[0].getblockcount(), self.nodes[1].getblockcount()) assert_equal(new_block.hash, self.nodes[0].getbestblockhash()) self.log.info("Great. Cold-staked block was accepted!") # check balances after staked block. @@ -275,13 +261,15 @@ def run_test(self): self.log.info("Block %s submitted." % new_block.hash) assert("rejected" in ret) # Verify that nodes[0] rejects it - self.sync_all() + sync_chain(self.nodes) try: self.nodes[0].getblock(new_block.hash) except JSONRPCException as e: assert("Block not found" in str(e)) self.log.info("Great. Malicious cold-staked block was NOT accepted!") pass + self.checkBalances() + self.log.info("Balances check out after (non) staked block") # 11) neither adding different outputs to the coinstake. @@ -301,20 +289,21 @@ def run_test(self): self.log.info("Block %s submitted." % new_block.hash) assert("rejected" in ret) # Verify that nodes[0] rejects it - self.sync_all() + sync_chain(self.nodes) try: self.nodes[0].getblock(new_block.hash) except JSONRPCException as e: assert("Block not found" in str(e)) self.log.info("Great. Malicious cold-staked block was NOT accepted!") pass - + self.checkBalances() + self.log.info("Balances check out after (non) staked block") # 12) Now node[0] gets mad and spends all the delegated coins, voiding the P2CS contracts. # ---------------------------------------------------------------------------------------- self.log.info("Let's void the contracts.") self.generateBlock() - self.sync_all() + sync_chain(self.nodes) print("*** 12 ***") self.log.info("Cancel the stake delegation spending the cold stakes...") delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) @@ -322,31 +311,27 @@ def run_test(self): assert(txhash != None) self.log.info("Good. Owner was able to void the stake delegations - tx: %s" % str(txhash)) self.generateBlock() - self.sync_all() + sync_chain(self.nodes) # check balances after big spend. self.expected_balance = 2 * (INPUT_VALUE + 250) self.checkBalances() self.log.info("Balances check out after the delegations have been voided.") + assert_equal(2, len(self.nodes[0].listcoldutxos())) # 13) check that coinstaker is empty and can no longer stake. # ----------------------------------------------------------- print("*** 13 ***") self.log.info("Trying to generate one cold-stake block again...") - try: - self.nodes[1].generate(1) - assert(False) - except JSONRPCException as e: - assert ("Couldn't create new block" in str(e)) - self.log.info("Cigar. Cold staker was NOT able to create any more blocks.\n") - pass + assert_equal(self.nodes[1].getstakingstatus()["mintablecoins"], False) + self.log.info("Cigar. Cold staker was NOT able to create any more blocks.\n") - def generateBlock(self): + def generateBlock(self, n=1): fStaked = False while (not fStaked): try: - self.nodes[2].generate(1) + self.nodes[2].generate(n) fStaked = True except JSONRPCException as e: if ("Couldn't create new block" in str(e)): @@ -398,7 +383,7 @@ def spendUTXOsWithNode(self, utxos, node_n): - def get_prevouts(self, coins, node_n, confs=15): + def get_prevouts(self, coins, node_n, confs=1): api = self.nodes[node_n] prevouts = {} for utxo in coins: diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index bddde627360d..39de1dd60ee6 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -55,13 +55,13 @@ BASE_SCRIPTS= [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel - 'feature_coldStaking.py', 'wallet_basic.py', 'wallet_backup.py', # vv Tests less than 5m vv 'wallet_abandonconflict.py', 'wallet_reorg-stake.py', + 'feature_coldStaking.py', 'rpc_rawtransaction.py', 'wallet_zapwallettxes.py', 'wallet_keypool_topup.py', From 255d757e3bb07f04fbd861b5fc82a72e75a72641 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 21 Oct 2019 04:05:11 +0200 Subject: [PATCH 58/59] [Core] Add CheckColdStakeFreeOutput function to verify that the last output of cold stakes has the correct amount regardless of the payee (which is checked in IsBlockPayeeValid). This prevents possible abuse when SPORK_8 and/or SPORK_9-SPORK_13 are disabled). Also adjust/cleanup feature_coldStaking functional test --- src/main.cpp | 43 +++++++++++---- test/functional/feature_coldStaking.py | 72 +++++++++++--------------- 2 files changed, 65 insertions(+), 50 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 1b4af4c268d5..d997261b227f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4379,6 +4379,32 @@ bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, bool f return true; } +bool CheckColdStakeFreeOutput(const CTransaction& tx, const int nHeight) +{ + if (!tx.HasP2CSOutputs()) + return true; + + const unsigned int outs = tx.vout.size(); + const CTxOut& lastOut = tx.vout[outs-1]; + if (outs >=3 && lastOut.scriptPubKey != tx.vout[outs-2].scriptPubKey) { + // last output can either be a mn reward or a budget payment + // cold staking is active much after nPublicZCSpends so GetMasternodePayment is always 3 PIV. + // TODO: double check this if/when MN rewards change + if (lastOut.nValue == 3 * COIN) + return true; + + if (budget.IsBudgetPaymentBlock(nHeight) & + sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && + sporkManager.IsSporkActive(SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT)) + return true; + + return error("%s: Wrong cold staking outputs: vout[%d].scriptPubKey (%s) != vout[%d].scriptPubKey (%s) - value: %s", + __func__, outs-1, HexStr(lastOut.scriptPubKey), outs-2, HexStr(tx.vout[outs-2].scriptPubKey), FormatMoney(lastOut.nValue).c_str()); + } + + return true; +} + bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bool fCheckMerkleRoot, bool fCheckSig) { // These are checks that are independent of context. @@ -4446,15 +4472,6 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo for (unsigned int i = 2; i < block.vtx.size(); i++) if (block.vtx[i].IsCoinStake()) return state.DoS(100, error("%s : more than one coinstake", __func__)); - - // if MN payment is disabled. Check that the last output is not abused by cold staker - if (!IsInitialBlockDownload() && block.vtx[1].HasP2CSOutputs() && !sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) { - const int outputs = block.vtx[1].vout.size(); - if (outputs >=3 && block.vtx[1].vout[outputs-1].scriptPubKey != block.vtx[1].vout[outputs-2].scriptPubKey) - return state.DoS(100, error("%s: Wrong cold staking outputs when masternode payment enforcement is disabled: " - "script[-1] (%s) != script[-2] (%s)", __func__, - HexStr(block.vtx[1].vout[outputs-1].scriptPubKey), HexStr(block.vtx[1].vout[outputs-2].scriptPubKey))); - } } // ----------- swiftTX transaction scanning ----------- @@ -4498,6 +4515,14 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo // The case also exists that the sending peer could not have enough data to see // that this block is invalid, so don't issue an outright ban. if (nHeight != 0 && !IsInitialBlockDownload()) { + // Last output of Cold-Stake is not abused + if (IsPoS && !CheckColdStakeFreeOutput(block.vtx[1], nHeight)) { + mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime())); + return state.DoS(0, error("%s : Cold stake outputs not valid", __func__), + REJECT_INVALID, "bad-p2cs-outs"); + } + + // Valid masternode/budget payment if (!IsBlockPayeeValid(block, nHeight)) { mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime())); return state.DoS(0, error("%s : Couldn't find masternode/budget payment", __func__), diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py index fa164a2a7344..196bd3b57eee 100755 --- a/test/functional/feature_coldStaking.py +++ b/test/functional/feature_coldStaking.py @@ -18,7 +18,7 @@ from test_framework.script import CScript, OP_CHECKSIG from test_framework.test_framework import BitcoinTestFramework from test_framework.util import hash256, connect_nodes_bi, p2p_port, bytes_to_hex_str, \ - hex_str_to_bytes, assert_equal, assert_greater_than, sync_chain + hex_str_to_bytes, assert_equal, assert_greater_than, sync_chain, assert_raises_rpc_error # filter utxos based on first 5 bytes of scriptPubKey def getDelegatedUtxos(utxos): @@ -97,13 +97,9 @@ def run_test(self): # --------------------- print("*** 3 ***") self.log.info("Creating a stake-delegation tx before cold staking enforcement...") - try: - self.nodes[0].delegatestake(staker_address, INPUT_VALUE, owner_address, False, False, True) - assert(False) - except JSONRPCException as e: - assert("The transaction was rejected!" in str(e)) - self.log.info("Good. Cold Staking NOT ACTIVE yet.") - pass + assert_raises_rpc_error(-4, "The transaction was rejected!", + self.nodes[0].delegatestake, staker_address, INPUT_VALUE, owner_address, False, False, True) + self.log.info("Good. Cold Staking NOT ACTIVE yet.") self.log.info("Mining 51 blocks to get to cold staking activation...") self.generateBlock(51) sync_chain(self.nodes) @@ -113,24 +109,21 @@ def run_test(self): # ------------------------------------------------------------------ print("*** 4 ***") self.log.info("First check warning when using external addresses...") - try: - self.nodes[0].delegatestake(staker_address, INPUT_VALUE, "yCgCXC8N5VThhfiaVuKaNLkNnrWduzVnoT") - assert(False) - except JSONRPCException as e: - assert("Set 'fExternalOwner' argument to true, in order to force the stake delegation" in str(e)) - self.log.info("Good. Warning triggered.") + assert_raises_rpc_error(-5, "Only the owner of the key to owneraddress will be allowed to spend these coins", + self.nodes[0].delegatestake, staker_address, INPUT_VALUE, "yCgCXC8N5VThhfiaVuKaNLkNnrWduzVnoT") + self.log.info("Good. Warning triggered.") + self.log.info("Now force the use of external address creating (but not sending) the delegation...") res = self.nodes[0].rawdelegatestake(staker_address, INPUT_VALUE, "yCgCXC8N5VThhfiaVuKaNLkNnrWduzVnoT", True) assert(res is not None and res != "") self.log.info("Good. Warning NOT triggered.") + self.log.info("Now delegate with internal owner address..") self.log.info("Try first with a value (5) below the threshold") - try: - self.nodes[0].delegatestake(staker_address, 5, owner_address) - assert(False) - except JSONRPCException as e: - assert("Invalid amount" in str(e)) - self.log.info("Nice. it was not possible.") + assert_raises_rpc_error(-8, "Invalid amount", + self.nodes[0].delegatestake, staker_address, 5, owner_address) + self.log.info("Nice. it was not possible.") + self.log.info("Creating %d stake-delegation txes..." % NUM_OF_INPUTS) for i in range(NUM_OF_INPUTS): res = self.nodes[0].delegatestake(staker_address, INPUT_VALUE, owner_address) @@ -156,6 +149,7 @@ def run_test(self): txhash = self.spendUTXOwithNode(u, 0) assert(txhash != None) self.log.info("Good. Owner was able to spend - tx: %s" % str(txhash)) + self.generateBlock() sync_chain(self.nodes) # check balances after spend. @@ -172,6 +166,7 @@ def run_test(self): self.log.info("Trying to generate a cold-stake block before whitelisting the owner...") assert_equal(self.nodes[1].getstakingstatus()["mintablecoins"], False) self.log.info("Nice. Cold staker was NOT able to create the block yet.") + self.log.info("Whitelisting the owner...") ret = self.nodes[1].delegatoradd(owner_address) assert(ret) @@ -185,18 +180,13 @@ def run_test(self): delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) assert_greater_than(len(delegated_utxos), 0) u = delegated_utxos[0] - try: - self.spendUTXOwithNode(u, 1) - assert(False) - except JSONRPCException as e: - assert("Script failed an OP_CHECKCOLDSTAKEVERIFY operation" in str(e)) - self.log.info("Good. Cold staker was NOT able to spend (failed OP_CHECKCOLDSTAKEVERIFY)") - pass + assert_raises_rpc_error(-26, "mandatory-script-verify-flag-failed (Script failed an OP_CHECKCOLDSTAKEVERIFY operation", + self.spendUTXOwithNode, u, 1) + self.log.info("Good. Cold staker was NOT able to spend (failed OP_CHECKCOLDSTAKEVERIFY)") self.generateBlock() sync_chain(self.nodes) - # 8) check that the staker can use the coins to stake a block with internal miner. # -------------------------------------------------------------------------------- print("*** 8 ***") @@ -206,11 +196,13 @@ def run_test(self): self.log.info("New block created by cold-staking. Trying to submit...") newblockhash = self.nodes[1].getbestblockhash() self.log.info("Block %s submitted" % newblockhash) + # Verify that nodes[0] accepts it sync_chain(self.nodes) assert_equal(self.nodes[0].getblockcount(), self.nodes[1].getblockcount()) assert_equal(newblockhash, self.nodes[0].getbestblockhash()) self.log.info("Great. Cold-staked block was accepted!") + # check balances after staked block. self.expected_balance += 250 self.checkBalances() @@ -233,11 +225,13 @@ def run_test(self): ret = self.nodes[1].submitblock(bytes_to_hex_str(new_block.serialize())) self.log.info("Block %s submitted." % new_block.hash) assert(ret is None) + # Verify that nodes[0] accepts it sync_chain(self.nodes) assert_equal(self.nodes[0].getblockcount(), self.nodes[1].getblockcount()) assert_equal(new_block.hash, self.nodes[0].getbestblockhash()) self.log.info("Great. Cold-staked block was accepted!") + # check balances after staked block. self.expected_balance += 250 self.checkBalances() @@ -260,14 +254,11 @@ def run_test(self): ret = self.nodes[1].submitblock(bytes_to_hex_str(new_block.serialize())) self.log.info("Block %s submitted." % new_block.hash) assert("rejected" in ret) + # Verify that nodes[0] rejects it sync_chain(self.nodes) - try: - self.nodes[0].getblock(new_block.hash) - except JSONRPCException as e: - assert("Block not found" in str(e)) - self.log.info("Great. Malicious cold-staked block was NOT accepted!") - pass + assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblock, new_block.hash) + self.log.info("Great. Malicious cold-staked block was NOT accepted!") self.checkBalances() self.log.info("Balances check out after (non) staked block") @@ -287,18 +278,16 @@ def run_test(self): # Try to submit the block ret = self.nodes[1].submitblock(bytes_to_hex_str(new_block.serialize())) self.log.info("Block %s submitted." % new_block.hash) - assert("rejected" in ret) + assert_equal(ret, "bad-p2cs-outs") + # Verify that nodes[0] rejects it sync_chain(self.nodes) - try: - self.nodes[0].getblock(new_block.hash) - except JSONRPCException as e: - assert("Block not found" in str(e)) - self.log.info("Great. Malicious cold-staked block was NOT accepted!") - pass + assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblock, new_block.hash) + self.log.info("Great. Malicious cold-staked block was NOT accepted!") self.checkBalances() self.log.info("Balances check out after (non) staked block") + # 12) Now node[0] gets mad and spends all the delegated coins, voiding the P2CS contracts. # ---------------------------------------------------------------------------------------- self.log.info("Let's void the contracts.") @@ -312,6 +301,7 @@ def run_test(self): self.log.info("Good. Owner was able to void the stake delegations - tx: %s" % str(txhash)) self.generateBlock() sync_chain(self.nodes) + # check balances after big spend. self.expected_balance = 2 * (INPUT_VALUE + 250) self.checkBalances() From e498a06f8b95d08e421f2da474898c7f62b8b096 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 21 Oct 2019 05:55:03 +0200 Subject: [PATCH 59/59] [Core] Cold Staking: lower dust threshold to 1 PIV --- src/chainparams.cpp | 2 +- src/wallet/rpcwallet.cpp | 2 +- test/functional/feature_coldStaking.py | 10 +++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 7c278f8582e1..c14bd20d304f 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -171,7 +171,7 @@ class CMainParams : public CChainParams nFutureTimeDriftPoS = 180; nMasternodeCountDrift = 20; nMaxMoneyOut = 21000000 * COIN; - nMinColdStakingAmount = 10 * COIN; + nMinColdStakingAmount = 1 * COIN; /** Height or Time Based Activations **/ nLastPOWBlock = 259200; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e08606c08c08..a9cac75bd3ae 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -578,7 +578,7 @@ UniValue CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CR // Get Amount CAmount nValue = AmountFromValue(params[1]); - if (nValue <= Params().GetMinColdStakingAmount()) + if (nValue < Params().GetMinColdStakingAmount()) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid amount (%d). Min amount: %d", nValue, Params().GetMinColdStakingAmount())); diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py index 196bd3b57eee..a4d6fb91435b 100755 --- a/test/functional/feature_coldStaking.py +++ b/test/functional/feature_coldStaking.py @@ -119,12 +119,16 @@ def run_test(self): self.log.info("Good. Warning NOT triggered.") self.log.info("Now delegate with internal owner address..") - self.log.info("Try first with a value (5) below the threshold") + self.log.info("Try first with a value (0.99) below the threshold") assert_raises_rpc_error(-8, "Invalid amount", - self.nodes[0].delegatestake, staker_address, 5, owner_address) + self.nodes[0].delegatestake, staker_address, 0.99, owner_address) self.log.info("Nice. it was not possible.") + self.log.info("Then try (creating but not sending) with the threshold value (1.00)") + res = self.nodes[0].rawdelegatestake(staker_address, 1.00, owner_address) + assert(res is not None and res != "") + self.log.info("Good. Warning NOT triggered.") - self.log.info("Creating %d stake-delegation txes..." % NUM_OF_INPUTS) + self.log.info("Now creating %d real stake-delegation txes..." % NUM_OF_INPUTS) for i in range(NUM_OF_INPUTS): res = self.nodes[0].delegatestake(staker_address, INPUT_VALUE, owner_address) assert(res != None and res["txid"] != None and res["txid"] != "")