]> Git Repo - VerusCoin.git/commitdiff
Return improved error message when trying to spend Coinbase coins (#1373).
authorSimon <[email protected]>
Thu, 22 Sep 2016 18:30:32 +0000 (11:30 -0700)
committerSimon <[email protected]>
Mon, 17 Oct 2016 02:05:56 +0000 (19:05 -0700)
Extra parameter added to AvailableCoins to include or exclude Coinbase coins.
SelectCoins, used for sending taddr->taddr, will exclude Coinbase coins.

Added qa rpc test and a runtime parameter -regtestprotectcoinbase to enforce
the coinbase->zaddr consensus rule in regtest mode.

qa/pull-tester/rpc-tests.sh
qa/rpc-tests/wallet_protectcoinbase.py [new file with mode: 0755]
src/chainparams.cpp
src/chainparams.h
src/wallet/asyncrpcoperation_sendmany.cpp
src/wallet/wallet.cpp
src/wallet/wallet.h

index e75999199ad00d0c40526d3812d0539209923656..0d70968ebfc9368b0378a0ad5b5117797448b4fc 100755 (executable)
@@ -11,6 +11,7 @@ export BITCOIND=${REAL_BITCOIND}
 #Run the tests
 
 testScripts=(
+    'wallet_protectcoinbase.py'
     'wallet.py'
     'listtransactions.py'
     'mempool_resurrect_test.py'
diff --git a/qa/rpc-tests/wallet_protectcoinbase.py b/qa/rpc-tests/wallet_protectcoinbase.py
new file mode 100755 (executable)
index 0000000..78550e4
--- /dev/null
@@ -0,0 +1,126 @@
+#!/usr/bin/env python2
+# Copyright (c) 2016 The Zcash developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from time import *
+
+class Wallet2Test (BitcoinTestFramework):
+
+    def setup_chain(self):
+        print("Initializing test directory "+self.options.tmpdir)
+        initialize_chain_clean(self.options.tmpdir, 4)
+
+    # Start nodes with -regtestprotectcoinbase to set fCoinbaseMustBeProtected to true.
+    def setup_network(self, split=False):
+        self.nodes = start_nodes(3, self.options.tmpdir, extra_args=[['-regtestprotectcoinbase']] * 3 )
+        connect_nodes_bi(self.nodes,0,1)
+        connect_nodes_bi(self.nodes,1,2)
+        connect_nodes_bi(self.nodes,0,2)
+        self.is_network_split=False
+        self.sync_all()
+
+    def wait_for_operationd_success(self, myopid):
+        print('waiting for async operation {}'.format(myopid))
+        opids = []
+        opids.append(myopid)
+        timeout = 120
+        status = None
+        for x in xrange(1, timeout):
+            results = self.nodes[0].z_getoperationresult(opids)
+            if len(results)==0:
+                sleep(1)
+            else:
+                status = results[0]["status"]
+                break
+        print('...returned status: {}'.format(status))
+        assert_equal("success", status)
+
+    def run_test (self):
+        print "Mining blocks..."
+
+        self.nodes[0].generate(4)
+
+        walletinfo = self.nodes[0].getwalletinfo()
+        assert_equal(walletinfo['immature_balance'], 40)
+        assert_equal(walletinfo['balance'], 0)
+
+        self.sync_all()
+        self.nodes[1].generate(101)
+        self.sync_all()
+
+        assert_equal(self.nodes[0].getbalance(), 40)
+        assert_equal(self.nodes[1].getbalance(), 10)
+        assert_equal(self.nodes[2].getbalance(), 0)
+
+        # Send will fail because we are enforcing the consensus rule that
+        # coinbase utxos can only be sent to a zaddr.
+        errorString = ""
+        try:
+            self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0)
+        except JSONRPCException,e:
+            errorString = e.error['message']
+        assert_equal("Coinbase funds can only be sent to a zaddr" in errorString, True)
+
+        # send node 0 taddr to node 0 zaddr
+        mytaddr = self.nodes[0].getnewaddress()
+        myzaddr = self.nodes[0].z_getnewaddress()
+        recipients = []
+        recipients.append({"address":myzaddr, "amount":20.0})
+        myopid = self.nodes[0].z_sendmany(mytaddr, recipients)
+        self.wait_for_operationd_success(myopid)
+        self.nodes[1].generate(1)
+        self.sync_all()
+
+        # check balances (the z_sendmany consumes 3 coinbase utxos)
+        resp = self.nodes[0].z_gettotalbalance()
+        assert_equal(Decimal(resp["transparent"]), Decimal('10.0'))
+        assert_equal(Decimal(resp["private"]), Decimal('29.9999'))
+        assert_equal(Decimal(resp["total"]), Decimal('39.9999'))
+
+        # convert note to transparent funds
+        recipients = []
+        recipients.append({"address":mytaddr, "amount":20.0})
+        myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
+        self.wait_for_operationd_success(myopid)
+        self.nodes[1].generate(1)
+        self.sync_all()
+
+        # check balances
+        resp = self.nodes[0].z_gettotalbalance()
+        assert_equal(Decimal(resp["transparent"]), Decimal('30.0'))
+        assert_equal(Decimal(resp["private"]), Decimal('9.9998'))
+        assert_equal(Decimal(resp["total"]), Decimal('39.9998'))
+
+        # Send will fail because send amount is too big, even when including coinbase utxos
+        errorString = ""
+        try:
+            self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 99999.0)
+        except JSONRPCException,e:
+            errorString = e.error['message']
+        assert_equal("Insufficient funds" in errorString, True)
+
+        # Send will fail because of insufficient funds unless sender uses coinbase utxos
+        try:
+            self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 21.0)
+        except JSONRPCException,e:
+            errorString = e.error['message']
+        assert_equal("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr" in errorString, True)
+
+        # Send will succeed because the balance of non-coinbase utxos is 20.0
+        try:
+            self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 19.0)
+        except JSONRPCException:
+            assert(False)
+
+        self.nodes[1].generate(10)
+        self.sync_all()
+
+        # check balance
+        assert_equal(self.nodes[2].getbalance(), Decimal('19'))
+
+if __name__ == '__main__':
+    Wallet2Test ().main ()
index 8446993ce988444797aef3fcce5a41c76f3f96a7..523bd4e043783cc900c58722a7d974c5092b9e87 100644 (file)
@@ -336,6 +336,11 @@ CChainParams &Params(CBaseChainParams::Network network) {
 void SelectParams(CBaseChainParams::Network network) {
     SelectBaseParams(network);
     pCurrentParams = &Params(network);
+
+    // Some python qa rpc tests need to enforce the coinbase consensus rule
+    if (network == CBaseChainParams::REGTEST && mapArgs.count("-regtestprotectcoinbase")) {
+        regTestParams.SetRegTestCoinbaseMustBeProtected();
+    }
 }
 
 bool SelectParamsFromCommandLine()
index ac9d099edea2c136dbbaea6baa1f92e5d9b2db62..c60baf6e3a33bce75a1061b3147bb89f635cc09e 100644 (file)
@@ -83,6 +83,8 @@ public:
     std::string GetFoundersRewardAddressAtIndex(int i) const;
     /** #1398 to return a fixed founders reward script for miner_tests */
     bool fMinerTestModeForFoundersRewardScript = false;
+    /** Enforce coinbase consensus rule in regtest mode */
+    void SetRegTestCoinbaseMustBeProtected() { consensus.fCoinbaseMustBeProtected = true; }
 protected:
     CChainParams() {}
 
index 0f663838712894e2255e8c727d0689e7a51f4f78..a374aee6253d2ec5c374f48164b21b5676b02ced 100644 (file)
@@ -715,7 +715,7 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) {
 
     LOCK2(cs_main, pwalletMain->cs_wallet);
 
-    pwalletMain->AvailableCoins(vecOutputs, false, NULL, true);
+    pwalletMain->AvailableCoins(vecOutputs, false, NULL, true, fAcceptCoinbase);
 
     BOOST_FOREACH(const COutput& out, vecOutputs) {
         if (out.nDepth < mindepth_) {
index 87deb024140e2abe955ef54ec3be2941381a8eb4..e8cb3493edcae41107a21b1efa67e267a05235ba 100644 (file)
@@ -2050,7 +2050,7 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
 /**
  * populate vCoins with vector of available COutputs.
  */
-void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const
+void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue, bool fIncludeCoinBase) const
 {
     vCoins.clear();
 
@@ -2067,6 +2067,9 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
             if (fOnlyConfirmed && !pcoin->IsTrusted())
                 continue;
 
+            if (pcoin->IsCoinBase() && !fIncludeCoinBase)
+                continue;
+
             if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
                 continue;
 
@@ -2232,10 +2235,38 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
     return true;
 }
 
-bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
+bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet,  bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl* coinControl) const
 {
-    vector<COutput> vCoins;
-    AvailableCoins(vCoins, true, coinControl);
+    // Output parameter fOnlyCoinbaseCoinsRet is set to true when the only available coins are coinbase utxos.
+    vector<COutput> vCoinsNoCoinbase, vCoinsWithCoinbase;
+    AvailableCoins(vCoinsNoCoinbase, true, coinControl, false, false);
+    AvailableCoins(vCoinsWithCoinbase, true, coinControl, false, true);
+    fOnlyCoinbaseCoinsRet = vCoinsNoCoinbase.size() == 0 && vCoinsWithCoinbase.size() > 0;
+
+    // If coinbase utxos can only be sent to zaddrs, exclude any coinbase utxos from coin selection.
+    bool fProtectCoinbase = Params().GetConsensus().fCoinbaseMustBeProtected;
+    vector<COutput> vCoins = (fProtectCoinbase) ? vCoinsNoCoinbase : vCoinsWithCoinbase;
+
+    // Output parameter fNeedCoinbaseCoinsRet is set to true if coinbase utxos need to be spent to meet target amount
+    if (fProtectCoinbase && vCoinsWithCoinbase.size() > vCoinsNoCoinbase.size()) {
+        CAmount value = 0;
+        for (const COutput& out : vCoinsNoCoinbase) {
+            if (!out.fSpendable) {
+                continue;
+            }
+            value += out.tx->vout[out.i].nValue;
+        }
+        if (value <= nTargetValue) {
+            CAmount valueWithCoinbase = 0;
+            for (const COutput& out : vCoinsWithCoinbase) {
+                if (!out.fSpendable) {
+                    continue;
+                }
+                valueWithCoinbase += out.tx->vout[out.i].nValue;
+            }
+            fNeedCoinbaseCoinsRet = (valueWithCoinbase >= nTargetValue);
+        }
+    }
 
     // coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
     if (coinControl && coinControl->HasSelected())
@@ -2355,9 +2386,17 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend,
                 // Choose coins to use
                 set<pair<const CWalletTx*,unsigned int> > setCoins;
                 CAmount nValueIn = 0;
-                if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl))
+                bool fOnlyCoinbaseCoins = false;
+                bool fNeedCoinbaseCoins = false;
+                if (!SelectCoins(nTotalValue, setCoins, nValueIn, fOnlyCoinbaseCoins, fNeedCoinbaseCoins, coinControl))
                 {
-                    strFailReason = _("Insufficient funds");
+                    if (fOnlyCoinbaseCoins && Params().GetConsensus().fCoinbaseMustBeProtected) {
+                        strFailReason = _("Coinbase funds can only be sent to a zaddr");
+                    } else if (fNeedCoinbaseCoins) {
+                        strFailReason = _("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr");
+                    } else {
+                        strFailReason = _("Insufficient funds");
+                    }
                     return false;
                 }
                 BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
index c3f1fd63f5077e9c86d07f8fc0fe4ea3e424e73f..376daba995da6ef6fcfad5bf05621f7b593e5ad1 100644 (file)
@@ -556,7 +556,7 @@ public:
 class CWallet : public CCryptoKeyStore, public CValidationInterface
 {
 private:
-    bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const;
+    bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl *coinControl = NULL) const;
 
     CWalletDB *pwalletdbEncryption;
 
@@ -689,7 +689,7 @@ public:
     //! check whether we are allowed to upgrade (or already support) to the named feature
     bool CanSupportFeature(enum WalletFeature wf) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
 
-    void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const;
+    void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false, bool fIncludeCoinBase=true) const;
     bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const;
 
     bool IsSpent(const uint256& hash, unsigned int n) const;
This page took 0.039481 seconds and 4 git commands to generate.