diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2700eec9c..5a81bf262 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,7 @@ jobs: fail-fast: false matrix: name: + - aarch64-linux - armhf-linux - i686-linux - i686-win @@ -39,69 +40,93 @@ jobs: include: - name: i686-linux host: i686-pc-linux-gnu - os: ubuntu-20.04 + os: ubuntu-18.04 packages: g++-multilib bc python3-zmq run-tests: true + check-security: true + check-symbols: true dep-opts: "NO_QT=1" - config-opts: "--enable-zmq --enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++" + config-opts: "--enable-zmq --enable-glibc-back-compat LDFLAGS=-static-libstdc++" goal: install - name: armhf-linux host: arm-linux-gnueabihf - os: ubuntu-20.04 + os: ubuntu-18.04 packages: g++-arm-linux-gnueabihf run-tests: false + check-security: true + check-symbols: false dep-opts: "NO_QT=1" - config-opts: "--enable-glibc-back-compat --enable-reduce-exports --disable-tests" + config-opts: "--enable-glibc-back-compat --disable-tests LDFLAGS=-static-libstdc++" + goal: install + - name: aarch64-linux + host: aarch64-linux-gnu + os: ubuntu-18.04 + packages: g++-aarch64-linux-gnu + run-tests: false + check-security: true + check-symbols: false + dep-opts: "NO_QT=1" + config-opts: "--enable-zmq --enable-glibc-back-compat --disable-tests LDFLAGS=-static-libstdc++" goal: install - name: x86_64-linux-nowallet host: x86_64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-18.04 packages: python3 run-tests: true + check-security: true + check-symbols: true dep-opts: "NO_WALLET=1" - config-opts: "--enable-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports --disable-wallet" + config-opts: "--enable-gui=qt5 --enable-glibc-back-compat --disable-wallet LDFLAGS=-static-libstdc++" goal: install - name: x86_64-linux-dbg host: x86_64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-18.04 packages: bc python3-zmq run-tests: true + check-security: true + check-symbols: false dep-opts: "DEBUG=1" - config-opts: "--enable-gui=qt5 --enable-zmq --enable-glibc-back-compat --enable-reduce-exports CPPFLAGS=-DDEBUG_LOCKORDER" + config-opts: "--enable-gui=qt5 --enable-zmq --enable-glibc-back-compat CPPFLAGS=-DDEBUG_LOCKORDER" goal: install - name: i686-win host: i686-w64-mingw32 arch: "i386" - os: ubuntu-20.04 - packages: python3 nsis g++-mingw-w64-i686 wine bc wine-binfmt + os: ubuntu-18.04 + packages: python3 nsis g++-mingw-w64-i686 wine-stable bc wine-binfmt postinstall: | sudo update-alternatives --set i686-w64-mingw32-gcc /usr/bin/i686-w64-mingw32-gcc-posix sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix sudo update-binfmts --import /usr/share/binfmts/wine run-tests: true + check-security: true + check-symbols: false dep-opts: "" - config-opts: "--enable-reduce-exports --enable-gui=qt5" + config-opts: "--enable-gui=qt5" goal: install - name: x86_64-win host: x86_64-w64-mingw32 arch: "i386" - os: ubuntu-20.04 + os: ubuntu-18.04 packages: python3 nsis g++-mingw-w64-x86-64 wine64 bc wine-binfmt postinstall: | sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix sudo update-binfmts --import /usr/share/binfmts/wine run-tests: true + check-security: true + check-symbols: false dep-opts: "" - config-opts: "--enable-reduce-exports --enable-gui=qt5" + config-opts: "--enable-gui=qt5" goal: install - name: x86_64-macos host: x86_64-apple-darwin11 os: ubuntu-18.04 packages: cmake imagemagick libcap-dev librsvg2-bin libz-dev libtiff-tools libtinfo5 python3-setuptools xorriso libtinfo5 run-tests: false + check-security: false + check-symbols: false dep-opts: "" - config-opts: "--enable-gui=qt5 --enable-reduce-exports" + config-opts: "--enable-gui=qt5 --disable-tests" goal: deploy sdk: 10.11 - name: x86_64-linux-experimental @@ -175,7 +200,7 @@ jobs: run: | depends/${{ matrix.host }}/native/bin/ccache --max-size=$CCACHE_SIZE ./autogen.sh - ./configure --prefix=`pwd`/depends/${{ matrix.host }} ${{ matrix.config-opts }} || ( cat config.log && false) + ./configure --prefix=`pwd`/depends/${{ matrix.host }} ${{ matrix.config-opts }} --enable-reduce-exports || ( cat config.log && false) make $MAKEJOBS ${{ matrix.goal }} || ( echo "Build failure. Verbose build follows." && make ${{ matrix.goal }} V=1 ; false ) - name: Run tests @@ -185,6 +210,14 @@ jobs: qa/pull-tester/install-deps.sh qa/pull-tester/rpc-tests.py --coverage + - name: Check security + if: ${{ matrix.check-security }} + run: make -C src check-security + + - name: Check symbols + if: ${{ matrix.check-symbols }} + run: make -C src check-symbols + - name: Upload artifacts uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 05e718c10..2e4224a22 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -21,7 +21,7 @@ on: jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 permissions: actions: read contents: read diff --git a/configure.ac b/configure.ac index cb1ef0f50..c929e6709 100644 --- a/configure.ac +++ b/configure.ac @@ -493,6 +493,8 @@ if test x$use_glibc_compat != xno; then [ fdelt_type="long int"]) AC_MSG_RESULT($fdelt_type) AC_DEFINE_UNQUOTED(FDELT_TYPE, $fdelt_type,[parameter and return value type for __fdelt_chk]) + AX_CHECK_LINK_FLAG([[-Wl,--wrap=__divmoddi4]], [COMPAT_LDFLAGS="$COMPAT_LDFLAGS -Wl,--wrap=__divmoddi4"]) + AX_CHECK_LINK_FLAG([[-Wl,--wrap=log2f]], [COMPAT_LDFLAGS="$COMPAT_LDFLAGS -Wl,--wrap=log2f"]) else AC_SEARCH_LIBS([clock_gettime],[rt]) fi @@ -1106,6 +1108,7 @@ AC_SUBST(BITCOIN_CLI_NAME) AC_SUBST(BITCOIN_TX_NAME) AC_SUBST(RELDFLAGS) +AC_SUBST(COMPAT_LDFLAGS) AC_SUBST(ERROR_CXXFLAGS) AC_SUBST(HARDENED_CXXFLAGS) AC_SUBST(HARDENED_CPPFLAGS) diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index 34ff16009..cee7ff269 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -48,7 +48,8 @@ MAX_VERSIONS = { # Ignore symbols that are exported as part of every executable IGNORE_EXPORTS = { -b'_edata', b'_end', b'_init', b'__bss_start', b'_fini', b'_IO_stdin_used' + b'_edata', b'_end', b'_init', b'__bss_start', b'_fini', b'_IO_stdin_used', + b'stdin', b'stdout', b'stderr' } READELF_CMD = os.getenv('READELF', '/usr/bin/readelf') CPPFILT_CMD = os.getenv('CPPFILT', '/usr/bin/c++filt') diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml index c43a28089..4458bb98a 100644 --- a/contrib/gitian-descriptors/gitian-linux.yml +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -2,21 +2,21 @@ name: "dogecoin-linux-1.14" enable_cache: true suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: - "curl" - "g++-aarch64-linux-gnu" -- "g++-4.8-aarch64-linux-gnu" -- "gcc-4.8-aarch64-linux-gnu" +- "g++-7-aarch64-linux-gnu" +- "gcc-7-aarch64-linux-gnu" - "binutils-aarch64-linux-gnu" - "g++-arm-linux-gnueabihf" -- "g++-4.8-arm-linux-gnueabihf" -- "gcc-4.8-arm-linux-gnueabihf" +- "g++-7-arm-linux-gnueabihf" +- "gcc-7-arm-linux-gnueabihf" - "binutils-arm-linux-gnueabihf" -- "g++-4.8-multilib" -- "gcc-4.8-multilib" +- "g++-7-multilib" +- "gcc-7-multilib" - "binutils-gold" - "git-core" - "pkg-config" diff --git a/contrib/gitian-descriptors/gitian-osx-signer.yml b/contrib/gitian-descriptors/gitian-osx-signer.yml index 2eb39e569..755f93eb2 100644 --- a/contrib/gitian-descriptors/gitian-osx-signer.yml +++ b/contrib/gitian-descriptors/gitian-osx-signer.yml @@ -1,7 +1,7 @@ --- name: "dogecoin-dmg-signer" suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: diff --git a/contrib/gitian-descriptors/gitian-osx.yml b/contrib/gitian-descriptors/gitian-osx.yml index e1ae624ed..f3b0346c5 100644 --- a/contrib/gitian-descriptors/gitian-osx.yml +++ b/contrib/gitian-descriptors/gitian-osx.yml @@ -2,7 +2,7 @@ name: "dogecoin-osx-1.14" enable_cache: true suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: diff --git a/contrib/gitian-descriptors/gitian-win-signer.yml b/contrib/gitian-descriptors/gitian-win-signer.yml index 9c11bc89f..8262e462b 100644 --- a/contrib/gitian-descriptors/gitian-win-signer.yml +++ b/contrib/gitian-descriptors/gitian-win-signer.yml @@ -1,7 +1,7 @@ --- name: "dogecoin-win-signer" suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: diff --git a/contrib/gitian-descriptors/gitian-win.yml b/contrib/gitian-descriptors/gitian-win.yml index 9ad12cc25..1b691c519 100644 --- a/contrib/gitian-descriptors/gitian-win.yml +++ b/contrib/gitian-descriptors/gitian-win.yml @@ -2,7 +2,7 @@ name: "dogecoin-win-1.14" enable_cache: true suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: @@ -21,6 +21,7 @@ packages: - "zip" - "ca-certificates" - "python" +- "rename" remotes: - "url": "https://github.com/dogecoin/dogecoin.git" "dir": "dogecoin" @@ -29,7 +30,7 @@ script: | WRAP_DIR=$HOME/wrapped HOSTS="i686-w64-mingw32 x86_64-w64-mingw32" CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests" - FAKETIME_HOST_PROGS="g++ ar ranlib nm windres strip objcopy" + FAKETIME_HOST_PROGS="ar ranlib nm windres strip objcopy" FAKETIME_PROGS="date makensis zip" HOST_CFLAGS="-O2 -g" HOST_CXXFLAGS="-O2 -g" @@ -70,21 +71,13 @@ script: | done } - function create_per-host_linker_wrapper { - # This is only needed for trusty, as the mingw linker leaks a few bytes of - # heap, causing non-determinism. See discussion in https://github.com/bitcoin/bitcoin/pull/6900 + function create_per-host_compiler_wrapper { + # -posix variant is required for c++11 threading. for i in $HOSTS; do mkdir -p ${WRAP_DIR}/${i} - for prog in collect2; do - echo '#!/bin/bash' > ${WRAP_DIR}/${i}/${prog} - REAL=$(${i}-gcc -print-prog-name=${prog}) - echo "export MALLOC_PERTURB_=255" >> ${WRAP_DIR}/${i}/${prog} - echo "${REAL} \$@" >> $WRAP_DIR/${i}/${prog} - chmod +x ${WRAP_DIR}/${i}/${prog} - done for prog in gcc g++; do echo '#!/bin/bash' > ${WRAP_DIR}/${i}-${prog} - echo "REAL=\`which -a ${i}-${prog} | grep -v ${WRAP_DIR}/${i}-${prog} | head -1\`" >> ${WRAP_DIR}/${i}-${prog} + echo "REAL=\`which -a ${i}-${prog}-posix | grep -v ${WRAP_DIR}/${i}-${prog} | head -1\`" >> ${WRAP_DIR}/${i}-${prog} echo 'export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1' >> ${WRAP_DIR}/${i}-${prog} echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${i}-${prog} echo "export COMPILER_PATH=${WRAP_DIR}/${i}" >> ${WRAP_DIR}/${i}-${prog} @@ -98,7 +91,7 @@ script: | export PATH_orig=${PATH} create_global_faketime_wrappers "2000-01-01 12:00:00" create_per-host_faketime_wrappers "2000-01-01 12:00:00" - create_per-host_linker_wrapper "2000-01-01 12:00:00" + create_per-host_compiler_wrapper "2000-01-01 12:00:00" export PATH=${WRAP_DIR}:${PATH} cd dogecoin @@ -112,7 +105,7 @@ script: | export PATH=${PATH_orig} create_global_faketime_wrappers "${REFERENCE_DATETIME}" create_per-host_faketime_wrappers "${REFERENCE_DATETIME}" - create_per-host_linker_wrapper "${REFERENCE_DATETIME}" + create_per-host_compiler_wrapper "${REFERENCE_DATETIME}" export PATH=${WRAP_DIR}:${PATH} # Create the release tarball using (arbitrarily) the first host diff --git a/depends/packages/freetype.mk b/depends/packages/freetype.mk index 76b025c46..41e02e203 100644 --- a/depends/packages/freetype.mk +++ b/depends/packages/freetype.mk @@ -5,7 +5,7 @@ $(package)_file_name=$(package)-$($(package)_version).tar.bz2 $(package)_sha256_hash=3a3bb2c4e15ffb433f2032f50a5b5a92558206822e22bfe8cbe339af4aa82f88 define $(package)_set_vars - $(package)_config_opts=--without-zlib --without-png --disable-static + $(package)_config_opts=--without-zlib --without-png --without-harfbuzz --without-bzip2 --disable-static $(package)_config_opts_linux=--with-pic endef diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 2f9227b7a..de558da58 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -73,6 +73,7 @@ $(package)_config_opts += -prefix $(host_prefix) $(package)_config_opts += -qt-libpng $(package)_config_opts += -qt-libjpeg $(package)_config_opts += -qt-pcre +$(package)_config_opts += -qt-harfbuzz $(package)_config_opts += -system-zlib $(package)_config_opts += -reduce-exports $(package)_config_opts += -static diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk index 0e76eb5a5..ac9d1eac5 100644 --- a/depends/packages/zeromq.mk +++ b/depends/packages/zeromq.mk @@ -4,7 +4,6 @@ $(package)_download_path=https://github.com/zeromq/libzmq/releases/download/v$($ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=c593001a89f5a85dd2ddf564805deb860e02471171b3f204944857336295c3e5 $(package)_patches=remove_libstd_link.patch clock-unused-nsecs.patch 0002-disable-pthread_set_name_np.patch -$(package)_patches+=trusty-add-win32-inethdr.patch define $(package)_set_vars $(package)_config_opts=--without-documentation --disable-shared --without-libsodium --disable-curve @@ -16,7 +15,6 @@ define $(package)_preprocess_cmds patch -p1 < $($(package)_patch_dir)/clock-unused-nsecs.patch && \ patch -p1 < $($(package)_patch_dir)/remove_libstd_link.patch && \ patch -p1 < $($(package)_patch_dir)/0002-disable-pthread_set_name_np.patch && \ - patch -p1 < $($(package)_patch_dir)/trusty-add-win32-inethdr.patch && \ cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub config endef diff --git a/depends/patches/zeromq/trusty-add-win32-inethdr.patch b/depends/patches/zeromq/trusty-add-win32-inethdr.patch deleted file mode 100644 index 65845a0f5..000000000 --- a/depends/patches/zeromq/trusty-add-win32-inethdr.patch +++ /dev/null @@ -1,11 +0,0 @@ -diff -dur a/src/windows.hpp b/src/windows.hpp ---- a/src/windows.hpp 2021-07-16 20:31:22.997078113 +0000 -+++ b/src/windows.hpp 2021-07-16 20:33:21.525281189 +0000 -@@ -52,6 +52,7 @@ - #endif - - #include -+#include - #include - #include - #include diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index c5528118e..8f4784836 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -154,7 +154,7 @@ testScripts = [ 'signmessages.py', # 'nulldummy.py', 'import-rescan.py', - 'harddustlimit.py', + 'dustlimits.py', 'paytxfee.py', 'feelimit.py', # While fee bumping should work in Doge, these tests depend on free transactions, which we don't support. @@ -164,6 +164,7 @@ testScripts = [ 'listsinceblock.py', 'p2p-leaktests.py', 'replace-by-fee.py', + 'p2p-policy.py', ] if ENABLE_ZMQ: testScripts.append('zmq_test.py') diff --git a/qa/rpc-tests/bumpfee.py b/qa/rpc-tests/bumpfee.py index b671d519f..2f3c65fb6 100755 --- a/qa/rpc-tests/bumpfee.py +++ b/qa/rpc-tests/bumpfee.py @@ -279,20 +279,32 @@ def test_locked_wallet_fails(rbf_node, dest_address): def test_dogecoin_wallet_minchange(rbf_node, dest_address): input = Decimal("10.00000000") - min_change = Decimal("0.03000000") - min_fee = Decimal("0.01000000") - bumpfee = Decimal("0.001") - est_tx_size = Decimal("0.193") + discard_threshold = Decimal("1.00000000") # DEFAULT_DISCARD_THRESHOLD + min_fee = Decimal("0.01000000") # DEFAULT_TRANSACTION_FEE + min_change = discard_threshold + 2 * min_fee # MIN_CHANGE + bumpfee = Decimal("0.001") # WALLET_INCREMENTAL_RELAY_FEE + est_tx_size = Decimal("0.226") # 1 in, 2 out + + # create a transaction with minimum fees destamount = input - min_change - min_fee * est_tx_size rbfid = spend_one_input(rbf_node, input, {dest_address: destamount, get_change_address(rbf_node): min_change}) + + # bump the fee with the default incremental fee; this should add 0.001 DOGE bumped_tx = rbf_node.bumpfee(rbfid) assert_equal(bumped_tx["fee"], min_fee * est_tx_size + bumpfee) - newfee = int((input - destamount - min_fee - bumpfee / 2 ) * 100000000) + + # bump the fee to only have a change output with the discard threshold + # plus half the incremental fee + newfee = int((input - destamount - discard_threshold - bumpfee / 2 ) * 100000000) bumped_tx = rbf_node.bumpfee(bumped_tx["txid"], {"totalFee": newfee}) - assert_equal(bumped_tx["fee"], input - destamount - min_fee - bumpfee / 2) + assert_equal(bumped_tx["fee"], input - destamount - discard_threshold - bumpfee / 2) + + # now bump with the default incremental fee again; as the resulting change + # output will be under the discard threshold, this must discard all change + # to fee bumped_tx = rbf_node.bumpfee(bumped_tx["txid"]) assert_equal(bumped_tx["fee"], input - destamount) rbf_node.settxfee(Decimal("0.00000000")) diff --git a/qa/rpc-tests/dustlimits.py b/qa/rpc-tests/dustlimits.py new file mode 100644 index 000000000..3ced377a3 --- /dev/null +++ b/qa/rpc-tests/dustlimits.py @@ -0,0 +1,166 @@ +#!/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. +"""Dust limit QA test. + +# Tests nodes with differing mempool/relay dust limits +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from decimal import Decimal + +class DustLimitTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 4 + + # set up receiving addresses outside of nodes' wallets + self.recv_1 = "n4LRQGEKcyRCXqD2MH3ompyMTJKitxu1WP" + self.recv_2 = "n1eAe5K2AQUtbmMxVzWnGAyq4hkWJdse2x" + + # seed moneys + self.seed = 100 + + def setup_nodes(self, split=False): + nodes = [] + + # 1.10.0-like node with only a soft dust limit + nodes.append(start_node(0, self.options.tmpdir, + ["-acceptnonstdtxn=0", "-dustlimit=1", "-harddustlimit=0.0", "-minrelaytxfee=1", "-debug"])) + + # 1.14.2-like node with only a hard dust limit + nodes.append(start_node(1, self.options.tmpdir, + ["-acceptnonstdtxn=0", "-dustlimit=1", "-harddustlimit=1", "-minrelaytxfee=1", "-debug"])) + + # 1.14.5-like node with a lower, different hard and soft dust limit + nodes.append(start_node(2, self.options.tmpdir, + ["-acceptnonstdtxn=0", "-dustlimit=0.01", "-harddustlimit=0.001", "-debug"])) + + # node that should accept everything + nodes.append(start_node(3, self.options.tmpdir, + ["-acceptnonstdtxn=0", "-dustlimit=0.0", "-harddustlimit=0.0", "-minrelaytxfee=0.00000001", "-debug"])) + + return nodes + + def setup_network(self, split = False): + self.nodes = self.setup_nodes() + + # connect everything to everything + for s in range(0, self.num_nodes): + for t in range(s+1, self.num_nodes): + connect_nodes_bi(self.nodes, s, t) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + + # set up 10 seeded addresses for node 0-2 + addrs = [] + for i in range(3): + for _ in range(10): + addrs.append(self.nodes[i].getnewaddress()) + + # mine some blocks and prepare some coins + self.nodes[2].generate(1) + self.sync_all() + self.nodes[0].generate(101) + self.sync_all() + for addr in addrs: + self.nodes[0].sendtoaddress(addr, self.seed) + self.nodes[0].generate(1) + self.sync_all() + + # create dusty transactions + + txids = [ + self.send_dusty_tx(self.nodes[1], Decimal("1"), Decimal("1")), # goes to all + self.send_dusty_tx(self.nodes[0], Decimal("0.9"), Decimal("2")), # goes to 3/4 + self.send_dusty_tx(self.nodes[0], Decimal("0.0009"), Decimal("2")), # goes to 3/4 + self.send_dusty_tx(self.nodes[2], Decimal("0.001"), Decimal("2")), # goes to 3/4 + self.send_dusty_tx(self.nodes[2], Decimal("0.001"), Decimal("0.02")), # goes to 2/4 + ] + + # nodes do not accept dust under their hard dust limit + # no matter how much fee is paid + self.get_dust_rejection(self.nodes[2], Decimal("0.0009"), Decimal("5")) + self.get_dust_rejection(self.nodes[1], Decimal("0.9"), Decimal("5")) + + # wait 15 seconds to sync mempools + time.sleep(15) + + assert_equal(self.nodes[0].getmempoolinfo()['size'], 4) # 4 of 5 + assert_equal(self.nodes[1].getmempoolinfo()['size'], 1) # 1 of 5 + assert_equal(self.nodes[2].getmempoolinfo()['size'], 4) # 4 of 5 + assert_equal(self.nodes[3].getmempoolinfo()['size'], 5) # all + + # check each tx + i = 0 + for checktx in [[0,1,2,3], [0], [0,1,3,4], [0,1,2,3,4]]: + for idx in checktx: + assert(txids[idx] in self.nodes[i].getrawmempool()) + i += 1 + + # mining the 1 tx known to node 1 + self.nodes[1].generate(1) + sync_blocks(self.nodes) + + assert_equal(self.nodes[0].getmempoolinfo()['size'], 3) # 3 of 4 + assert_equal(self.nodes[1].getmempoolinfo()['size'], 0) # none left + assert_equal(self.nodes[2].getmempoolinfo()['size'], 3) # 3 of 4 + assert_equal(self.nodes[3].getmempoolinfo()['size'], 4) # all + + # check each tx + i = 0 + for checktx in [[1,2,3], [], [1,3,4], [1,2,3,4]]: + for idx in checktx: + assert(txids[idx] in self.nodes[i].getrawmempool()) + i += 1 + + # mine the 3 tx known to node 0 + self.nodes[0].generate(1) + sync_blocks(self.nodes) + + # now only the last tx is left + assert_equal(self.nodes[0].getmempoolinfo()['size'], 0) # none left + assert_equal(self.nodes[1].getmempoolinfo()['size'], 0) # none left + assert_equal(self.nodes[2].getmempoolinfo()['size'], 1) # all + assert_equal(self.nodes[3].getmempoolinfo()['size'], 1) # all + + # check each tx on the remaining nodes + for i in [2,3]: + assert(txids[4] in self.nodes[i].getrawmempool()) + + # after mining the last tx from node 2, all nodes should have empty mempools + self.nodes[2].generate(1) + self.sync_all() + + assert_equal(self.nodes[0].getmempoolinfo()['size'], 0) + assert_equal(self.nodes[1].getmempoolinfo()['size'], 0) + assert_equal(self.nodes[2].getmempoolinfo()['size'], 0) + assert_equal(self.nodes[3].getmempoolinfo()['size'], 0) + + print("such success. wow!") + + def create_dusty_tx(self, n, dust, fee): + minAmount = 5 * (dust + fee) + avail = n.listunspent(0, 1000, [], True, {'minimumAmount': minAmount})[0] + inputs = [ {'txid': avail['txid'], 'vout': avail['vout']} ] + outputs = { self.recv_1 : avail['amount'] - fee - dust , self.recv_2: dust } + rawtx = n.createrawtransaction(inputs, outputs) + return n.signrawtransaction(rawtx) + + def send_dusty_tx(self, n, dust, fee): + rawtx = self.create_dusty_tx(n, dust, fee) + return n.sendrawtransaction(rawtx['hex']) + + def get_dust_rejection(self, n, dust, fee): + rawtx = self.create_dusty_tx(n, dust, fee) + assert_raises_jsonrpc(-26, "dust", n.sendrawtransaction, rawtx['hex']) + +if __name__ == '__main__': + DustLimitTest().main() diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index 9ae7933d0..972f0e8fb 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -200,7 +200,7 @@ class RawTransactionsTest(BitcoinTestFramework): utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ] - outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) } + outputs = { self.nodes[0].getnewaddress() : Decimal("3.9") } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawtx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) @@ -336,7 +336,7 @@ class RawTransactionsTest(BitcoinTestFramework): ############################################################ #compare fee of a standard pubkeyhash transaction with multiple outputs inputs = [] - outputs = {self.nodes[1].getnewaddress():1.1,self.nodes[1].getnewaddress():1.2,self.nodes[1].getnewaddress():0.1,self.nodes[1].getnewaddress():1.3,self.nodes[1].getnewaddress():0.2,self.nodes[1].getnewaddress():0.3} + outputs = {self.nodes[1].getnewaddress():1.1,self.nodes[1].getnewaddress():1.2,self.nodes[1].getnewaddress():1.1,self.nodes[1].getnewaddress():1.3,self.nodes[1].getnewaddress():1.2,self.nodes[1].getnewaddress():1.3} rawTx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawTx) #create same transaction over sendtoaddress @@ -507,14 +507,14 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() - for i in range(0,20): - self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + for i in range(0,22): + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1) self.nodes[0].generate(1) self.sync_all() - #fund a tx with ~20 small inputs + #fund a tx with ~20 small inputs, by spending in combination the 22 DOGE we just sent inputs = [] - outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04} + outputs = {self.nodes[0].getnewaddress():16.0,self.nodes[0].getnewaddress():5.9} rawTx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawTx) @@ -537,8 +537,8 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() - for i in range(0,20): - self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + for i in range(0,22): + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1) self.nodes[0].generate(1) self.sync_all() @@ -546,7 +546,7 @@ class RawTransactionsTest(BitcoinTestFramework): oldBalance = self.nodes[0].getbalance() inputs = [] - outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04} + outputs = {self.nodes[0].getnewaddress():16.0,self.nodes[0].getnewaddress():5.9} rawTx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawTx) fundedAndSignedTx = self.nodes[1].signrawtransaction(fundedTx['hex']) @@ -554,7 +554,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.sync_all() self.nodes[0].generate(1) self.sync_all() - assert_equal(oldBalance+Decimal('500000.19000'), self.nodes[0].getbalance()) #0.19+block reward + assert_equal(oldBalance+Decimal('500021.9000'), self.nodes[0].getbalance()) #2.19+block reward ##################################################### # test fundrawtransaction with OP_RETURN and no vin # @@ -677,7 +677,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(len(self.nodes[3].listunspent(1)), 1) inputs = [] - outputs = {self.nodes[2].getnewaddress(): 1} + outputs = {self.nodes[2].getnewaddress(): 2} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) result = [self.nodes[3].fundrawtransaction(rawtx), # uses min_relay_tx_fee (set by settxfee) @@ -700,7 +700,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(change[3] + result[3]['fee'], change[4]) inputs = [] - outputs = {self.nodes[2].getnewaddress(): value for value in (1.0, 1.1, 1.2, 1.3)} + outputs = {self.nodes[2].getnewaddress(): value for value in (1.05, 1.1, 1.2, 1.3)} keys = list(outputs.keys()) rawtx = self.nodes[3].createrawtransaction(inputs, outputs) diff --git a/qa/rpc-tests/harddustlimit.py b/qa/rpc-tests/harddustlimit.py deleted file mode 100644 index d7b46fd94..000000000 --- a/qa/rpc-tests/harddustlimit.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/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("0.001") - 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/qa/rpc-tests/importprunedfunds.py b/qa/rpc-tests/importprunedfunds.py index c909d7164..a0f2b0dee 100755 --- a/qa/rpc-tests/importprunedfunds.py +++ b/qa/rpc-tests/importprunedfunds.py @@ -58,13 +58,13 @@ class ImportPrunedFundsTest(BitcoinTestFramework): assert_equal(address_info['ismine'], False) #Send funds to self - txnid1 = self.nodes[0].sendtoaddress(address1, 0.1) + txnid1 = self.nodes[0].sendtoaddress(address1, 10) rawtxn1 = self.nodes[0].gettransaction(txnid1)['hex'] - txnid2 = self.nodes[0].sendtoaddress(address2, 0.05) + txnid2 = self.nodes[0].sendtoaddress(address2, 5) rawtxn2 = self.nodes[0].gettransaction(txnid2)['hex'] - txnid3 = self.nodes[0].sendtoaddress(address3, 0.025) + txnid3 = self.nodes[0].sendtoaddress(address3, 2.5) rawtxn3 = self.nodes[0].gettransaction(txnid3)['hex'] self.nodes[0].generate(1) @@ -85,15 +85,15 @@ class ImportPrunedFundsTest(BitcoinTestFramework): self.nodes[1].importaddress(address2, "add2", False) result2 = self.nodes[1].importprunedfunds(rawtxn2, proof2) balance2 = self.nodes[1].getbalance("add2", 0, True) - assert_equal(balance2, Decimal('0.05')) + assert_equal(balance2, Decimal('5.0')) #Import with private key with no rescan self.nodes[1].importprivkey(address3_privkey, "add3", False) result3 = self.nodes[1].importprunedfunds(rawtxn3, proof3) balance3 = self.nodes[1].getbalance("add3", 0, False) - assert_equal(balance3, Decimal('0.025')) + assert_equal(balance3, Decimal('2.5')) balance3 = self.nodes[1].getbalance("*", 0, True) - assert_equal(balance3, Decimal('0.075')) + assert_equal(balance3, Decimal('7.5')) #Addresses Test - after import address_info = self.nodes[1].validateaddress(address1) @@ -110,11 +110,11 @@ class ImportPrunedFundsTest(BitcoinTestFramework): assert_raises_jsonrpc(-8, "Transaction does not exist in wallet.", self.nodes[1].removeprunedfunds, txnid1) balance1 = self.nodes[1].getbalance("*", 0, True) - assert_equal(balance1, Decimal('0.075')) + assert_equal(balance1, Decimal('7.5')) self.nodes[1].removeprunedfunds(txnid2) balance2 = self.nodes[1].getbalance("*", 0, True) - assert_equal(balance2, Decimal('0.025')) + assert_equal(balance2, Decimal('2.5')) self.nodes[1].removeprunedfunds(txnid3) balance3 = self.nodes[1].getbalance("*", 0, True) diff --git a/qa/rpc-tests/p2p-policy.py b/qa/rpc-tests/p2p-policy.py new file mode 100644 index 000000000..0a2cdd311 --- /dev/null +++ b/qa/rpc-tests/p2p-policy.py @@ -0,0 +1,201 @@ +#!/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. +"""P2P Policies QA test + +# Tests relay and mempool acceptance policies from p2p perspective +""" + +from test_framework.mininode import * +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +class TestNode(NodeConnCB): + def __init__(self): + NodeConnCB.__init__(self) + self.connection = None + self.ping_counter = 1 + self.last_pong = msg_pong() + self.txinvs = {} + self.rejects = [] + + def add_connection(self, conn): + self.connection = conn + + # Track transaction invs for wait_for_tx_inv + def on_inv(self, conn, message): + for i in message.inv: + if (i.type == 1): + self.txinvs[format(i.hash, '064x')] = True + + # Track pongs for sync_with_ping + def on_pong(self, conn, message): + self.last_pong = message + + # Track reject messages + def on_reject(self, conn, message): + self.rejects.append(message) + + # wait for verack to make sure the node accepts our connection attempt + def wait_for_verack(self): + def veracked(): + return self.verack_received + return wait_until(veracked, timeout=10) + + # Wait until we have received an inv of a specific tx + def wait_for_tx_inv(self, hash, timeout=30): + def have_received_tx_inv(): + try: + return self.txinvs[hash] + except KeyError as e: + return False + return wait_until(have_received_tx_inv, timeout=timeout) + + # Send a ping message and wait until we get the pong message back + def sync_with_ping(self, timeout=30): + def received_pong(): + return (self.last_pong.nonce == self.ping_counter) + self.connection.send_message(msg_ping(nonce=self.ping_counter)) + success = wait_until(received_pong, timeout=timeout) + self.ping_counter += 1 + return success + +class P2PPolicyTests(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 + self.utxo = [] + + # a private key and corresponding address and p2pkh output script + self.srcPrivKey = "cRhVU6TU1qHfRg3ee59yqg7ifhREKPLPPk8eccrrAEEY74bY1dCY" + self.srcAddr = "mmMP9oKFdADezYzduwJFcLNmmi8JHUKdx9" + self.srcOutScript = "76a91440015860f45d48eeeb2224dce3ad94ba91763e1e88ac" + + # valid regtest address that no one has the key to + self.tgtAddr = "mkwDHkWXF8x6aFtdGVm5E9PVC7yPY8cb4r" + + def create_testnode(self, node_idx=0): + node = TestNode() + conn = NodeConn('127.0.0.1', p2p_port(node_idx), self.nodes[node_idx], node) + node.add_connection(conn) + return node + + def setup_network(self): + self.nodes = [] + + # a Dogecoin Core node that behaves similar to mainnet policies + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-acceptnonstdtxn=0"])) + + # custom testnodes + self.sendNode = self.create_testnode() # to send tx from + self.recvNode = self.create_testnode() # to check relay from + + # start networking and handshake the mininodes + NetworkThread().start() + self.sendNode.wait_for_verack() + self.recvNode.wait_for_verack() + + def run_test(self): + self.nodes[0].generate(101) + + ### test constants ### + koinu = Decimal("0.00000001") # 1 Koinu expressed in DOGE + ten = Decimal("10.0") # uniform 10 DOGE seed moneys + + ### parameters from fee policy ### + relay_fee = Decimal("0.001") # DEFAULT_MIN_RELAY_TX_FEE + soft_dust_limit = Decimal("0.01") # DEFAULT_DUST_LIMIT + + relay_fee_per_byte = relay_fee / 1000 + + # create a bunch of UTXO with seed money from the Dogecoin Core wallet + for i in range(10): + inputs = [self.nodes[0].listunspent()[0]] + outputs = { self.srcAddr : ten } + tx = self.nodes[0].createrawtransaction(inputs, outputs) + signed = self.nodes[0].signrawtransaction(tx) + txid = self.nodes[0].sendrawtransaction(signed['hex'], True) + self.utxo.append(txid) + self.nodes[0].generate(1) + + # test legacy output of 1 DOGE output and 1 DOGE fee + output = { self.tgtAddr : 1, self.srcAddr: 8 } + self.run_relay_test(output, None) + + # test exact relay fee rate + output = { self.tgtAddr: ten - relay_fee_per_byte * 192} + tx = self.run_relay_test(output, None) + + # test too low relay fee rate + output = { self.tgtAddr: ten - relay_fee_per_byte * 191 + koinu } + tx = self.run_relay_test(output, 66) # 66 = too low fee + + # test exact dust limit + change = ten - soft_dust_limit - relay_fee_per_byte * 226 + output = { self.tgtAddr : soft_dust_limit, self.srcAddr: change} + self.run_relay_test(output, None) + + # test soft dust limit with sufficient fee + amount = soft_dust_limit - koinu + change = ten - amount - relay_fee_per_byte * 226 - soft_dust_limit + output = { self.tgtAddr : amount, self.srcAddr: change } + self.run_relay_test(output, None) + + # test soft dust limit with insufficient fee + amount = soft_dust_limit - koinu + change = ten - amount - relay_fee_per_byte * 225 - soft_dust_limit + koinu + output = { self.tgtAddr : amount, self.srcAddr: change } + self.run_relay_test(output, 66) + + # test a 1 koinu output with sufficient fee + amount = koinu + change = ten - amount - relay_fee_per_byte * 226 - soft_dust_limit + output = { self.tgtAddr : amount, self.srcAddr: change } + self.run_relay_test(output, 64) # 64 = dust + + # test a 1 koinu output with insufficient fee + amount = koinu + change = ten - amount - relay_fee_per_byte * 225 - soft_dust_limit + koinu + output = { self.tgtAddr : amount, self.srcAddr: change } + self.run_relay_test(output, 64) + + + # test mempool acceptance and relay outcomes + def run_relay_test(self, output, expected_reject_code): + num_rejects = len(self.sendNode.rejects) + + tx = self.spend_utxo(output) + self.sendNode.sync_with_ping(timeout=10) + + if (expected_reject_code is None): + # test that the tx got relayed + assert_equal(self.recvNode.wait_for_tx_inv(tx.hash), True) + assert_equal(len(self.sendNode.rejects), num_rejects) + else: + # test that there was a rejection received with the correct code + assert_greater_than(len(self.sendNode.rejects), num_rejects) + assert_equal(self.sendNode.rejects[-1].code, expected_reject_code) + + return tx + + # spend seed money with a key not in the Dogecoin Core wallet. + def spend_utxo(self, output): + # construct the transaction using Dogecoin Core raw tx APIs + input = [{ "txid": self.utxo.pop(), "vout": 0, "scriptPubKey": self.srcOutScript }] + rawtx = self.nodes[0].createrawtransaction(input, output) + signed_tx = self.nodes[0].signrawtransaction(rawtx, input, [self.srcPrivKey]) + + # import the signed tx into a format the mininode client understands + # and send the tx from there rather than from Dogecoin Core, to test + # mempool acceptance as it would happen on mainnet: through relay + tx = FromHex(CTransaction(), signed_tx['hex']) + tx.rehash() + self.sendNode.connection.send_message(msg_tx(tx)) + + return tx + +if __name__ == '__main__': + P2PPolicyTests().main() diff --git a/src/Makefile.am b/src/Makefile.am index f438235ee..2e7ebb299 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -356,7 +356,10 @@ libdogecoin_util_a_SOURCES = \ $(BITCOIN_CORE_H) if GLIBC_BACK_COMPAT + libdogecoin_util_a_SOURCES += compat/glibc_compat.cpp +AM_LDFLAGS += $(COMPAT_LDFLAGS) + endif # cli: shared between bitcoin-cli and bitcoin-qt diff --git a/src/compat/glibc_compat.cpp b/src/compat/glibc_compat.cpp index 3b9c70df7..0815f530c 100644 --- a/src/compat/glibc_compat.cpp +++ b/src/compat/glibc_compat.cpp @@ -7,6 +7,7 @@ #endif #include +#include #if defined(HAVE_SYS_SELECT_H) #include @@ -27,3 +28,47 @@ extern "C" FDELT_TYPE __fdelt_warn(FDELT_TYPE a) return a / __NFDBITS; } extern "C" FDELT_TYPE __fdelt_chk(FDELT_TYPE) __attribute__((weak, alias("__fdelt_warn"))); + +#if defined(__i386__) || defined(__arm__) + +extern "C" int64_t __udivmoddi4(uint64_t u, uint64_t v, uint64_t* rp); + +extern "C" int64_t __wrap___divmoddi4(int64_t u, int64_t v, int64_t* rp) +{ + int32_t c1 = 0, c2 = 0; + int64_t uu = u, vv = v; + int64_t w; + int64_t r; + + if (uu < 0) { + c1 = ~c1, c2 = ~c2, uu = -uu; + } + if (vv < 0) { + c1 = ~c1, vv = -vv; + } + + w = __udivmoddi4(uu, vv, (uint64_t*)&r); + if (c1) + w = -w; + if (c2) + r = -r; + + *rp = r; + return w; +} +#endif + +extern "C" float log2f_old(float x); +#ifdef __i386__ +__asm(".symver log2f_old,log2f@GLIBC_2.1"); +#elif defined(__amd64__) +__asm(".symver log2f_old,log2f@GLIBC_2.2.5"); +#elif defined(__arm__) +__asm(".symver log2f_old,log2f@GLIBC_2.4"); +#elif defined(__aarch64__) +__asm(".symver log2f_old,log2f@GLIBC_2.17"); +#endif +extern "C" float __wrap_log2f(float x) +{ + return log2f_old(x); +} diff --git a/src/dogecoin-fees.cpp b/src/dogecoin-fees.cpp index 28e25b5a0..bf8affe88 100644 --- a/src/dogecoin-fees.cpp +++ b/src/dogecoin-fees.cpp @@ -92,7 +92,7 @@ CAmount GetDogecoinMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool } CAmount nMinFee = ::minRelayTxFeeRate.GetFee(nBytes); - nMinFee += GetDogecoinDustFee(tx.vout, ::minRelayTxFeeRate); + nMinFee += GetDogecoinDustFee(tx.vout, nDustLimit); if (fAllowFree) { @@ -109,13 +109,14 @@ CAmount GetDogecoinMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool return nMinFee; } -CAmount GetDogecoinDustFee(const std::vector &vout, CFeeRate &baseFeeRate) { +CAmount GetDogecoinDustFee(const std::vector &vout, const CAmount dustLimit) { CAmount nFee = 0; - // To limit dust spam, add base fee for each output less than a COIN + // To limit dust spam, add the dust limit for each output + // less than the (soft) dustlimit BOOST_FOREACH(const CTxOut& txout, vout) - if (txout.IsDust(::minRelayTxFeeRate)) - nFee += baseFeeRate.GetFeePerK(); + if (txout.IsDust(dustLimit)) + nFee += dustLimit; return nFee; } diff --git a/src/dogecoin-fees.h b/src/dogecoin-fees.h index b8f98b69b..c8eab7b0a 100644 --- a/src/dogecoin-fees.h +++ b/src/dogecoin-fees.h @@ -28,6 +28,6 @@ CFeeRate GetDogecoinWalletFeeRate(); CAmount GetDogecoinMinWalletFee(unsigned int nBytes_); #endif // ENABLE_WALLET CAmount GetDogecoinMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree); -CAmount GetDogecoinDustFee(const std::vector &vout, CFeeRate &baseFeeRate); +CAmount GetDogecoinDustFee(const std::vector &vout, const CAmount dustLimit); #endif // BITCOIN_DOGECOIN_FEES_H diff --git a/src/init.cpp b/src/init.cpp index dd1942133..ebfe30960 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -473,9 +473,9 @@ std::string HelpMessage(HelpMessageMode mode) if (showDebug) { strUsage += HelpMessageOpt("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !Params(CBaseChainParams::TESTNET).RequireStandard())); 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("-harddustlimit=", strprintf(_("Amount under which a transaction output is considered non-standard and will not be accepted or relayed, in %s (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_HARD_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)); @@ -1029,29 +1029,34 @@ bool AppInitParameterInteraction() return InitError(AmountErrMsg("blockmintxfee", GetArg("-blockmintxfee", ""))); } - // Feerate used to define dust. Shouldn't be changed lightly as old - // implementations may inadvertently create non-standard transactions - if (IsArgSet("-dustrelayfee")) - { - CAmount n = 0; - if (!ParseMoney(GetArg("-dustrelayfee", ""), n) || 0 == n) - return InitError(AmountErrMsg("dustrelayfee", GetArg("-dustrelayfee", ""))); - dustRelayFee = CFeeRate(n); - } - fRequireStandard = !GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard()); if (chainparams.RequireStandard() && !fRequireStandard) return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString())); nBytesPerSigOp = GetArg("-bytespersigop", nBytesPerSigOp); + if (IsArgSet("-harddustlimit")) + { + CAmount n = nHardDustLimit; + if (!ParseMoney(GetArg("-harddustlimit", ""), n)) + return InitError(AmountErrMsg("harddustlimit", GetArg("-harddustlimit", ""))); + nHardDustLimit = n; + } + if (IsArgSet("-dustlimit")) { CAmount n = nDustLimit; if (!ParseMoney(GetArg("-dustlimit", ""), n)) return InitError(AmountErrMsg("dustlimit", GetArg("-dustlimit", ""))); + nDustLimit = n; } + if (nDustLimit < nHardDustLimit) + { + nDustLimit = nHardDustLimit; + LogPrintf("Increasing -dustlimit to %s to match -harddustlimit\n", FormatMoney(nHardDustLimit)); + } + #ifdef ENABLE_WALLET if (!CWallet::ParameterInteraction()) return false; @@ -1347,7 +1352,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) if (!mapMultiArgs.count("-bind") && !mapMultiArgs.count("-whitebind")) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; - fBound |= Bind(connman, CService(in6addr_any, GetListenPort()), BF_NONE); + fBound |= Bind(connman, CService((in6_addr)IN6ADDR_ANY_INIT, GetListenPort()), BF_NONE); fBound |= Bind(connman, CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE); } if (!fBound) diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 506eb6f17..16d0f6fd1 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -105,7 +105,7 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnes else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) { reason = "bare-multisig"; return false; - } else if (txout.IsDust(dustRelayFee)) { + } else if (txout.IsDust(nHardDustLimit)) { reason = "dust"; return false; } @@ -207,9 +207,9 @@ 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; CAmount nDustLimit = DEFAULT_DUST_LIMIT; +CAmount nHardDustLimit = DEFAULT_HARD_DUST_LIMIT; int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost) { diff --git a/src/policy/policy.h b/src/policy/policy.h index 619fab69a..4b1b175d5 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -61,18 +61,23 @@ static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS = 100; static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE = 80; /** The maximum size of a standard witnessScript */ static const unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600; -/** Min feerate for defining dust. Historically this has been the same as the - * minRelayTxFee, however changing the dust limit changes which transactions are - * standard and should be done with care and ideally rarely. It makes sense to - * only increase the dust limit after prior releases were already not creating - * outputs below the new threshold */ -static const CAmount DUST_RELAY_TX_FEE = RECOMMENDED_MIN_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 CAmount DEFAULT_DUST_LIMIT = RECOMMENDED_MIN_TX_FEE; +/** + * Dogecoin: Default hard dust limit that is evaluated when considering whether + * a transaction is standard. Transactions under this limit will not be accepted + * to the mempool and thus not relayed. Can be overridden by -harddustlimit + * + * Changing the hard dust limit changes which transactions are standard and + * should be done with care and ideally rarely. It makes sense to only increase + * this limit after prior releases were already not creating outputs below the + * new threshold + */ +static const CAmount DEFAULT_HARD_DUST_LIMIT = DEFAULT_DUST_LIMIT / 10; /** * Standard script verification flags that standard transactions will comply @@ -125,6 +130,7 @@ extern CFeeRate incrementalRelayFee; extern CFeeRate dustRelayFee; extern unsigned int nBytesPerSigOp; extern CAmount nDustLimit; +extern CAmount nHardDustLimit; /** 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 63e34697a..26c12a83c 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -206,6 +206,15 @@ public: return (nValue < GetDustThreshold(minRelayTxFeeRate)); } + // Dogecoin: allow comparison against different dustlimit parameters + bool IsDust(const CAmount dustLimit) const + { + if (scriptPubKey.IsUnspendable()) + return false; + + return (nValue < dustLimit); + } + friend bool operator==(const CTxOut& a, const CTxOut& b) { return (a.nValue == b.nValue && diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 251944bd4..99ff54e89 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -433,7 +433,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) { CTxOut txout(amount, (CScript)std::vector(24, 0)); txDummy.vout.push_back(txout); - if (txout.IsDust(dustRelayFee)) + if (txout.IsDust(CWallet::discardThreshold)) fDust = true; } } @@ -543,13 +543,13 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) nChange -= nPayFee; // Never create dust outputs; if we would, just add the dust to the fee. - if (nChange > 0 && nChange < MIN_CHANGE) + if (nChange > 0 && nChange < CWallet::GetMinChange()) { CTxOut txout(nChange, (CScript)std::vector(24, 0)); - if (txout.IsDust(dustRelayFee)) + if (txout.IsDust(CWallet::discardThreshold)) { if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust - nChange = txout.GetDustThreshold(dustRelayFee); + nChange = CWallet::discardThreshold; else { nPayFee += nChange; diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 71fe49ae5..6365186d1 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -18,6 +18,10 @@ #include "script/standard.h" #include "util.h" +#ifdef ENABLE_WALLET +#include "wallet/wallet.h" +#endif + #ifdef WIN32 #ifdef _WIN32_WINNT #undef _WIN32_WINNT @@ -248,13 +252,15 @@ QString formatBitcoinURI(const SendCoinsRecipient &info) return ret; } +#ifdef ENABLE_WALLET bool isDust(const QString& address, const CAmount& amount) { CTxDestination dest = CBitcoinAddress(address.toStdString()).Get(); CScript script = GetScriptForDestination(dest); CTxOut txOut(amount, script); - return txOut.IsDust(dustRelayFee); + return txOut.IsDust(CWallet::discardThreshold); } +#endif QString HtmlEscape(const QString& str, bool fMultiLine) { diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 5ac303125..70ca2d509 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -51,8 +51,11 @@ namespace GUIUtil bool parseBitcoinURI(QString uri, SendCoinsRecipient *out); QString formatBitcoinURI(const SendCoinsRecipient &info); +//Dogecoin: need wallet to establish dust from a wallet perspective +#ifdef ENABLE_WALLET // Returns true if given address+amount meets "dust" definition bool isDust(const QString& address, const CAmount& amount); +#endif // HTML escaping for rich text controls QString HtmlEscape(const QString& str, bool fMultiLine=false); diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index edcb9fdcd..84ea24b6f 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -580,8 +580,8 @@ bool PaymentServer::processPaymentRequest(const PaymentRequestPlus& request, Sen // Extract and check amounts CTxOut txOut(sendingTo.second, sendingTo.first); - if (txOut.IsDust(dustRelayFee)) { - Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).") + if (txOut.IsDust(CWallet::discardThreshold)) { + Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (below discard threshold).") .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)), CClientUIInterface::MSG_ERROR); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 9124c2ff3..2c195dafb 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -681,35 +681,53 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vin[0].prevout.n = 1; t.vin[0].scriptSig << std::vector(65, 0); t.vout.resize(1); - t.vout[0].nValue = COIN; + CKey key; key.MakeNewKey(true); t.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID()); std::string reason; + + // Standard: 1 DOGE + t.vout[0].nValue = COIN; BOOST_CHECK(IsStandardTx(t, reason)); - // Dogecoin: Dust is totally different in Dogecoin, disable these tests - // Check dust with default relay fee: - /* CAmount nDustThreshold = 182 * dustRelayFee.GetFeePerK()/1000 * 3; - BOOST_CHECK_EQUAL(nDustThreshold, 546); - // dust: - t.vout[0].nValue = nDustThreshold - 1; + // Non-standard (1 koinu): + t.vout[0].nValue = 1; BOOST_CHECK(!IsStandardTx(t, reason)); - // not dust: - t.vout[0].nValue = nDustThreshold; + + // Non-standard (below hard dust): + t.vout[0].nValue = nHardDustLimit - 1; + BOOST_CHECK(!IsStandardTx(t, reason)); + + // Standard (at hard dust): + t.vout[0].nValue = nHardDustLimit; BOOST_CHECK(IsStandardTx(t, reason)); - // Check dust with odd relay fee to verify rounding: - // nDustThreshold = 182 * 1234 / 1000 * 3 - dustRelayFee = CFeeRate(1234); - // dust: - t.vout[0].nValue = 672 - 1; + // Standard (below soft dust but above hard): + t.vout[0].nValue = nDustLimit - 1; + BOOST_CHECK(IsStandardTx(t, reason)); + + // Standard (at soft dust): + t.vout[0].nValue = nDustLimit; + BOOST_CHECK(IsStandardTx(t, reason)); + + // Lowering limits: + CAmount nPrevHardDustLimit = nHardDustLimit; + CAmount nPrevDustLimit = nDustLimit; + nHardDustLimit = nHardDustLimit / 2; + nDustLimit = nPrevHardDustLimit; + + // Standard: + t.vout[0].nValue = nDustLimit - 1; + BOOST_CHECK(IsStandardTx(t, reason)); + + // Non-standard: + t.vout[0].nValue = nHardDustLimit - 1; BOOST_CHECK(!IsStandardTx(t, reason)); - // not dust: - t.vout[0].nValue = 672; - BOOST_CHECK(IsStandardTx(t, reason)); */ - dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); + + nHardDustLimit = nPrevHardDustLimit; + nDustLimit = nPrevDustLimit; t.vout[0].scriptPubKey = CScript() << OP_1; BOOST_CHECK(!IsStandardTx(t, reason)); diff --git a/src/validation.h b/src/validation.h index a3795fbf2..f5f0e5bb1 100644 --- a/src/validation.h +++ b/src/validation.h @@ -59,10 +59,13 @@ static const CAmount DEFAULT_MIN_RELAY_TX_FEE = RECOMMENDED_MIN_TX_FEE / 10; //! -maxtxfee default //rnicoll: 8/2021 scaled down as recommended fee is lowered static const CAmount DEFAULT_TRANSACTION_MAXFEE = RECOMMENDED_MIN_TX_FEE * 10000; + //! Discourage users to set fees higher than this amount (in satoshis) per kB -//mlumin: 5/2021 adjusted downward for fee revisions -//rnicoll: 8/2021 scale further down as recommended fee is lowered -static const CAmount HIGH_TX_FEE_PER_KB = RECOMMENDED_MIN_TX_FEE * 100; +/* Dogecoin: Set the high tx fee to be higher than the default values + * implemented by the wallet. + */ +static const CAmount HIGH_TX_FEE_PER_KB = RECOMMENDED_MIN_TX_FEE * 1000; + //! -maxtxfee will warn if called with a higher fee than this amount (in satoshis) //mlumin: 5/2021 adjusted max upward in terms of coin static const CAmount HIGH_MAX_TX_FEE = 100 * HIGH_TX_FEE_PER_KB; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 3a6c48dd7..fc32966ce 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2947,7 +2947,7 @@ UniValue bumpfee(const JSONRPCRequest& request) // If the output would become dust, discard it (converting the dust to fee) poutput->nValue -= nDelta; - if (poutput->nValue <= poutput->GetDustThreshold(::dustRelayFee)) { + if (poutput->nValue <= CWallet::discardThreshold) { LogPrint("rpc", "Bumping fee and discarding dust output\n"); nNewFee += poutput->nValue; tx.vout.erase(tx.vout.begin() + nOutput); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index a5ea0dc96..b79c33798 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -93,139 +93,139 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // with an empty wallet we can't even pay one coin BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); - add_coin(1*COIN, 4); // add a new 1 coin output + add_coin(10*COIN, 4); // add a new 10 coin output - // with only a new 1 coin output, we still can't find a mature 1 coin output - BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + // with only a new 1 coin output, we still can't find a mature 10 coin output + BOOST_CHECK(!wallet.SelectCoinsMinConf( 10 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); - // but we can find a new 1 coin output - BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); + // but we can find a new 10 coin output + BOOST_CHECK( wallet.SelectCoinsMinConf( 10 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 10 * COIN); - add_coin(2*COIN); // add a mature 2 coin output + add_coin(20*COIN); // add a mature 20 coin output - // we can't make 3 coins of mature outputs - BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + // we can't make 30 coins of mature outputs + BOOST_CHECK(!wallet.SelectCoinsMinConf( 30 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); - // we can make 3 coin of new outputs - BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 3 * COIN); + // we can make 30 coin of new outputs + BOOST_CHECK( wallet.SelectCoinsMinConf( 30 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 30 * COIN); - add_coin(5*COIN); // add a mature 5 coin output, - add_coin(10*COIN, 3, true); // a new 10 coin output sent from one of our own addresses - add_coin(20*COIN); // and a mature 20 coin output + add_coin(50*COIN); // add a mature 50 coin output, + add_coin(100*COIN, 3, true); // a new 100 coin output sent from one of our own addresses + add_coin(200*COIN); // and a mature 200 coin output - // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 + // now we have new: 10+100=110 (of which 100 was self-sent), and mature: 20+50+200=270. total = 380 - // we can't make 38 coins only if we disallow new output: - BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); - // we can't even make 37 coins if we don't allow new output even if they're from us - BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * COIN, 6, 6, 0, vCoins, setCoinsRet, nValueRet)); - // but we can make 37 coins if we accept new output from ourself - BOOST_CHECK( wallet.SelectCoinsMinConf(37 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 37 * COIN); - // and we can make 38 coins if we accept all new output - BOOST_CHECK( wallet.SelectCoinsMinConf(38 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 38 * COIN); + // we can't make 380 coins only if we disallow new output: + BOOST_CHECK(!wallet.SelectCoinsMinConf(380 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + // we can't even make 370 coins if we don't allow new output even if they're from us + BOOST_CHECK(!wallet.SelectCoinsMinConf(380 * COIN, 6, 6, 0, vCoins, setCoinsRet, nValueRet)); + // but we can make 370 coins if we accept new output from ourself + BOOST_CHECK( wallet.SelectCoinsMinConf(370 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 370 * COIN); + // and we can make 380 coins if we accept all new output + BOOST_CHECK( wallet.SelectCoinsMinConf(380 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 380 * COIN); - // try making 34 coins from 1,2,5,10,20 - we can't do it exactly - BOOST_CHECK( wallet.SelectCoinsMinConf(34 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 35 * COIN); // but 35 coins is closest - BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible) + // try making 340 coins from 10,20,50,100,200 - we can't do it exactly + BOOST_CHECK( wallet.SelectCoinsMinConf(340 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 350 * COIN); // but 350 coins is closest + BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 200+100+50. it's incredibly unlikely the 10 or 20 got included (but possible) - // when we try making 7 coins, the smaller outputs (1,2,5) are enough. We should see just 2+5 - BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 7 * COIN); + // when we try making 70 coins, the smaller outputs (10,20,50) are enough. We should see just 20+50 + BOOST_CHECK( wallet.SelectCoinsMinConf( 70 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 70 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); - // when we try making 8 coins, the smaller outputs (1,2,5) are exactly enough. - BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK(nValueRet == 8 * COIN); + // when we try making 80 coins, the smaller outputs (10,20,50) are exactly enough. + BOOST_CHECK( wallet.SelectCoinsMinConf( 80 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(nValueRet == 80 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); - // when we try making 9 coins, no subset of smaller outputs is enough, and we get the next bigger output (10) - BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 10 * COIN); + // when we try making 90 coins, no subset of smaller outputs is enough, and we get the next bigger output (100) + BOOST_CHECK( wallet.SelectCoinsMinConf( 90 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 100 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin empty_wallet(); - add_coin( 6*COIN); - add_coin( 7*COIN); - add_coin( 8*COIN); - add_coin(20*COIN); - add_coin(30*COIN); // now we have 6+7+8+20+30 = 71 coins total + add_coin( 60*COIN); + add_coin( 70*COIN); + add_coin( 80*COIN); + add_coin(200*COIN); + add_coin(300*COIN); // now we have 60+70+80+200+300 = 710 coins total - // check that we have 71 and not 72 - BOOST_CHECK( wallet.SelectCoinsMinConf(71 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + // check that we have 710 and not 711 + BOOST_CHECK( wallet.SelectCoinsMinConf(710 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!wallet.SelectCoinsMinConf(711 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - // now try making 16 coins. the best smaller outputs can do is 6+7+8 = 21; not as good at the next biggest output, 20 - BOOST_CHECK( wallet.SelectCoinsMinConf(16 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 20 * COIN); // we should get 20 in one output + // now try making 160 coins. the best smaller outputs can do is 60+70+80 = 210; not as good at the next biggest output, 200 + BOOST_CHECK( wallet.SelectCoinsMinConf(160 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 200 * COIN); // we should get 200 in one output BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); - add_coin( 5*COIN); // now we have 5+6+7+8+20+30 = 75 coins total + add_coin( 50*COIN); // now we have 50+60+70+80+200+300 = 750 coins total - // now if we try making 16 coins again, the smaller outputs can make 5+6+7 = 18 coins, better than the next biggest output, 20 - BOOST_CHECK( wallet.SelectCoinsMinConf(16 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 18 * COIN); // we should get 18 in 3 outputs + // now if we try making 160 coins again, the smaller outputs can make 50+60+70 = 18 coins, better than the next biggest output, 200 + BOOST_CHECK( wallet.SelectCoinsMinConf(160 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 180 * COIN); // we should get 180 in 3 outputs BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); - add_coin( 18*COIN); // now we have 5+6+7+8+18+20+30 + add_coin( 180*COIN); // now we have 50+60+70+80+180+200+300 - // and now if we try making 16 coins again, the smaller outputs can make 5+6+7 = 18 coins, the same as the next biggest output, 18 - BOOST_CHECK( wallet.SelectCoinsMinConf(16 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 18 * COIN); // we should get 18 in 1 output + // and now if we try making 16 coins again, the smaller outputs can make 50+60+70 = 180 coins, the same as the next biggest output, 180 + BOOST_CHECK( wallet.SelectCoinsMinConf(160 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 180 * COIN); // we should get 180 in 1 output BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest output wins - // now try making 11 coins. we should get 5+6 - BOOST_CHECK( wallet.SelectCoinsMinConf(11 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 11 * COIN); + // now try making 110 coins. we should get 50+60 + BOOST_CHECK( wallet.SelectCoinsMinConf(110 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 110 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // check that the smallest bigger output is used - add_coin( 100*COIN); - add_coin( 200*COIN); - add_coin( 300*COIN); - add_coin( 400*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 coins - BOOST_CHECK( wallet.SelectCoinsMinConf(95 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 100 * COIN); // we should get 100 coins in 1 output + add_coin( 1000*COIN); + add_coin( 2000*COIN); + add_coin( 3000*COIN); + add_coin( 4000*COIN); // now we have 50+60+70+80+180+200+300+1000+2000+3000+4000 = 10940 coins + BOOST_CHECK( wallet.SelectCoinsMinConf(950 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1000 * COIN); // we should get 1000 coins in 1 output BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); - BOOST_CHECK( wallet.SelectCoinsMinConf(195 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 200 * COIN); // we should get 200 coins in 1 output + BOOST_CHECK( wallet.SelectCoinsMinConf(1950 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 2000 * COIN); // we should get 2000 coins in 1 output BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // empty the wallet and start again, now with fractions of a coin, to test small change avoidance empty_wallet(); - add_coin(MIN_CHANGE * 1 / 10); - add_coin(MIN_CHANGE * 2 / 10); - add_coin(MIN_CHANGE * 3 / 10); - add_coin(MIN_CHANGE * 4 / 10); - add_coin(MIN_CHANGE * 5 / 10); + add_coin(CWallet::GetMinChange() * 1 / 10); + add_coin(CWallet::GetMinChange() * 2 / 10); + add_coin(CWallet::GetMinChange() * 3 / 10); + add_coin(CWallet::GetMinChange() * 4 / 10); + add_coin(CWallet::GetMinChange() * 5 / 10); - // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE - // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly - BOOST_CHECK( wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); + // try making 1 * GetMinChange() from the 1.5 * GetMinChange() + // we'll get change smaller than GetMinChange() whatever happens, so can expect GetMinChange() exactly + BOOST_CHECK( wallet.SelectCoinsMinConf(CWallet::GetMinChange(), 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CWallet::GetMinChange()); // but if we add a bigger output, small change is avoided - add_coin(1111*MIN_CHANGE); + add_coin(1111*CWallet::GetMinChange()); // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount + BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CWallet::GetMinChange(), 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CWallet::GetMinChange()); // we should get the exact amount // if we add more small output: - add_coin(MIN_CHANGE * 6 / 10); - add_coin(MIN_CHANGE * 7 / 10); + add_coin(CWallet::GetMinChange() * 6 / 10); + add_coin(CWallet::GetMinChange() * 7 / 10); - // and try again to make 1.0 * MIN_CHANGE - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount + // and try again to make 1.0 * GetMinChange() + BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CWallet::GetMinChange(), 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CWallet::GetMinChange()); // we should get the exact amount // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) // they tried to consolidate 10 50k outputs into one 500k output, and ended up with 50k in change @@ -237,43 +237,43 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten outputs - // if there's not enough in the smaller coins to make at least 1 * MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0), + // if there's not enough in the smaller coins to make at least 1 * GetMinChange() change (0.5+0.6+0.7 < 1.0+1.0), // we need to try finding an exact subset anyway // sometimes it will fail, and so we use the next biggest output: empty_wallet(); - add_coin(MIN_CHANGE * 5 / 10); - add_coin(MIN_CHANGE * 6 / 10); - add_coin(MIN_CHANGE * 7 / 10); - add_coin(1111 * MIN_CHANGE); - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger output + add_coin(CWallet::GetMinChange() * 5 / 10); + add_coin(CWallet::GetMinChange() * 6 / 10); + add_coin(CWallet::GetMinChange() * 7 / 10); + add_coin(1111 * CWallet::GetMinChange()); + BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CWallet::GetMinChange(), 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1111 * CWallet::GetMinChange()); // we get the bigger output BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0) empty_wallet(); - add_coin(MIN_CHANGE * 4 / 10); - add_coin(MIN_CHANGE * 6 / 10); - add_coin(MIN_CHANGE * 8 / 10); - add_coin(1111 * MIN_CHANGE); - BOOST_CHECK( wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount + add_coin(CWallet::GetMinChange() * 4 / 10); + add_coin(CWallet::GetMinChange() * 6 / 10); + add_coin(CWallet::GetMinChange() * 8 / 10); + add_coin(1111 * CWallet::GetMinChange()); + BOOST_CHECK( wallet.SelectCoinsMinConf(CWallet::GetMinChange(), 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CWallet::GetMinChange()); // we should get the exact amount BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two outputs 0.4+0.6 // test avoiding small change empty_wallet(); - add_coin(MIN_CHANGE * 5 / 100); - add_coin(MIN_CHANGE * 1); - add_coin(MIN_CHANGE * 100); + add_coin(CWallet::GetMinChange() * 5 / 100); + add_coin(CWallet::GetMinChange() * 1); + add_coin(CWallet::GetMinChange() * 100); // trying to make 100.01 from these three outputs - BOOST_CHECK(wallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all outputs + BOOST_CHECK(wallet.SelectCoinsMinConf(CWallet::GetMinChange() * 10001 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CWallet::GetMinChange() * 10105 / 100); // we should get all outputs BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // but if we try to make 99.9, we should take the bigger of the two small outputs to avoid small change - BOOST_CHECK(wallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE); + BOOST_CHECK(wallet.SelectCoinsMinConf(CWallet::GetMinChange() * 9990 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 101 * CWallet::GetMinChange()); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // test with many inputs @@ -283,9 +283,9 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) for (uint16_t j = 0; j < 676; j++) add_coin(amt); BOOST_CHECK(wallet.SelectCoinsMinConf(20*CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - if (amt - 20*CENT < MIN_CHANGE) { + if (amt - 20*CENT < CWallet::GetMinChange()) { // needs more than one input: - uint16_t returnSize = std::ceil((20.0 * CENT + MIN_CHANGE)/amt); + uint16_t returnSize = std::ceil((20.0 * CENT + CWallet::GetMinChange())/amt); CAmount returnValue = amt * returnSize; BOOST_CHECK_EQUAL(nValueRet, returnValue); BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize); @@ -522,15 +522,17 @@ BOOST_AUTO_TEST_CASE(GetMinimumFee_dust_test) CAmount nMinTxFee = COIN / 100; // Confirm dust penalty fees are added on - CAmount nDustPenalty = COIN / 100; + // Because this is ran by the wallet, this takes the discardThreshold, + // not the dust limit + CAmount nDustPenalty = COIN; BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 963, 0, pool), nDustPenalty + (nMinTxFee * 0.963)); BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 1000, 0, pool), nDustPenalty + (nMinTxFee * 1.000)); BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 1999, 0, pool), nDustPenalty + (nMinTxFee * 1.999)); - // change the hard dust limit + // change the discard threshold - nDustLimit = COIN / 1000; + CWallet::discardThreshold = COIN / 1000; // Confirm dust penalty fees are not added @@ -538,7 +540,7 @@ BOOST_AUTO_TEST_CASE(GetMinimumFee_dust_test) BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 1000, 0, pool), nMinTxFee * 1.000); BOOST_CHECK_EQUAL(CWallet::GetMinimumFee(tx, 1999, 0, pool), nMinTxFee * 1.999); - nDustLimit = COIN; + CWallet::discardThreshold = COIN; } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1beeb4362..b2539c401 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -58,6 +58,12 @@ CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE); * Override with -fallbackfee */ CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE); +/** + * Dogecoin: Effective dust limit for the wallet + * - Outputs smaller than this get rejected + * - Change smaller than this gets discarded to fee + */ +CAmount CWallet::discardThreshold = DEFAULT_DISCARD_THRESHOLD; /** @defgroup mapWallet * @@ -2173,6 +2179,14 @@ static void ApproximateBestSubset(vector vCoins, set >& setCoinsRet, CAmount& nValueRet) const { @@ -2212,7 +2226,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMin nValueRet += coin.first; return true; } - else if (n < nTargetValue + MIN_CHANGE) + else if (n < nTargetValue + GetMinChange()) { vValue.push_back(coin); nTotalLower += n; @@ -2249,13 +2263,13 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMin CAmount nBest; ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest); - if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) - ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest); + if (nBest != nTargetValue && nTotalLower >= nTargetValue + GetMinChange()) + ApproximateBestSubset(vValue, nTotalLower, nTargetValue + GetMinChange(), vfBest, nBest); // If we have a bigger coin and (either the stochastic approximation didn't find a good solution, // or the next bigger coin is closer), return the bigger coin if (coinLowestLarger.second.first && - ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger.first <= nBest)) + ((nBest != nTargetValue && nBest < nTargetValue + GetMinChange()) || coinLowestLarger.first <= nBest)) { setCoinsRet.insert(coinLowestLarger.second); nValueRet += coinLowestLarger.first; @@ -2500,7 +2514,15 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt } } - if (txout.IsDust(dustRelayFee)) + /* + * Dogecoin: check all outputs against the discard threshold + * to make sure that the wallet's dust policy gets + * followed rather than the current relay rules, + * because the larger network may settle on a + * higher hard limit than the current version's + * soft limit. + */ + if (txout.IsDust(discardThreshold)) { if (recipient.fSubtractFeeFromAmount && nFeeRet > 0) { @@ -2578,16 +2600,16 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt // We do not move dust-change to fees, because the sender would end up paying more than requested. // This would be against the purpose of the all-inclusive feature. // So instead we raise the change and deduct from the recipient. - if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(dustRelayFee)) + if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(discardThreshold)) { - CAmount nDust = newTxOut.GetDustThreshold(dustRelayFee) - newTxOut.nValue; - newTxOut.nValue += nDust; // raise change until no more dust + CAmount changeDelta = discardThreshold - newTxOut.nValue; + newTxOut.nValue += changeDelta; // raise change until we reach the discard threshold for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient { if (vecSend[i].fSubtractFeeFromAmount) { - txNew.vout[i].nValue -= nDust; - if (txNew.vout[i].IsDust(dustRelayFee)) + txNew.vout[i].nValue -= changeDelta; + if (txNew.vout[i].IsDust(discardThreshold)) { strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); return false; @@ -2597,9 +2619,9 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt } } - // Never create dust outputs; if we would, just - // add the dust to the fee. - if (newTxOut.IsDust(dustRelayFee)) + // Never create change under the discard threshold; + // if we would, just discard the change to the fee. + if (newTxOut.IsDust(discardThreshold)) { nChangePosInOut = -1; nFeeRet += nChange; @@ -2721,7 +2743,16 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet; vector::iterator change_position = txNew.vout.begin()+nChangePosInOut; // Only reduce change if remaining amount is still a large enough output. - if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) { + /* Dogecoin: this has been changed from a static MIN_FINAL_CHANGE that + * followed DEFAULT_DISCARD_THRESHOLD to instead use the configurable + * discard threshold. + * + * Note: + * If GetMinChange() ever becomes configurable or otherwise changes to no + * longer be derived from DEFAULT_DISCARD_THRESHOLD, then this check + * must be adapted. + */ + if (change_position->nValue >= discardThreshold + additionalFeeNeeded) { change_position->nValue -= additionalFeeNeeded; nFeeRet += additionalFeeNeeded; break; // Done, able to increase fee from change @@ -2852,8 +2883,8 @@ bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry, CWalletDB *pwa CAmount CWallet::GetRequiredFee(const CMutableTransaction& tx, unsigned int nTxBytes) { - // Dogecoin: Add an increased fee for each dust output - return std::max(minTxFee.GetFee(nTxBytes) + GetDogecoinDustFee(tx.vout, minTxFee), ::minRelayTxFeeRate.GetFee(nTxBytes)); + // Dogecoin: Add an increased fee for each output that is lower than the discard threshold + return std::max(minTxFee.GetFee(nTxBytes) + GetDogecoinDustFee(tx.vout, discardThreshold), ::minRelayTxFeeRate.GetFee(nTxBytes)); } CAmount CWallet::GetRequiredFee(unsigned int nTxBytes) @@ -3620,6 +3651,8 @@ std::string CWallet::GetWalletHelpString(bool showDebug) std::string strUsage = HelpMessageGroup(_("Wallet options:")); strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls")); strUsage += HelpMessageOpt("-keypool=", strprintf(_("Set key pool size to (default: %u)"), DEFAULT_KEYPOOL_SIZE)); + strUsage += HelpMessageOpt("-discardthreshold=", strprintf(_("The minimum transaction output size (in %s) used to validate wallet transactions and discard change (to fee) (default: %s)"), + CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_THRESHOLD))); strUsage += HelpMessageOpt("-fallbackfee=", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE))); strUsage += HelpMessageOpt("-mintxfee=", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)"), @@ -3953,6 +3986,22 @@ bool CWallet::ParameterInteraction() GetArg("-maxtxfee", ""), ::minRelayTxFeeRate.ToString())); } } + if (IsArgSet("-discardthreshold")) + { + CAmount nDiscardThreshold = 0; + if (!ParseMoney(GetArg("-discardthreshold", ""), nDiscardThreshold)) + return InitError(AmountErrMsg("discardthreshold", GetArg("-discardthreshold", ""))); + + if (nDiscardThreshold < nDustLimit) + { + return InitError(strprintf(_("Invalid amount for -discardthreshold=: '%s' (must be at least the dust limit of %s to prevent stuck transactions)"), + GetArg("-discardthreshold", ""), FormatMoney(nDustLimit))); + } + if (nDiscardThreshold > HIGH_TX_FEE_PER_KB) + InitWarning(_("-discardthreshold is set very high! This is the output amount that the wallet will discard (to fee) if it is smaller than this setting.")); + + CWallet::discardThreshold = nDiscardThreshold; + } nTxConfirmTarget = GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET); bSpendZeroConfChange = GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); fSendFreeTransactions = GetBoolArg("-sendfreetransactions", DEFAULT_SEND_FREE_TRANSACTIONS); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 34aa0c452..206ce39ad 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -56,6 +56,14 @@ static const CAmount DEFAULT_TRANSACTION_FEE = RECOMMENDED_MIN_TX_FEE; static const CAmount DEFAULT_FALLBACK_FEE = RECOMMENDED_MIN_TX_FEE; //! -mintxfee default static const CAmount DEFAULT_TRANSACTION_MINFEE = RECOMMENDED_MIN_TX_FEE; +//! -discardthreshold default +/* 1.14.5: set the wallet's discard threshold to 1 DOGE because that's what 97% + * of the network currently implements as the hard dust limit. This + * value can be changed when a significant portion of the relay network + * and miners have adopted a different hard dust limit. + */ +static const CAmount DEFAULT_DISCARD_THRESHOLD = COIN; + //! minimum recommended increment for BIP 125 replacement txs /* * Dogecoin: Scaled to 1/10th of the recommended transaction fee to make RBF @@ -66,11 +74,10 @@ static const CAmount DEFAULT_TRANSACTION_MINFEE = RECOMMENDED_MIN_TX_FEE; * This way, replacements for fee bumps are transient rather than persisted. */ static const CAmount WALLET_INCREMENTAL_RELAY_FEE = RECOMMENDED_MIN_TX_FEE / 10; - /* * Dogecoin: Creating change outputs at exactly the dustlimit is counter- * productive because it leaves no space to bump the fee up, so we make the - * MIN_CHANGE parameter higher than the MIN_FINAL_CHANGE parameter. + * minimum change higher than the discard threshold. * * When RBF is not a default policy, we need to scale for both that and CPFP, * to have a facility for those that did not manually enable RBF, yet need to @@ -85,24 +92,18 @@ static const CAmount WALLET_INCREMENTAL_RELAY_FEE = RECOMMENDED_MIN_TX_FEE / 10; * or transaction size, we assume that most transactions are < 1kb, leading * to the following when planning for a replacements with 2x original fee: * - * RBF: MIN_CHANGE = dust limit + min fee or - * CPFP: MIN_CHANGE = dust limit + 2 * min fee * 0.147 + min fee + * RBF: min change = discardThreshold + minTxFee(1000) or + * CPFP: min change = discardThreshold + 2 * minTxFee(147) + minTxFee(1000) * * Where the CPFP requirement is higher than the RBF one to lead to the same * result. * - * This can be rounded up to the nearest multiple of RECOMMENDED_MIN_TX_FEE as: + * This can be rounded up to the nearest multiple of minTxFee(1000) as: * - * MIN_CHANGE = DEFAULT_DUST_LIMIT + 2 * RECOMMENDED_MIN_TX_FEE - * - * The MIN_FINAL_CHANGE parameter can stay equal to DEFAULT_DUST_LIMIT as this - * influences when the wallet will discard all remaining dust as fee instead of - * change. + * min change = discardThreshold + 2 * minTxFee(1000) */ -//! target minimum change amount -static const CAmount MIN_CHANGE = DEFAULT_DUST_LIMIT + 2 * RECOMMENDED_MIN_TX_FEE; -//! final minimum change amount after paying for fees -static const CAmount MIN_FINAL_CHANGE = DEFAULT_DUST_LIMIT; +//! target minimum change fee multiplier +static const CAmount MIN_CHANGE_FEE_MULTIPLIER = 2; //! Default for -spendzeroconfchange static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; @@ -793,6 +794,13 @@ public: static CFeeRate minTxFee; static CFeeRate fallbackFee; + static CAmount discardThreshold; + + /** + * Minimum change as a function of discardThreshold + */ + static CAmount GetMinChange(); + /** * Estimate the minimum fee considering user set parameters * and the required fee