diff --git a/.cirrus.yml b/.cirrus.yml index e1102be4467..c2b3c500592 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -194,10 +194,10 @@ task: FILE_ENV: "./ci/test/00_setup_env_win64.sh" task: - name: '32-bit + dash [gui] [CentOS 8]' + name: '32-bit + dash [gui] [Rocky 8]' << : *GLOBAL_TASK_TEMPLATE container: - image: quay.io/centos/centos:stream8 + image: quay.io/rockylinux/rockylinux:8 env: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV PACKAGE_MANAGER_INSTALL: "yum install -y" diff --git a/ci/test/00_setup_env_i686_centos.sh b/ci/test/00_setup_env_i686_centos.sh index 8f1cc8af29e..28583c6e63b 100755 --- a/ci/test/00_setup_env_i686_centos.sh +++ b/ci/test/00_setup_env_i686_centos.sh @@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8 export HOST=i686-pc-linux-gnu export CONTAINER_NAME=ci_i686_centos -export DOCKER_NAME_TAG=quay.io/centos/centos:stream8 +export DOCKER_NAME_TAG=quay.io/rockylinux/rockylinux:8 export DOCKER_PACKAGES="gcc-c++ glibc-devel.x86_64 libstdc++-devel.x86_64 glibc-devel.i686 libstdc++-devel.i686 ccache libtool make git python3 python3-zmq which patch lbzip2 xz procps-ng dash rsync coreutils bison" export GOAL="install" export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-reduce-exports" diff --git a/ci/test/00_setup_env_mac_native_arm64.sh b/ci/test/00_setup_env_mac_native_arm64.sh index 1a7be56d5bd..ade2d2787c1 100755 --- a/ci/test/00_setup_env_mac_native_arm64.sh +++ b/ci/test/00_setup_env_mac_native_arm64.sh @@ -7,7 +7,9 @@ export LC_ALL=C.UTF-8 export HOST=arm64-apple-darwin -export PIP_PACKAGES="zmq" +# Homebrew's python@3.12 is marked as externally managed (PEP 668). +# Therefore, `--break-system-packages` is needed. +export PIP_PACKAGES="--break-system-packages zmq" export GOAL="install" # ELEMENTS: add -fno-stack-check to work around clang bug on macos export BITCOIN_CONFIG="--with-gui --with-miniupnpc --with-natpmp --enable-reduce-exports CXXFLAGS=-fno-stack-check" diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index eaeecec60ad..ff4293009b9 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -6,12 +6,14 @@ export LC_ALL=C.UTF-8 +set -x + if [[ $QEMU_USER_CMD == qemu-s390* ]]; then export LC_ALL=C fi if [ "$CI_OS_NAME" == "macos" ]; then - sudo -H pip3 install --upgrade pip + sudo -H pip3 install --upgrade pip || true # shellcheck disable=SC2086 IN_GETOPT_BIN="$(brew --prefix gnu-getopt)/bin/getopt" ${CI_RETRY_EXE} pip3 install --user $PIP_PACKAGES fi @@ -64,7 +66,7 @@ if [ -n "$DPKG_ADD_ARCH" ]; then CI_EXEC dpkg --add-architecture "$DPKG_ADD_ARCH" fi -if [[ $DOCKER_NAME_TAG == *centos* ]]; then +if [[ $DOCKER_NAME_TAG == *centos* ]] || [[ $DOCKER_NAME_TAG == *rocky* ]]; then ${CI_RETRY_EXE} CI_EXEC dnf -y install epel-release ${CI_RETRY_EXE} CI_EXEC dnf -y --allowerasing install "$DOCKER_PACKAGES" "$PACKAGES" elif [ "$CI_USE_APT_INSTALL" != "no" ]; then diff --git a/ci/test/05_before_script.sh b/ci/test/05_before_script.sh index 8f75fbd1faf..b1cb1e300f3 100755 --- a/ci/test/05_before_script.sh +++ b/ci/test/05_before_script.sh @@ -44,7 +44,7 @@ if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then fi if [ -z "$NO_DEPENDS" ]; then - if [[ $DOCKER_NAME_TAG == *centos* ]]; then + if [[ $DOCKER_NAME_TAG == *centos* ]] || [[ $DOCKER_NAME_TAG == *rocky* ]]; then # CentOS has problems building the depends if the config shell is not explicitly set # (i.e. for libevent a Makefile with an empty SHELL variable is generated, leading to # an error as the first command is executed) diff --git a/ci/test_run_all.sh b/ci/test_run_all.sh index 93b07aab1ec..1aec4909627 100755 --- a/ci/test_run_all.sh +++ b/ci/test_run_all.sh @@ -6,6 +6,7 @@ export LC_ALL=C.UTF-8 +set -x set -o errexit; source ./ci/test/00_setup_env.sh set -o errexit; source ./ci/test/04_install.sh set -o errexit; source ./ci/test/05_before_script.sh diff --git a/configure.ac b/configure.ac index 379ebbe695e..55e57c58473 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ AC_PREREQ([2.69]) define(_CLIENT_VERSION_MAJOR, 23) define(_CLIENT_VERSION_MINOR, 3) define(_CLIENT_VERSION_BUILD, 0) -define(_CLIENT_VERSION_RC, 1) +define(_CLIENT_VERSION_RC, 2) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2024) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/src/asset.h b/src/asset.h index ceb16216258..a15861d8f89 100644 --- a/src/asset.h +++ b/src/asset.h @@ -90,15 +90,19 @@ bool operator==(const CAmountMap& a, const CAmountMap& b); bool operator!=(const CAmountMap& a, const CAmountMap& b); bool operator!(const CAmountMap& a); // Check if all values are 0 -inline bool MoneyRange(const CAmountMap& mapValue) { +inline bool MoneyRange(const CAmountMap& mapValue, const CAsset& pegged_asset) { for(CAmountMap::const_iterator it = mapValue.begin(); it != mapValue.end(); it++) { - if (it->second < 0 || it->second > MAX_MONEY) { + if (it->second < 0 || ((pegged_asset.IsNull() || it->first == pegged_asset) && it->second > MAX_MONEY)) { return false; } } return true; } +inline bool MoneyRange(const CAmountMap& mapValue) { + return MoneyRange(mapValue, CAsset()); +} + CAmount valueFor(const CAmountMap& mapValue, const CAsset& asset); std::ostream& operator<<(std::ostream& out, const CAmountMap& map); diff --git a/src/blind.cpp b/src/blind.cpp index e1a15ab05a3..9cb9ea7a7d8 100644 --- a/src/blind.cpp +++ b/src/blind.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include @@ -157,11 +158,6 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& return false; } - // Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check) - if (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)) { - return false; - } - // Convenience pointers to starting point of each recovered 32 byte message unsigned char *asset_type = msg; unsigned char *asset_blinder = msg+32; @@ -172,6 +168,13 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& return false; } + CAsset asset{std::vector{asset_type, asset_type + 32}}; + + // Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check) + if ((!committedScript.IsUnspendable() && amount == 0) || (asset == Params().GetConsensus().pegged_asset && (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)))) { + return false; + } + // Serialize both generators then compare unsigned char observed_generator[33]; unsigned char derived_generator[33]; @@ -182,7 +185,7 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& } amount_out = (CAmount)amount; - asset_out = CAsset(std::vector(asset_type, asset_type+32)); + asset_out = asset; asset_blinding_factor_out = uint256(std::vector(asset_blinder, asset_blinder+32)); return true; } diff --git a/src/mapport.cpp b/src/mapport.cpp index 42ca3660893..af983a2c5b8 100644 --- a/src/mapport.cpp +++ b/src/mapport.cpp @@ -168,8 +168,11 @@ static bool ProcessUpnp() struct UPNPUrls urls; struct IGDdatas data; int r; - +#if MINIUPNPC_API_VERSION <= 17 r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); +#else + r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr), nullptr, 0); +#endif if (r == 1) { if (fDiscover) { diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 3148b7cc853..0a368a1c168 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -3050,7 +3050,7 @@ static RPCHelpMan rawissueasset() CAmount asset_amount = 0; const UniValue& asset_amount_uni = issuance_o["asset_amount"]; if (asset_amount_uni.isNum()) { - asset_amount = AmountFromValue(asset_amount_uni); + asset_amount = AmountFromValue(asset_amount_uni, false); if (asset_amount <= 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, asset_amount must be positive"); } @@ -3067,7 +3067,7 @@ static RPCHelpMan rawissueasset() CAmount token_amount = 0; const UniValue& token_amount_uni = issuance_o["token_amount"]; if (token_amount_uni.isNum()) { - token_amount = AmountFromValue(token_amount_uni); + token_amount = AmountFromValue(token_amount_uni, false); if (token_amount <= 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, token_amount must be positive"); } @@ -3178,7 +3178,7 @@ static RPCHelpMan rawreissueasset() CAmount asset_amount = 0; const UniValue& asset_amount_uni = issuance_o["asset_amount"]; if (asset_amount_uni.isNum()) { - asset_amount = AmountFromValue(asset_amount_uni); + asset_amount = AmountFromValue(asset_amount_uni, false); if (asset_amount <= 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, asset_amount must be positive"); } diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index cfb70cfce89..71e896951c4 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -90,14 +90,14 @@ void RPCTypeCheckObj(const UniValue& o, } } -CAmount AmountFromValue(const UniValue& value, int decimals) +CAmount AmountFromValue(const UniValue& value, bool check_range, int decimals) { if (!value.isNum() && !value.isStr()) throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number or string"); CAmount amount; if (!ParseFixedPoint(value.getValStr(), decimals, &amount)) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); - if (!MoneyRange(amount)) + if (amount < 0 || (check_range && !MoneyRange(amount))) throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range"); return amount; } diff --git a/src/rpc/util.h b/src/rpc/util.h index 898db3cd39d..4a55dddf615 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -91,7 +91,7 @@ std::vector ParseHexO(const UniValue& o, std::string strKey); * @param[in] decimals Number of significant digits (default: 8). * @returns a CAmount if the various checks pass. */ -CAmount AmountFromValue(const UniValue& value, int decimals = 8); +CAmount AmountFromValue(const UniValue& value, bool check_range = true, int decimals = 8); using RPCArgList = std::vector>; std::string HelpExampleCli(const std::string& methodname, const std::string& args); diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index 20a8c613761..cf988926b83 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -70,14 +70,17 @@ CAmountMap TxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const ismine { LOCK(wallet.cs_wallet); + CAsset pegged_asset{Params().GetConsensus().pegged_asset}; for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { if (wallet.IsMine(wtx.tx->vout[i]) & filter) { + CAsset asset{wtx.GetOutputAsset(wallet, i)}; CAmount credit = std::max(0, wtx.GetOutputValueOut(wallet, i)); - if (!MoneyRange(credit)) + if (asset == pegged_asset && !MoneyRange(credit)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); + } - nCredit[wtx.GetOutputAsset(wallet, i)] += credit; - if (!MoneyRange(nCredit)) + nCredit[asset] += credit; + if (!MoneyRange(nCredit, pegged_asset)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } } @@ -226,16 +229,18 @@ CAmountMap CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wt bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); CAmountMap nCredit; uint256 hashTx = wtx.GetHash(); + CAsset pegged_asset{Params().GetConsensus().pegged_asset}; for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { if (!wallet.IsSpent(hashTx, i) && (allow_used_addresses || !wallet.IsSpentKey(hashTx, i))) { if (wallet.IsMine(wtx.tx->vout[i]) & filter) { + CAsset asset = wtx.GetOutputAsset(wallet, i); CAmount credit = std::max(0, wtx.GetOutputValueOut(wallet, i)); - if (!MoneyRange(credit)) + if (asset == pegged_asset && !MoneyRange(credit)) throw std::runtime_error(std::string(__func__) + ": value out of range"); nCredit[wtx.GetOutputAsset(wallet, i)] += std::max(0, wtx.GetOutputValueOut(wallet, i)); - if (!MoneyRange(nCredit)) + if (!MoneyRange(nCredit, pegged_asset)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } } diff --git a/src/wallet/rpc/elements.cpp b/src/wallet/rpc/elements.cpp index d51274360c6..798c218249b 100644 --- a/src/wallet/rpc/elements.cpp +++ b/src/wallet/rpc/elements.cpp @@ -1426,8 +1426,8 @@ RPCHelpMan issueasset() throw JSONRPCError(RPC_TYPE_ERROR, "Issuance can only be done on elements-style chains. Note: `-regtest` is Bitcoin's regtest mode, instead try `-chain=`"); } - CAmount nAmount = AmountFromValue(request.params[0]); - CAmount nTokens = AmountFromValue(request.params[1]); + CAmount nAmount = AmountFromValue(request.params[0], false); + CAmount nTokens = AmountFromValue(request.params[1], false); if (nAmount == 0 && nTokens == 0) { throw JSONRPCError(RPC_TYPE_ERROR, "Issuance must have one non-zero component"); } @@ -1524,7 +1524,7 @@ RPCHelpMan reissueasset() std::string assetstr = request.params[0].get_str(); CAsset asset = GetAssetFromString(assetstr); - CAmount nAmount = AmountFromValue(request.params[1]); + CAmount nAmount = AmountFromValue(request.params[1], false); if (nAmount <= 0) { throw JSONRPCError(RPC_TYPE_ERROR, "Reissuance must create a non-zero amount."); } diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 903b2481444..0ead021dbbc 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -50,7 +50,7 @@ static void ParseRecipients(const UniValue& address_amounts, const UniValue& add destinations.insert(dest); CScript script_pub_key = GetScriptForDestination(dest); - CAmount amount = AmountFromValue(address_amounts[i++]); + CAmount amount = AmountFromValue(address_amounts[i++], asset == Params().GetConsensus().pegged_asset); bool subtract_fee = false; for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) { @@ -124,7 +124,7 @@ static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const Un throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate"); } // Fee rates in sat/vB cannot represent more than 3 significant digits. - cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /* decimals */ 3)}; + cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /*check_range=*/true, /*decimals=*/3)}; if (override_min_fee) cc.fOverrideFeeRate = true; // Default RBF to true for explicit fee_rate, if unset. if (!cc.m_signal_bip125_rbf) cc.m_signal_bip125_rbf = true; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index f7bd721cef2..9a82b63b2d2 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -265,8 +265,9 @@ void AvailableCoins(const CWallet& wallet, std::vector &vCoins, const C if (asset_filter && asset != *asset_filter) { continue; } - if (outValue < nMinimumAmount || outValue > nMaximumAmount) + if (outValue < nMinimumAmount || (asset == Params().GetConsensus().pegged_asset && outValue > nMaximumAmount)) { continue; + } if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) continue; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9399cb9a87b..c64489b828a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1430,7 +1430,7 @@ CAmountMap CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) for (const CTxIn& txin : tx.vin) { nDebit += GetDebit(txin, filter); - if (!MoneyRange(nDebit)) + if (!MoneyRange(nDebit, Params().GetConsensus().pegged_asset)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } return nDebit; diff --git a/test/functional/example_elements_code_tutorial.py b/test/functional/example_elements_code_tutorial.py index feb645f1f10..62a24ea76a7 100755 --- a/test/functional/example_elements_code_tutorial.py +++ b/test/functional/example_elements_code_tutorial.py @@ -24,7 +24,7 @@ def set_test_params(self): self.extra_args[0].append("-anyonecanspendaremine=1") def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + self.skip_if_no_bdb() def run_test(self): self.generate(self.nodes[0], COINBASE_MATURITY + 1) diff --git a/test/functional/feature_discount_ct.py b/test/functional/feature_discount_ct.py index 84e2c096b19..8b4cdd658e0 100755 --- a/test/functional/feature_discount_ct.py +++ b/test/functional/feature_discount_ct.py @@ -35,7 +35,7 @@ def set_test_params(self): ] def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + self.skip_if_no_bdb() def run_test(self): feerate = 1.0 diff --git a/test/functional/feature_discount_ct_ordering.py b/test/functional/feature_discount_ct_ordering.py index aed8a1f0db7..0dd2d9d54c8 100755 --- a/test/functional/feature_discount_ct_ordering.py +++ b/test/functional/feature_discount_ct_ordering.py @@ -32,7 +32,7 @@ def set_test_params(self): ] def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + self.skip_if_no_bdb() def run_test(self): diff --git a/test/functional/feature_taphash_pegins_issuances.py b/test/functional/feature_taphash_pegins_issuances.py index e4716e8ebc0..ec2ee8d99f1 100755 --- a/test/functional/feature_taphash_pegins_issuances.py +++ b/test/functional/feature_taphash_pegins_issuances.py @@ -40,7 +40,7 @@ def set_test_params(self): ]] def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + self.skip_if_no_bdb() def setup_network(self, split=False): self.setup_nodes() diff --git a/test/functional/feature_tapscript_opcodes.py b/test/functional/feature_tapscript_opcodes.py index 8772a08d72b..6174e18e302 100755 --- a/test/functional/feature_tapscript_opcodes.py +++ b/test/functional/feature_tapscript_opcodes.py @@ -44,7 +44,7 @@ def set_test_params(self): ]] def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + self.skip_if_no_bdb() def setup_network(self, split=False): self.setup_nodes() diff --git a/test/functional/feature_trim_headers.py b/test/functional/feature_trim_headers.py index 8d44ee92a95..0ed204ffa95 100755 --- a/test/functional/feature_trim_headers.py +++ b/test/functional/feature_trim_headers.py @@ -38,7 +38,7 @@ def make_signblockscript(num_nodes, required_signers, keys): class TrimHeadersTest(BitcoinTestFramework): def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + self.skip_if_no_bdb() # Dynamically generate N keys to be used for block signing. def init_keys(self, num_keys): diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index b9efe9b8d87..710e372752b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -112,6 +112,7 @@ 'rpc_getnewblockhex.py', 'wallet_elements_regression_1172.py --legacy-wallet', 'wallet_elements_regression_1259.py --legacy-wallet', + 'wallet_elements_21million.py', 'feature_trim_headers.py', # Longest test should go first, to favor running tests in parallel 'wallet_hd.py --legacy-wallet', diff --git a/test/functional/wallet_elements_21million.py b/test/functional/wallet_elements_21million.py new file mode 100755 index 00000000000..ea6b34dd6f0 --- /dev/null +++ b/test/functional/wallet_elements_21million.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017-2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.blocktools import COINBASE_MATURITY +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + +class WalletTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 3 + self.extra_args = [['-blindedaddresses=1']] * self.num_nodes + + def setup_network(self, split=False): + self.setup_nodes() + self.connect_nodes(0, 1) + self.connect_nodes(1, 2) + self.connect_nodes(0, 2) + self.sync_all() + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + self.generate(self.nodes[0], COINBASE_MATURITY + 1) + + assert_equal(self.nodes[0].getbalance(), {'bitcoin': 50}) + assert_equal(self.nodes[1].getbalance(), {'bitcoin': 0}) + + self.log.info("Issue more than 21 million of a non-policy asset") + issuance = self.nodes[0].issueasset(100_000_000, 100) + asset = issuance['asset'] + self.generate(self.nodes[0], 1) + assert_equal(self.nodes[0].getbalance()[asset], 100_000_000) + + self.log.info("Reissue more than 21 million of a non-policy asset") + self.nodes[0].reissueasset(asset, 100_000_000) + self.generate(self.nodes[0], 1) + assert_equal(self.nodes[0].getbalance()[asset], 200_000_000) + + # send more than 21 million of that asset + addr = self.nodes[1].getnewaddress() + self.nodes[0].sendtoaddress(address=addr, amount=22_000_000, assetlabel=asset) + self.generate(self.nodes[0], 1) + assert_equal(self.nodes[0].getbalance()[asset], 178_000_000) + assert_equal(self.nodes[1].getbalance()[asset], 22_000_000) + + # unload/load wallet + self.nodes[1].unloadwallet(self.default_wallet_name) + self.nodes[1].loadwallet(self.default_wallet_name) + assert_equal(self.nodes[1].getbalance()[asset], 22_000_000) + + # send more than 45 million of that asset + addr = self.nodes[2].getnewaddress() + self.nodes[0].sendtoaddress(address=addr, amount=46_000_000, assetlabel=asset) + self.generate(self.nodes[0], 1) + assert_equal(self.nodes[0].getbalance()[asset], 132_000_000) + assert_equal(self.nodes[2].getbalance()[asset], 46_000_000) + + # unload/load wallet + self.nodes[2].unloadwallet(self.default_wallet_name) + self.nodes[2].loadwallet(self.default_wallet_name) + assert_equal(self.nodes[2].getbalance()[asset], 46_000_000) + + # send some policy asset to node 1 for fees + addr = self.nodes[1].getnewaddress() + self.nodes[0].sendtoaddress(address=addr, amount=1) + self.generate(self.nodes[0], 1) + assert_equal(self.nodes[1].getbalance()['bitcoin'], 1) + assert_equal(self.nodes[1].getbalance()[asset], 22_000_000) + + # send the remainders + addr = self.nodes[2].getnewaddress() + self.nodes[0].sendtoaddress(address=addr, amount=132_000_000, assetlabel=asset) + addr = self.nodes[2].getnewaddress() + self.nodes[1].sendtoaddress(address=addr, amount=22_000_000, assetlabel=asset) + self.sync_mempools() + self.generate(self.nodes[0], 1) + + assert asset not in self.nodes[0].getbalance() + assert asset not in self.nodes[1].getbalance() + assert_equal(self.nodes[2].getbalance()[asset], 200_000_000) + + # unload/load wallet + self.nodes[2].unloadwallet(self.default_wallet_name) + self.nodes[2].loadwallet(self.default_wallet_name) + assert_equal(self.nodes[2].getbalance()[asset], 200_000_000) + +if __name__ == '__main__': + WalletTest().main() diff --git a/test/functional/wallet_elements_regression_1263.py b/test/functional/wallet_elements_regression_1263.py index c4f713c8e36..e13c8a6f8ea 100755 --- a/test/functional/wallet_elements_regression_1263.py +++ b/test/functional/wallet_elements_regression_1263.py @@ -18,7 +18,7 @@ def set_test_params(self): self.chain = "regtest" def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + self.skip_if_no_bdb() def run_test(self): self.log.info("Start in Bitcoin regtest mode")