diff --git a/src/chainparams.cpp b/src/chainparams.cpp index c14bd20d304f..6f69ac1d46bd 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -203,9 +203,6 @@ 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. @@ -347,9 +344,6 @@ 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; @@ -448,9 +442,6 @@ 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 1497d4319582..94f16641c439 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -145,8 +145,6 @@ 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; } @@ -232,7 +230,7 @@ class CChainParams int nPublicZCSpendsV4; int nBlockStakeModifierlV2; int nBlockEnforceNewMessageSignatures; - int nColdStakingStart; + CAmount nMinColdStakingAmount; // fake serial attack diff --git a/src/main.cpp b/src/main.cpp index d997261b227f..a846c2ad4798 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1212,10 +1212,10 @@ 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 + // check cold staking enforcement (for delegations) 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"); + return state.DoS(10, 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"); @@ -1355,9 +1355,9 @@ 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 + // Check transaction int chainHeight = chainActive.Height(); - bool fColdStakingActive = Params().Cold_Staking_Enabled(chainHeight); + bool fColdStakingActive = sporkManager.IsSporkActive(SPORK_17_COLDSTAKING_ENFORCEMENT); 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"); @@ -4496,6 +4496,12 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo LogPrintf("%s : skipping transaction locking checks\n", __func__); } + // Cold Staking enforcement (true during sync - reject P2CS outputs when false) + bool fColdStakingActive = true; + + // Zerocoin activation + bool fZerocoinActive = block.GetBlockTime() > Params().Zerocoin_StartTime(); + // masternode payments / budgets CBlockIndex* pindexPrev = chainActive.Tip(); int nHeight = 0; @@ -4522,7 +4528,10 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo REJECT_INVALID, "bad-p2cs-outs"); } - // Valid masternode/budget payment + // set Cold Staking Spork + fColdStakingActive = sporkManager.IsSporkActive(SPORK_17_COLDSTAKING_ENFORCEMENT); + + // check 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__), @@ -4535,8 +4544,6 @@ 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; diff --git a/src/spork.cpp b/src/spork.cpp index 533a6cea128f..0f1ed7f667b1 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -24,6 +24,7 @@ std::vector sporkDefs = { MAKE_SPORK_DEF(SPORK_14_NEW_PROTOCOL_ENFORCEMENT, 4070908800ULL), // OFF MAKE_SPORK_DEF(SPORK_15_NEW_PROTOCOL_ENFORCEMENT_2, 4070908800ULL), // OFF MAKE_SPORK_DEF(SPORK_16_ZEROCOIN_MAINTENANCE_MODE, 4070908800ULL), // OFF + MAKE_SPORK_DEF(SPORK_17_COLDSTAKING_ENFORCEMENT, 4070908800ULL), // OFF }; CSporkManager sporkManager; @@ -172,6 +173,7 @@ bool CSporkManager::UpdateSpork(SporkId nSporkID, int64_t nValue) fNewSigs = chainActive.NewSigsActive(); } + CSporkMessage spork = CSporkMessage(nSporkID, nValue, GetTime()); if(spork.Sign(strMasterPrivKey, fNewSigs)){ diff --git a/src/spork.h b/src/spork.h index 674c2fcbc74d..adcc412c2a03 100644 --- a/src/spork.h +++ b/src/spork.h @@ -18,6 +18,7 @@ #include "obfuscation.h" #include "protocol.h" + class CSporkMessage; class CSporkManager; diff --git a/src/sporkid.h b/src/sporkid.h index 7d681725926c..f2fc222404ca 100644 --- a/src/sporkid.h +++ b/src/sporkid.h @@ -22,6 +22,7 @@ enum SporkId : int32_t { SPORK_14_NEW_PROTOCOL_ENFORCEMENT = 10013, SPORK_15_NEW_PROTOCOL_ENFORCEMENT_2 = 10014, SPORK_16_ZEROCOIN_MAINTENANCE_MODE = 10015, + SPORK_17_COLDSTAKING_ENFORCEMENT = 10017, SPORK_INVALID = -1 }; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a9cac75bd3ae..d8219cdc101a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -560,11 +560,10 @@ UniValue CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CR 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); + if (!sporkManager.IsSporkActive(SPORK_17_COLDSTAKING_ENFORCEMENT) && !fForceNotEnabled) { + std::string errMsg = "Cold Staking disabled with SPORK 17.\n" + "You may force the stake delegation setting fForceNotEnabled to true.\n" + "WARNING: If relayed before activation, this tx will be rejected resulting in a ban.\n"; throw JSONRPCError(RPC_WALLET_ERROR, errMsg); } @@ -657,7 +656,7 @@ UniValue delegatestake(const UniValue& params, bool fHelp) "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)." + "6. \"fForceNotEnabled\" (boolean, optional, default = false) force the creation even if SPORK 17 is disabled (for tests)." "\nResult:\n" "{\n" diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5b2f705b01f0..81602203bd23 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2103,7 +2103,7 @@ bool CWallet::SelectStakeCoins(std::list >& listInp std::vector vCoins; // include cold, exclude delegated - const bool fIncludeCold = Params().Cold_Staking_Enabled(blockHeight) && GetBoolArg("-coldstaking", true); + const bool fIncludeCold = sporkManager.IsSporkActive(SPORK_17_COLDSTAKING_ENFORCEMENT) && GetBoolArg("-coldstaking", true); AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, fIncludeCold, false); CAmount nAmountSelected = 0; @@ -2184,7 +2184,7 @@ bool CWallet::MintableCoins() std::vector vCoins; // include cold, exclude delegated - const bool fIncludeCold = Params().Cold_Staking_Enabled(chainHeight) && GetBoolArg("-coldstaking", true); + const bool fIncludeCold = sporkManager.IsSporkActive(SPORK_17_COLDSTAKING_ENFORCEMENT) && GetBoolArg("-coldstaking", true); AvailableCoins(vCoins, true, NULL, false, STAKABLE_COINS, false, 1, fIncludeCold, false); int64_t time = GetAdjustedTime(); diff --git a/test/functional/feature_coldStaking.py b/test/functional/feature_coldStaking.py index a4d6fb91435b..90331eed1121 100755 --- a/test/functional/feature_coldStaking.py +++ b/test/functional/feature_coldStaking.py @@ -31,6 +31,7 @@ def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 self.extra_args = [['-staking=1']] * self.num_nodes + self.extra_args[0].append('-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi') def setup_network(self): @@ -60,27 +61,50 @@ def init_test(self): self.test_nodes[i].wait_for_verack() + + def setColdStakingEnforcement(self, fEnable=True): + new_val = 1563253447 if fEnable else 4070908800 + # update spork 17 and mine 1 more block + mess = "Enabling" if fEnable else "Disabling" + mess += " cold staking with SPORK 17..." + self.log.info(mess) + res = self.nodes[0].spork("SPORK_17_COLDSTAKING_ENFORCEMENT", new_val) + self.log.info(res) + assert (res == "success") + time.sleep(1) + sync_chain(self.nodes) + + + def isColdStakingEnforced(self): + # verify from node[1] + active = self.nodes[1].spork("active") + return active["SPORK_17_COLDSTAKING_ENFORCEMENT"] + + + 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 + INITAL_MINED_BLOCKS = LAST_POW_BLOCK + 1 # nodes[0] - coin-owner # nodes[1] - cold-staker - # 1) nodes[0] mines 20 blocks. nodes[2] mines 180 blocks. + # 1) nodes[0] mines 20 blocks. nodes[2] mines 231 blocks. # ----------------------------------------------------------- + # Check that SPORK 17 is disabled + assert (not self.isColdStakingEnforced()) print("*** 1 ***") self.log.info("Mining %d blocks..." % INITAL_MINED_BLOCKS) - self.nodes[0].generate(20) + self.generateBlock(20, 0) sync_chain(self.nodes) self.log.info("20 Blocks mined.") - self.generateBlock(180) + self.generateBlock(INITAL_MINED_BLOCKS-20) sync_chain(self.nodes) - self.log.info("200 Blocks mined.") + self.log.info("251 Blocks mined.") # 2) nodes[0] generates a owner address @@ -100,9 +124,11 @@ def run_test(self): 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) + + # Enable SPORK + self.setColdStakingEnforcement() + # double check + assert (self.isColdStakingEnforced()) # 4) nodes[0] delegates a number of inputs for nodes[1] to stake em. @@ -196,7 +222,7 @@ def run_test(self): 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.generateBlock(1, 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) @@ -300,17 +326,30 @@ def run_test(self): print("*** 12 ***") self.log.info("Cancel the stake delegation spending the cold stakes...") delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) + # remove one utxo to spend later + final_spend = delegated_utxos.pop() 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) + # deactivate SPORK 17 and check that the owner can still spend the last utxo + self.setColdStakingEnforcement(False) + assert (not self.isColdStakingEnforced()) + txhash = self.spendUTXOsWithNode([final_spend], 0) + assert(txhash != None) + self.log.info("Good. Owner was able to void the last stake delegation (with SPORK 17 disabled) - 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())) + # re-activate SPORK17 + self.setColdStakingEnforcement() + assert (self.isColdStakingEnforced()) # 13) check that coinstaker is empty and can no longer stake. @@ -321,11 +360,11 @@ def run_test(self): self.log.info("Cigar. Cold staker was NOT able to create any more blocks.\n") - def generateBlock(self, n=1): + def generateBlock(self, n=1, nodeid=2): fStaked = False while (not fStaked): try: - self.nodes[2].generate(n) + self.nodes[nodeid].generate(n) fStaked = True except JSONRPCException as e: if ("Couldn't create new block" in str(e)):