diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index f7a2deb0d..fd78d3701 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -153,6 +153,7 @@ testScripts = [ 'signmessages.py', # 'nulldummy.py', 'import-rescan.py', + 'harddustlimit.py', # While fee bumping should work in Doge, these tests depend on free transactions, which we don't support. # Disable until we can do a full rewrite of the tests (possibly upstream), or revise fee schedule, or something 'bumpfee.py', diff --git a/qa/rpc-tests/harddustlimit.py b/qa/rpc-tests/harddustlimit.py new file mode 100644 index 000000000..9d56febb7 --- /dev/null +++ b/qa/rpc-tests/harddustlimit.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Dogecoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Hard dust limit QA test. + +# Tests nodes with differing -dustlimits +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from decimal import Decimal + +class HardDustLimitTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 3 + + def setup_network(self, split=False): + self.nodes = [] + self.nodes.append(start_node(0, self.options.tmpdir, ["-dustlimit=0.1", "-debug"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-dustlimit=1", "-debug"])) + self.nodes.append(start_node(2, self.options.tmpdir, ["-dustlimit=0.01", "-debug"])) + + 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 run_test(self): + + self.fee = Decimal("1") + self.seed = 1000 + self.coinselector = {'minimumAmount': self.fee * 10, 'maximumAmount': self.seed} + + # set up addresses + n0a1 = self.nodes[0].getnewaddress() + n0a2 = self.nodes[0].getnewaddress() + n0a3 = self.nodes[0].getnewaddress() + n1a1 = self.nodes[1].getnewaddress() + n2a1 = self.nodes[2].getnewaddress() + n2a2 = self.nodes[2].getnewaddress() + n2a3 = self.nodes[2].getnewaddress() + n2a4 = self.nodes[2].getnewaddress() + + # mine some blocks and prepare some coins + self.nodes[2].generate(1) + self.sync_all() + self.nodes[0].generate(101) + self.sync_all() + self.nodes[0].sendtoaddress(n0a1, self.seed) + self.nodes[0].sendtoaddress(n2a1, self.seed) + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + # create dusty transactions + self.send_dusty_tx(self.nodes[2], n2a2, n0a2, Decimal("0.9")) + self.send_dusty_tx(self.nodes[2], n2a3, n0a3, Decimal("0.09")) + self.send_dusty_tx(self.nodes[0], n2a4, n1a1, Decimal("1")) + + # wait 10 seconds to sync mempools + time.sleep(10) + + assert_equal(self.nodes[2].getmempoolinfo()['size'], 3) + assert_equal(self.nodes[1].getmempoolinfo()['size'], 1) + assert_equal(self.nodes[0].getmempoolinfo()['size'], 2) + + def send_dusty_tx(self, n, addr1, addr2, dust): + avail = n.listunspent(0, 1000, [], True, self.coinselector) + inputs = [ {'txid': avail[0]['txid'], 'vout': avail[0]['vout']}] + outputs = { addr1 : avail[0]['amount'] - self.fee - dust , addr2: dust } + rawtx = n.createrawtransaction(inputs, outputs) + rawtx = n.signrawtransaction(rawtx) + n.sendrawtransaction(rawtx['hex']) + +if __name__ == '__main__': + HardDustLimitTest().main() diff --git a/src/init.cpp b/src/init.cpp index 22dc45929..dd1942133 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -475,6 +475,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-incrementalrelayfee=", strprintf("Fee rate (in %s/kB) used to define cost of relay, used for mempool limiting and BIP 125 replacement. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE))); strUsage += HelpMessageOpt("-dustrelayfee=", strprintf("Fee rate (in %s/kB) used to defined dust, the value of an output such that it will cost about 1/3 of its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE))); } + strUsage += HelpMessageOpt("-dustlimit=", strprintf(_("Amount under which a transaction output is considered dust, in %s (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_DUST_LIMIT))); strUsage += HelpMessageOpt("-bytespersigop", strprintf(_("Equivalent bytes per sigop in transactions for relay and mining (default: %u)"), DEFAULT_BYTES_PER_SIGOP)); strUsage += HelpMessageOpt("-datacarrier", strprintf(_("Relay and mine data carrier transactions (default: %u)"), DEFAULT_ACCEPT_DATACARRIER)); strUsage += HelpMessageOpt("-datacarriersize", strprintf(_("Maximum size of data in data carrier transactions we relay and mine (default: %u)"), MAX_OP_RETURN_RELAY)); @@ -1043,6 +1044,14 @@ bool AppInitParameterInteraction() return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString())); nBytesPerSigOp = GetArg("-bytespersigop", nBytesPerSigOp); + if (IsArgSet("-dustlimit")) + { + CAmount n = nDustLimit; + if (!ParseMoney(GetArg("-dustlimit", ""), n)) + return InitError(AmountErrMsg("dustlimit", GetArg("-dustlimit", ""))); + nDustLimit = n; + } + #ifdef ENABLE_WALLET if (!CWallet::ParameterInteraction()) return false; diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index ec398f662..7b2b23964 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -209,6 +209,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) CFeeRate incrementalRelayFee = CFeeRate(DEFAULT_INCREMENTAL_RELAY_FEE); CFeeRate dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); unsigned int nBytesPerSigOp = DEFAULT_BYTES_PER_SIGOP; +unsigned int nDustLimit = DEFAULT_DUST_LIMIT; int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost) { diff --git a/src/policy/policy.h b/src/policy/policy.h index a7fc8b78e..be34b9156 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -46,6 +46,13 @@ static const unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600; * only increase the dust limit after prior releases were already not creating * outputs below the new threshold */ static const unsigned int DUST_RELAY_TX_FEE = 1000; +/** + * Dogecoin: Default dust limit that is evaluated when considering whether a + * transaction output is required to pay additional fee for relay and inclusion + * in blocks. Overridden by -dustlimit + */ +static const unsigned int DEFAULT_DUST_LIMIT = 100000000; + /** * Standard script verification flags that standard transactions will comply * with. However scripts violating these flags may still be present in valid @@ -96,6 +103,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) extern CFeeRate incrementalRelayFee; extern CFeeRate dustRelayFee; extern unsigned int nBytesPerSigOp; +extern unsigned int nDustLimit; /** Compute the virtual transaction size (weight reinterpreted as bytes). */ int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost); diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 16a92bf2e..0d0ceda54 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -15,6 +15,8 @@ static const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000; static const int WITNESS_SCALE_FACTOR = 4; +extern unsigned int nDustLimit; + /** An outpoint - a combination of a transaction hash and an index n into its vout */ class COutPoint { @@ -195,7 +197,7 @@ public: */ // Dogecoin: Anything below 1 DOGE is always dust - return COIN; + return nDustLimit; } bool IsDust(const CFeeRate &minRelayTxFeeRate) const diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 4e84bb688..79a994bf6 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -36,6 +36,8 @@ std::vector> wtxn; typedef set > CoinSet; +extern unsigned int nDustLimit; + BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) static const CWallet wallet; @@ -524,6 +526,18 @@ BOOST_AUTO_TEST_CASE(GetMinimumFee_dust_test) BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 963, 0, pool), 2 * nMinTxFee); BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 1000, 0, pool), 2 * nMinTxFee); BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 1999, 0, pool), 3 * nMinTxFee); + + // change the hard dust limit + + nDustLimit = COIN / 10; + + // Confirm dust penalty fees are not added + + BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 963, 0, pool), 1 * nMinTxFee); + BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 1000, 0, pool), 1 * nMinTxFee); + BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 1999, 0, pool), 2 * nMinTxFee); + + nDustLimit = COIN; } BOOST_AUTO_TEST_SUITE_END()