diff --git a/src/base58.cpp b/src/base58.cpp index da669fc73773..7c3f74a64ab6 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -224,20 +224,24 @@ class CBitcoinAddressVisitor : public boost::static_visitor { private: CBitcoinAddress* addr; + CChainParams::Base58Type type; public: - CBitcoinAddressVisitor(CBitcoinAddress* addrIn) : addr(addrIn) {} + CBitcoinAddressVisitor(CBitcoinAddress* addrIn, + const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS) : + addr(addrIn), + type(addrType){}; - bool operator()(const CKeyID& id) const { return addr->Set(id); } + 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) +bool CBitcoinAddress::Set(const CKeyID& id, const CChainParams::Base58Type addrType) { - SetData(Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS), &id, 20); + SetData(Params().Base58Prefix(addrType), &id, 20); return true; } @@ -247,9 +251,9 @@ bool CBitcoinAddress::Set(const CScriptID& id) return true; } -bool CBitcoinAddress::Set(const CTxDestination& dest) +bool CBitcoinAddress::Set(const CTxDestination& dest, const CChainParams::Base58Type addrType) { - return boost::apply_visitor(CBitcoinAddressVisitor(this), dest); + return boost::apply_visitor(CBitcoinAddressVisitor(this, addrType), dest); } bool CBitcoinAddress::IsValid() const @@ -261,7 +265,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 +276,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 +287,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); @@ -291,7 +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 +{ + bool fCorrectSize = vchData.size() == 20; + return fCorrectSize && vchVersion == Params().Base58Prefix(CChainParams::STAKING_ADDRESS); } void CBitcoinSecret::SetKey(const CKey& vchSecret) diff --git a/src/base58.h b/src/base58.h index 150f0d544402..1df40dd61930 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 CKeyID& id, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); bool Set(const CScriptID& id); - bool Set(const CTxDestination& dest); + 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) { Set(dest); } + 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); } CTxDestination Get() const; bool GetKeyID(CKeyID& keyID) const; bool IsScript() const; + bool IsStakingAddress() const; }; /** 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()); } } diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b850c634ad2e..c14bd20d304f 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); @@ -168,6 +171,7 @@ class CMainParams : public CChainParams nFutureTimeDriftPoS = 180; nMasternodeCountDrift = 20; nMaxMoneyOut = 21000000 * COIN; + nMinColdStakingAmount = 1 * COIN; /** Height or Time Based Activations **/ nLastPOWBlock = 259200; @@ -187,6 +191,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 +203,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. @@ -233,10 +241,11 @@ 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 >(); - // 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)); @@ -326,6 +335,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; @@ -337,6 +347,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; @@ -352,6 +365,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 >(); @@ -423,6 +437,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; @@ -433,6 +448,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 5a45e57c7335..1497d4319582 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 }; @@ -103,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; } @@ -142,6 +145,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; } @@ -227,6 +232,8 @@ class CChainParams int nPublicZCSpendsV4; int nBlockStakeModifierlV2; int nBlockEnforceNewMessageSignatures; + int nColdStakingStart; + CAmount nMinColdStakingAmount; // fake serial attack int nFakeSerialBlockheightEnd = 0; diff --git a/src/core_write.cpp b/src/core_write.cpp index cc91b232489e..30fc44fad6c5 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], 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()); + } out.pushKV("addresses", a); } 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/kernel.cpp b/src/kernel.cpp index 7cf67cd81bd3..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; @@ -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/src/keystore.cpp b/src/keystore.cpp index 4cd5e549094a..d3466c48d4cf 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -147,4 +147,4 @@ bool CBasicKeyStore::GetKey(const CKeyID& address, CKey& keyOut) const } } return false; -} \ No newline at end of file +} diff --git a/src/main.cpp b/src/main.cpp index 022c08d9e122..d997261b227f 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,7 @@ bool CheckZerocoinSpend(const CTransaction& tx, bool fVerifySignature, CValidati return fValidated; } -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()) @@ -1192,12 +1191,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.nValue < 0) return state.DoS(100, error("CheckTransaction() : txout.nValue negative"), REJECT_INVALID, "bad-txns-vout-negative"); @@ -1212,16 +1212,24 @@ 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"); + } } 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()) { @@ -1347,10 +1355,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()) @@ -4370,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. @@ -4480,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__), @@ -4493,6 +4536,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; @@ -4502,7 +4546,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..a7473c0476c7 100644 --- a/src/main.h +++ b/src/main.h @@ -355,7 +355,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsVi 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); 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; } 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/primitives/transaction.cpp b/src/primitives/transaction.cpp index 96fc5db02116..1eab8fc29a73 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -209,6 +209,40 @@ bool CTransaction::IsCoinStake() const return (vout.size() >= 2 && vout[0].IsEmpty()); } +bool CTransaction::CheckColdStake(const CScript& script) const +{ + + // tx is a coinstake tx + if (!IsCoinStake()) + return false; + + // all inputs have the same scriptSig + CScript firstScript = vin[0].scriptSig; + if (vin.size() > 1) { + for (unsigned int i=1; i 3) { + for (unsigned int i=2; i 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())); @@ -362,10 +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" - " \"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" @@ -396,7 +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))); + 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); 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/rpc/server.cpp b/src/rpc/server.cpp index 05cafe7c335a..1de4e30755c0 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}, @@ -408,7 +409,10 @@ 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", "getdelegatedbalance", &getdelegatedbalance, 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}, @@ -423,7 +427,10 @@ 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", "liststakingaddresses", &liststakingaddresses, 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}, @@ -433,6 +440,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}, @@ -444,6 +452,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 80d66aebd66d..6fc4a95da78f 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -212,7 +212,12 @@ 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 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); extern UniValue getrawchangeaddress(const UniValue& params, bool fHelp); extern UniValue setaccount(const UniValue& params, bool fHelp); @@ -224,16 +229,21 @@ 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 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); 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); 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/script/interpreter.cpp b/src/script/interpreter.cpp index 08b18729809d..d904c591bcff 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -959,6 +959,15 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_CHECKCOLDSTAKEVERIFY: + { + // check it is used in a valid cold stake transaction. + if(!checker.CheckColdStake(script)) { + 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..137814da118b 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -98,6 +98,11 @@ class BaseSignatureChecker return false; } + virtual bool CheckColdStake(const CScript& script) 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 CScript& script) const override { + return txTo->CheckColdStake(script); + } }; class MutableTransactionSignatureChecker : public TransactionSignatureChecker diff --git a/src/script/script.cpp b/src/script/script.cpp index 0fc8178ca0ce..60b5bb3a37fd 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"; @@ -248,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 b826c2e9dea8..45a38daa861e 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, @@ -602,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; 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/sign.cpp b/src/script/sign.cpp index 1f9ecc7564f7..7d643c37c722 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(); @@ -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; @@ -103,12 +104,29 @@ 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; + 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; } 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 +136,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 +161,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 +249,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/script/standard.cpp b/src/script/standard.cpp index 4b4239696af2..9fb1066d46bc 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 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) { + addressRet = CKeyID(uint160(vSolutions[!fColdStake])); + return true; } // Multisig txns have more than one address... return false; @@ -268,8 +273,17 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std:: if (addressRet.empty()) return false; - } - else + + } else if (typeRet == TX_COLDSTAKE) + { + if (vSolutions.size() < 2) + return false; + nRequiredRet = 2; + addressRet.push_back(CKeyID(uint160(vSolutions[0]))); + addressRet.push_back(CKeyID(uint160(vSolutions[1]))); + return true; + + } else { nRequiredRet = 1; CTxDestination address; @@ -317,6 +331,16 @@ CScript GetScriptForDestination(const CTxDestination& dest) return script; } +CScript GetScriptForStakeDelegation(const CKeyID& stakingKey, const CKeyID& spendingKey) +{ + CScript script; + script << OP_DUP << OP_HASH160 << OP_ROT << + OP_IF << OP_CHECKCOLDSTAKEVERIFY << ToByteVector(stakingKey) << + OP_ELSE << ToByteVector(spendingKey) << OP_ENDIF << + OP_EQUALVERIFY << OP_CHECKSIG; + return script; +} + CScript GetScriptForMultisig(int nRequired, const std::vector& keys) { CScript script; diff --git a/src/script/standard.h b/src/script/standard.h index 9970ded44528..d5ca54ef2b76 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. */ @@ -65,6 +65,7 @@ enum txnouttype TX_MULTISIG, TX_NULL_DATA, TX_ZEROCOINMINT, + TX_COLDSTAKE }; class CNoDestination { @@ -73,7 +74,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 @@ -87,10 +88,11 @@ 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); CScript GetScriptForMultisig(int nRequired, const std::vector& keys); +CScript GetScriptForStakeDelegation(const CKeyID& stakingKey, const CKeyID& spendingKey); #endif // BITCOIN_SCRIPT_STANDARD_H 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/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"], diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 330d0ac075e8..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) { @@ -194,7 +193,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 f9a7929fd111..a9cac75bd3ae 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -80,6 +80,29 @@ std::string AccountFromValue(const UniValue& value) return strAccount; } +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 + 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, addrType); +} + UniValue getnewaddress(const UniValue& params, bool fHelp) { if (fHelp || params.size() > 1) @@ -96,30 +119,157 @@ 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\"")); - LOCK2(cs_main, pwalletMain->cs_wallet); + return GetNewAddressFromAccount("receive", 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 (!pwalletMain->IsLocked()) - pwalletMain->TopUpKeyPool(); + if (fHelp || params.size() > 1) + throw std::runtime_error( + "getnewstakingaddress ( \"account\" )\n" + "\nReturns a new PIVX cold staking address for receiving delegated cold stakes.\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(); + "\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" - pwalletMain->SetAddressBook(keyID, strAccount, "receive"); - return CBitcoinAddress(keyID).ToString(); + "\nResult:\n" + "\"pivxaddress\" (string) The new pivx address\n" + + "\nExamples:\n" + + HelpExampleCli("getnewstakingaddress", "") + HelpExampleRpc("getnewstakingaddress", "\"\"") + + HelpExampleCli("getnewstakingaddress", "\"myaccount\"") + HelpExampleRpc("getnewstakingaddress", "\"myaccount\"")); + + return GetNewAddressFromAccount("coldstaking", params, CChainParams::STAKING_ADDRESS).ToString(); } +UniValue delegatoradd(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw std::runtime_error( + "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" + + "\nArguments:\n" + "1. \"addr\" (string, required) The address to whitelist\n" + + "\nResult:\n" + "true|false (boolean) true if successful.\n" + + "\nExamples:\n" + + HelpExampleCli("delegatoradd", "DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6") + + HelpExampleRpc("delegatoradd", "\"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"); + + return pwalletMain->SetAddressBook(keyID, "", "delegator"); +} + +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 disables 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"); + + 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) + 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", "")); + + return ListaddressesForPurpose("delegator"); +} + +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) { @@ -381,7 +531,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 @@ -401,6 +551,212 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) return wtx.GetHash().GetHex(); } +UniValue 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 < Params().GetMinColdStakingAmount()) + 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; + 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", 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); + + // 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); + 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."); + + ret.push_back(Pair("txid", wtx.GetHash().GetHex())); + return ret; +} + +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) @@ -429,7 +785,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 @@ -673,16 +1029,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; } @@ -696,9 +1072,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" @@ -708,6 +1084,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" @@ -733,44 +1110,80 @@ 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() - // (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); + std::string strAccount = params[0].get_str(); + return ValueFromAmount(GetAccountBalance(strAccount, nMinDepth, filter)); +} - if (!IsFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || depth < 0 || fConflicted) - continue; +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" - 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); - } + "\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, filter); + "\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); - return ValueFromAmount(nBalance); + if (params.size() == 0) + return ValueFromAmount(pwalletMain->GetColdStakingBalance()); + + std::string strAccount = params[0].get_str(); + return ValueFromAmount(GetAccountBalance(strAccount, /*nMinDepth*/ 1, ISMINE_COLD)); +} + +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" + + "\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("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()); + + std::string strAccount = params[0].get_str(); + return ValueFromAmount(GetAccountBalance(strAccount, /*nMinDepth*/ 1, ISMINE_SPENDABLE_DELEGATED)); } UniValue getunconfirmedbalance(const UniValue ¶ms, bool fHelp) @@ -858,9 +1271,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" @@ -875,6 +1288,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" @@ -891,7 +1305,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; @@ -905,10 +1319,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"); @@ -920,9 +1338,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" @@ -935,6 +1353,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" @@ -968,7 +1387,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)) @@ -982,6 +1401,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 @@ -1075,7 +1498,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; @@ -1243,6 +1666,79 @@ 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; + 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++) { + 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], 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); + } + } + + return results; +} + static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) { CBitcoinAddress addr; @@ -1332,9 +1828,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" @@ -1343,6 +1839,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" @@ -1405,9 +1903,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"); @@ -1577,7 +2078,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; @@ -1665,7 +2166,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; @@ -2167,7 +2668,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" @@ -2186,6 +2689,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())); @@ -3041,7 +3546,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"); } @@ -3064,7 +3569,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)); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0d1646f11c57..5b2f705b01f0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -508,9 +508,10 @@ 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; @@ -538,7 +539,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 +550,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; @@ -1108,10 +1109,38 @@ 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; } -CAmount CWalletTx::GetCredit(const isminefilter& filter) const +CAmount CWalletTx::GetColdStakingDebit(bool fUseCache) const +{ + return UpdateAmount(nColdDebitCached, fColdDebitCached, fUseCache, ISMINE_COLD, false); +} + +CAmount CWalletTx::GetStakeDelegationDebit(bool fUseCache) const +{ + return UpdateAmount(nDelegatedDebitCached, fDelegatedDebitCached, fUseCache, ISMINE_SPENDABLE_DELEGATED, false); +} + +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) @@ -1123,7 +1152,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; } @@ -1132,11 +1161,29 @@ 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; } } + if (filter & ISMINE_COLD) { + if (fColdCreditCached) + credit += nColdCreditCached; + else { + nColdCreditCached = pwallet->GetCredit(*this, ISMINE_COLD, fUnspent); + fColdCreditCached = true; + credit += nColdCreditCached; + } + } + if (filter & ISMINE_SPENDABLE_DELEGATED) { + if (fDelegatedCreditCached) + credit += nDelegatedCreditCached; + else { + nDelegatedCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE_DELEGATED, fUnspent); + fDelegatedCreditCached = true; + credit += nDelegatedCreditCached; + } + } return credit; } @@ -1155,6 +1202,20 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const } CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const +{ + return UpdateAmount(nAvailableCreditCached, fAvailableCreditCached, fUseCache, ISMINE_SPENDABLE);} + +CAmount CWalletTx::GetColdStakingCredit(bool fUseCache) const +{ + 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; @@ -1163,23 +1224,12 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const if (IsCoinBase() && GetBlocksToMaturity() > 0) return 0; - if (fUseCache && fAvailableCreditCached) - return nAvailableCreditCached; + if (fUseCache && cacheFlagToUpdate) + return amountToUpdate; - 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"); - } - } - - nAvailableCreditCached = nCredit; - fAvailableCreditCached = true; - return nCredit; + amountToUpdate = (fCredit) ? GetCredit(mimeType, true) : GetDebit(mimeType); + cacheFlagToUpdate = true; + return amountToUpdate; } // Return sum of unlocked coins @@ -1208,7 +1258,7 @@ CAmount CWalletTx::GetUnlockedCredit() const return nCredit; } - // Return sum of unlocked coins +// Return sum of locked coins CAmount CWalletTx::GetLockedCredit() const { if (pwallet == 0) @@ -1226,6 +1276,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); @@ -1353,10 +1406,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()); } @@ -1643,6 +1697,43 @@ 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->HasP2CSOutputs() && pcoin->IsTrusted()) + nTotal += pcoin->GetColdStakingCredit(); + } + } + + return nTotal; +} + +CAmount CWallet::GetStakingBalance(const bool fIncludeColdStaking) const +{ + return GetBalance() + (fIncludeColdStaking ? GetColdStakingBalance() : 0); +} + +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->HasP2CSOutputs() && pcoin->IsTrusted()) + nTotal += pcoin->GetStakeDelegationCredit(); + } + } + + return nTotal; +} + //std::map mapMintMaturity; //int nLastMaturityCheck = 0; @@ -1825,9 +1916,15 @@ void CWallet::AvailableCoins( bool fIncludeZeroValue, AvailableCoinsType nCoinType, bool fUseIX, - int nWatchonlyConfig) const + int nWatchonlyConfig, + bool fIncludeColdStaking, + 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); @@ -1892,16 +1989,23 @@ 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; - bool fIsSpendable = false; - if ((mine & ISMINE_SPENDABLE) != ISMINE_NO) - fIsSpendable = true; - if ((mine & ISMINE_MULTISIG) != ISMINE_NO) - fIsSpendable = true; + // --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; - vCoins.emplace_back(COutput(pcoin, i, nDepth, 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, fIsValid)); } } } @@ -1910,7 +2014,8 @@ void CWallet::AvailableCoins( std::map > CWallet::AvailableCoinsByAddress(bool fConfirmed, CAmount maxCoinValue) { std::vector vCoins; - AvailableCoins(vCoins, fConfirmed); + // include cold and delegated coins + AvailableCoins(vCoins, fConfirmed, nullptr, false, ALL_COINS, false, 1, true, true); std::map > mapCoins; for (COutput out : vCoins) { @@ -1918,10 +2023,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)) { + // 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)].push_back(out); + mapCoins[CBitcoinAddress(address, fColdStakeAddr ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS)].push_back(out); } return mapCoins; @@ -1990,7 +2101,11 @@ bool CWallet::SelectStakeCoins(std::list >& listInp LOCK(cs_main); //Add PIV std::vector vCoins; - AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS); + + // include cold, exclude delegated + 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) { for (const COutput &out : vCoins) { @@ -2055,7 +2170,7 @@ bool CWallet::SelectStakeCoins(std::list >& listInp bool CWallet::MintableCoins() { LOCK(cs_main); - CAmount nBalance = GetBalance(); + CAmount nBalance = GetStakingBalance(GetBoolArg("-coldstaking", true)); CAmount nZpivBalance = GetZerocoinBalance(false); int chainHeight = chainActive.Height(); @@ -2068,7 +2183,9 @@ bool CWallet::MintableCoins() return false; std::vector vCoins; - AvailableCoins(vCoins, true); + // include cold, exclude delegated + 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) { @@ -2201,12 +2318,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()) { @@ -2284,7 +2400,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; @@ -2353,7 +2470,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) { @@ -2374,6 +2491,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 @@ -2512,11 +2631,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 @@ -2541,7 +2660,7 @@ bool CWallet::CreateCoinStake( txNew.vout.push_back(CTxOut(0, scriptEmpty)); // Choose coins to use - CAmount nBalance = GetBalance(); + CAmount nBalance = GetStakingBalance(); if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) return error("CreateCoinStake : invalid reserve balance amount"); @@ -2603,7 +2722,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()); @@ -2671,7 +2790,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 { @@ -2840,11 +2959,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; @@ -2869,6 +2986,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); } @@ -2881,6 +2999,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 @@ -3043,7 +3183,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; @@ -5441,14 +5582,15 @@ 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); - if (!MoneyRange(nCredit)) - throw std::runtime_error("CWallet::GetCredit() : value out of range"); + 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"); return nCredit; } @@ -5528,6 +5670,11 @@ void CWalletTx::Init(const CWallet* pwalletIn) fImmatureWatchCreditCached = false; fAvailableWatchCreditCached = false; fChangeCached = false; + fColdDebitCached = false; + fColdCreditCached = false; + fDelegatedDebitCached = false; + fDelegatedCreditCached = false; + fStakeDelegationVoided = false; nDebitCached = 0; nCreditCached = 0; nImmatureCreditCached = 0; @@ -5540,6 +5687,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 +5750,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..f62795f506de 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; @@ -346,14 +346,14 @@ 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; /// 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; @@ -427,6 +427,9 @@ 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 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; @@ -439,7 +442,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, @@ -448,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); @@ -498,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); @@ -506,8 +509,9 @@ 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 HasAddressBook(const CTxDestination& address) const; + bool HasDelegator(const CTxOut& out) const; bool UpdatedTransaction(const uint256& hashTx); @@ -718,6 +722,11 @@ 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 bool fStakeDelegationVoided; mutable CAmount nDebitCached; mutable CAmount nCreditCached; mutable CAmount nImmatureCreditCached; @@ -731,6 +740,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); @@ -790,7 +803,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 @@ -802,6 +815,15 @@ class CWalletTx : public CMerkleTx CAmount GetLockedWatchOnlyCredit() const; CAmount GetChange() const; + // 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; + + // 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, @@ -822,7 +844,6 @@ class CWalletTx : public CMerkleTx int64_t GetComputedTxTime() const; int GetRequestCount() const; void RelayWalletTransaction(std::string strCommand = "tx"); - std::set GetConflicts() const; }; diff --git a/src/wallet/wallet_ismine.cpp b/src/wallet/wallet_ismine.cpp index aaa9a61c0676..c5e3bd3780bc 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: { + 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) + 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; 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 diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py new file mode 100755 index 000000000000..a4d6fb91435b --- /dev/null +++ b/test/functional/feature_coldStaking.py @@ -0,0 +1,466 @@ +#!/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, assert_equal, assert_greater_than, sync_chain, assert_raises_rpc_error + +# 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 = 3 + 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 = 200 + + # nodes[0] - coin-owner + # nodes[1] - cold-staker + + # 1) nodes[0] mines 20 blocks. nodes[2] mines 180 blocks. + # ----------------------------------------------------------- + print("*** 1 ***") + self.log.info("Mining %d blocks..." % INITAL_MINED_BLOCKS) + 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 ***") + 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.log.info("Creating a stake-delegation tx before cold staking enforcement...") + 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) + + + # 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...") + 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 (0.99) below the threshold") + assert_raises_rpc_error(-8, "Invalid amount", + 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("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"] != "") + assert_equal(res["owner_address"], owner_address) + assert_equal(res["staker_address"], staker_address) + self.generateBlock() + sync_chain(self.nodes) + self.log.info("%d Txes created." % NUM_OF_INPUTS) + # check balances: + self.expected_balance = NUM_OF_INPUTS * INPUT_VALUE + self.checkBalances() + + + # 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_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() + 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. + # ----------------------------------------------------------- + print("*** 6 ***") + 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) + self.log.info("Delegator address %s whitelisted" % owner_address) + + + # 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_greater_than(len(delegated_utxos), 0) + u = delegated_utxos[0] + 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 ***") + 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 + 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() + 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_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...") + # 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 + 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() + 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 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_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...") + # 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 + sync_chain(self.nodes) + 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") + + + # 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_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...") + # 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_equal(ret, "bad-p2cs-outs") + + # Verify that nodes[0] rejects it + sync_chain(self.nodes) + 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.") + self.generateBlock() + sync_chain(self.nodes) + 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) + assert(txhash != None) + 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() + 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...") + 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, n=1): + fStaked = False + while (not fStaked): + try: + self.nodes[2].generate(n) + 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_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_equal(float(w_info["delegated_balance"]), 0) + assert_equal(float(w_info["cold_staking_balance"]), self.expected_balance) + + + + 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=1): + 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=0): + 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 == 1: + # 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..39de1dd60ee6 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -61,6 +61,7 @@ # 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', @@ -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',