Compare commits

...

8 Commits

Author SHA1 Message Date
maflcko
1dd89ff8f3
Merge fa0cf040baa74b161b4b00ddea95bfb5aac0bb53 into 5f4422d68dc3530c353af1f87499de1c864b60ad 2025-03-17 09:50:15 +07:00
merge-script
5f4422d68d
Merge bitcoin/bitcoin#32010: qa: Fix TxIndex race conditions
3301d2cbe8c3b76c97285d75fa59637cb6952d0b qa: Wait for txindex to avoid race condition (Hodlinator)
9bfb0d75ba10591cc6c9620f9fd1ecc0e55e7a48 qa: Remove unnecessary -txindex args (Hodlinator)
7ac281c19cd3d11f316dbbb3308eabf1ad4f26d6 qa: Add missing coverage of corrupt indexes (Hodlinator)

Pull request description:

  - Add synchronization in 3 places where if the Transaction Index happens to be slow, we get rare test failures when querying it for transactions (one such case experienced on Windows, prompting investigation).
  - Remove unnecessary TxIndex initialization in some tests.
  - Add some test coverage where TxIndex aspect could be tested in feature_init.py.

ACKs for top commit:
  fjahr:
    re-ACK 3301d2cbe8c3b76c97285d75fa59637cb6952d0b
  mzumsande:
    Code Review ACK 3301d2cbe8c3b76c97285d75fa59637cb6952d0b
  furszy:
    Code review ACK 3301d2cbe8c3b76c97285d75fa59637cb6952d0b
  Prabhat1308:
    Concept ACK [`3301d2c`](3301d2cbe8)

Tree-SHA512: 7c2019e38455f344856aaf6b381faafbd88d53dc88d13309deb718c1dcfbee4ccca7c7f1b66917395503a6f94c3b216a007ad432cc8b93d0309db9805f38d602
2025-03-17 10:28:14 +08:00
MarcoFalke
fa0cf040ba
doc: Remove outdated and stale todo comment
If anything is left to be done, a new discussion issue or pull request
can be created.
2025-03-16 14:18:06 +01:00
Hodlinator
3301d2cbe8
qa: Wait for txindex to avoid race condition
Can be verified to be necessary through adding std::this_thread::sleep_for(0.5s) at the beginning of TxIndex::CustomAppend.
2025-03-10 15:24:16 +01:00
Hodlinator
9bfb0d75ba
qa: Remove unnecessary -txindex args
(Parent commit ensured indexes in feature_init.py are actually used, otherwise they would be removed here as well).
2025-03-07 22:22:31 +01:00
Hodlinator
7ac281c19c
qa: Add missing coverage of corrupt indexes 2025-03-07 22:22:31 +01:00
MarcoFalke
fa3fb3c23f
refactor: Remove redundant call to IsArgSet
Checking for IsArgSet before calling GetArg while providing an arbitrary
default value as fallback is both confusing and fragile.

It is confusing, because the provided fallback is dead code. So it would
be better to just call GetArg without a fallback.

Even better would be to provide the true fallback value and sanitize it
as if it were user-input, but this can be done in a follow-up.

Removing the redundant call to IsArgSet will have to be done either way,
so do it now.
2025-02-18 15:45:28 +01:00
MarcoFalke
fa41685f43
refactor: Remove IsArgSet guard when fallback value is provided
Checking for IsArgSet before calling GetArg while providing the args
default value as fallback is both confusing and fragile.

It is confusing, because the provided fallback is dead code. So it would
be better to just call GetArg without a fallback.

However, ignoring the fallback value is fragile, because it would not be
sanitized.

Fix all issues by sanitizing the fallback value.
2025-02-18 12:52:14 +01:00
11 changed files with 73 additions and 54 deletions

View File

@ -1078,7 +1078,7 @@ static void ParseGetInfoResult(UniValue& result)
}
#endif
if (gArgs.IsArgSet("-color")) {
{
const std::string color{gArgs.GetArg("-color", DEFAULT_COLOR_SETTING)};
if (color == "always") {
should_colorize = true;

View File

@ -1036,22 +1036,20 @@ bool AppInitParameterInteraction(const ArgsManager& args)
return InitError(Untranslated("peertimeout must be a positive integer."));
}
// Sanity check argument for min fee for including tx in block
// TODO: Harmonize which arguments need sanity checking and where that happens
if (args.IsArgSet("-blockmintxfee")) {
if (!ParseMoney(args.GetArg("-blockmintxfee", ""))) {
return InitError(AmountErrMsg("blockmintxfee", args.GetArg("-blockmintxfee", "")));
if (const auto arg{args.GetArg("-blockmintxfee")}) {
if (!ParseMoney(*arg)) {
return InitError(AmountErrMsg("blockmintxfee", *arg));
}
}
if (args.IsArgSet("-blockmaxweight")) {
{
const auto max_block_weight = args.GetIntArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT);
if (max_block_weight > MAX_BLOCK_WEIGHT) {
return InitError(strprintf(_("Specified -blockmaxweight (%d) exceeds consensus maximum block weight (%d)"), max_block_weight, MAX_BLOCK_WEIGHT));
}
}
if (args.IsArgSet("-blockreservedweight")) {
{
const auto block_reserved_weight = args.GetIntArg("-blockreservedweight", DEFAULT_BLOCK_RESERVED_WEIGHT);
if (block_reserved_weight > MAX_BLOCK_WEIGHT) {
return InitError(strprintf(_("Specified -blockreservedweight (%d) exceeds consensus maximum block weight (%d)"), block_reserved_weight, MAX_BLOCK_WEIGHT));
@ -1183,11 +1181,10 @@ bool CheckHostPortOptions(const ArgsManager& args) {
"-port",
"-rpcport",
}) {
if (args.IsArgSet(port_option)) {
const std::string port = args.GetArg(port_option, "");
if (const auto port{args.GetArg(port_option)}) {
uint16_t n;
if (!ParseUInt16(port, &n) || n == 0) {
return InitError(InvalidPortErrMsg(port_option, port));
if (!ParseUInt16(*port, &n) || n == 0) {
return InitError(InvalidPortErrMsg(port_option, *port));
}
}
}

View File

@ -48,20 +48,20 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
// incremental relay fee sets the minimum feerate increase necessary for replacement in the mempool
// and the amount the mempool min fee increases above the feerate of txs evicted due to mempool limiting.
if (argsman.IsArgSet("-incrementalrelayfee")) {
if (std::optional<CAmount> inc_relay_fee = ParseMoney(argsman.GetArg("-incrementalrelayfee", ""))) {
if (const auto arg{argsman.GetArg("-incrementalrelayfee")}) {
if (std::optional<CAmount> inc_relay_fee = ParseMoney(*arg)) {
mempool_opts.incremental_relay_feerate = CFeeRate{inc_relay_fee.value()};
} else {
return util::Error{AmountErrMsg("incrementalrelayfee", argsman.GetArg("-incrementalrelayfee", ""))};
return util::Error{AmountErrMsg("incrementalrelayfee", *arg)};
}
}
if (argsman.IsArgSet("-minrelaytxfee")) {
if (std::optional<CAmount> min_relay_feerate = ParseMoney(argsman.GetArg("-minrelaytxfee", ""))) {
if (const auto arg{argsman.GetArg("-minrelaytxfee")}) {
if (std::optional<CAmount> min_relay_feerate = ParseMoney(*arg)) {
// High fee check is done afterward in CWallet::Create()
mempool_opts.min_relay_feerate = CFeeRate{min_relay_feerate.value()};
} else {
return util::Error{AmountErrMsg("minrelaytxfee", argsman.GetArg("-minrelaytxfee", ""))};
return util::Error{AmountErrMsg("minrelaytxfee", *arg)};
}
} else if (mempool_opts.incremental_relay_feerate > mempool_opts.min_relay_feerate) {
// Allow only setting incremental fee to control both
@ -71,11 +71,11 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
// Feerate used to define dust. Shouldn't be changed lightly as old
// implementations may inadvertently create non-standard transactions
if (argsman.IsArgSet("-dustrelayfee")) {
if (std::optional<CAmount> parsed = ParseMoney(argsman.GetArg("-dustrelayfee", ""))) {
if (const auto arg{argsman.GetArg("-dustrelayfee")}) {
if (std::optional<CAmount> parsed = ParseMoney(*arg)) {
mempool_opts.dust_relay_feerate = CFeeRate{parsed.value()};
} else {
return util::Error{AmountErrMsg("dustrelayfee", argsman.GetArg("-dustrelayfee", ""))};
return util::Error{AmountErrMsg("dustrelayfee", *arg)};
}
}

View File

@ -140,9 +140,9 @@ Intro::Intro(QWidget *parent, int64_t blockchain_size_gb, int64_t chain_state_si
const int min_prune_target_GB = std::ceil(MIN_DISK_SPACE_FOR_BLOCK_FILES / 1e9);
ui->pruneGB->setRange(min_prune_target_GB, std::numeric_limits<int>::max());
if (gArgs.IsArgSet("-prune")) {
if (const auto arg{gArgs.GetIntArg("-prune")}) {
m_prune_checkbox_is_default = false;
ui->prune->setChecked(gArgs.GetIntArg("-prune", 0) >= 1);
ui->prune->setChecked(*arg >= 1);
ui->prune->setEnabled(false);
}
ui->pruneGB->setValue(m_prune_target_gb);

View File

@ -3124,10 +3124,10 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
walletInstance->m_default_change_type = parsed.value();
}
if (args.IsArgSet("-mintxfee")) {
std::optional<CAmount> min_tx_fee = ParseMoney(args.GetArg("-mintxfee", ""));
if (const auto arg{args.GetArg("-mintxfee")}) {
std::optional<CAmount> min_tx_fee = ParseMoney(*arg);
if (!min_tx_fee) {
error = AmountErrMsg("mintxfee", args.GetArg("-mintxfee", ""));
error = AmountErrMsg("mintxfee", *arg);
return nullptr;
} else if (min_tx_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-mintxfee") + Untranslated(" ") +
@ -3137,8 +3137,8 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
walletInstance->m_min_fee = CFeeRate{min_tx_fee.value()};
}
if (args.IsArgSet("-maxapsfee")) {
const std::string max_aps_fee{args.GetArg("-maxapsfee", "")};
if (const auto arg{args.GetArg("-maxapsfee")}) {
const std::string& max_aps_fee{*arg};
if (max_aps_fee == "-1") {
walletInstance->m_max_aps_fee = -1;
} else if (std::optional<CAmount> max_fee = ParseMoney(max_aps_fee)) {
@ -3153,10 +3153,10 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
}
}
if (args.IsArgSet("-fallbackfee")) {
std::optional<CAmount> fallback_fee = ParseMoney(args.GetArg("-fallbackfee", ""));
if (const auto arg{args.GetArg("-fallbackfee")}) {
std::optional<CAmount> fallback_fee = ParseMoney(*arg);
if (!fallback_fee) {
error = strprintf(_("Invalid amount for %s=<amount>: '%s'"), "-fallbackfee", args.GetArg("-fallbackfee", ""));
error = strprintf(_("Invalid amount for %s=<amount>: '%s'"), "-fallbackfee", *arg);
return nullptr;
} else if (fallback_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-fallbackfee") + Untranslated(" ") +
@ -3168,10 +3168,10 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
// Disable fallback fee in case value was set to 0, enable if non-null value
walletInstance->m_allow_fallback_fee = walletInstance->m_fallback_fee.GetFeePerK() != 0;
if (args.IsArgSet("-discardfee")) {
std::optional<CAmount> discard_fee = ParseMoney(args.GetArg("-discardfee", ""));
if (const auto arg{args.GetArg("-discardfee")}) {
std::optional<CAmount> discard_fee = ParseMoney(*arg);
if (!discard_fee) {
error = strprintf(_("Invalid amount for %s=<amount>: '%s'"), "-discardfee", args.GetArg("-discardfee", ""));
error = strprintf(_("Invalid amount for %s=<amount>: '%s'"), "-discardfee", *arg);
return nullptr;
} else if (discard_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-discardfee") + Untranslated(" ") +
@ -3180,10 +3180,10 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
walletInstance->m_discard_rate = CFeeRate{discard_fee.value()};
}
if (args.IsArgSet("-paytxfee")) {
std::optional<CAmount> pay_tx_fee = ParseMoney(args.GetArg("-paytxfee", ""));
if (const auto arg{args.GetArg("-paytxfee")}) {
std::optional<CAmount> pay_tx_fee = ParseMoney(*arg);
if (!pay_tx_fee) {
error = AmountErrMsg("paytxfee", args.GetArg("-paytxfee", ""));
error = AmountErrMsg("paytxfee", *arg);
return nullptr;
} else if (pay_tx_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-paytxfee") + Untranslated(" ") +
@ -3194,15 +3194,15 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
if (chain && walletInstance->m_pay_tx_fee < chain->relayMinFee()) {
error = strprintf(_("Invalid amount for %s=<amount>: '%s' (must be at least %s)"),
"-paytxfee", args.GetArg("-paytxfee", ""), chain->relayMinFee().ToString());
"-paytxfee", *arg, chain->relayMinFee().ToString());
return nullptr;
}
}
if (args.IsArgSet("-maxtxfee")) {
std::optional<CAmount> max_fee = ParseMoney(args.GetArg("-maxtxfee", ""));
if (const auto arg{args.GetArg("-maxtxfee")}) {
std::optional<CAmount> max_fee = ParseMoney(*arg);
if (!max_fee) {
error = AmountErrMsg("maxtxfee", args.GetArg("-maxtxfee", ""));
error = AmountErrMsg("maxtxfee", *arg);
return nullptr;
} else if (max_fee.value() > HIGH_MAX_TX_FEE) {
warnings.push_back(strprintf(_("%s is set very high! Fees this large could be paid on a single transaction."), "-maxtxfee"));
@ -3210,18 +3210,18 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
if (chain && CFeeRate{max_fee.value(), 1000} < chain->relayMinFee()) {
error = strprintf(_("Invalid amount for %s=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
"-maxtxfee", args.GetArg("-maxtxfee", ""), chain->relayMinFee().ToString());
"-maxtxfee", *arg, chain->relayMinFee().ToString());
return nullptr;
}
walletInstance->m_default_max_tx_fee = max_fee.value();
}
if (args.IsArgSet("-consolidatefeerate")) {
if (std::optional<CAmount> consolidate_feerate = ParseMoney(args.GetArg("-consolidatefeerate", ""))) {
if (const auto arg{args.GetArg("-consolidatefeerate")}) {
if (std::optional<CAmount> consolidate_feerate = ParseMoney(*arg)) {
walletInstance->m_consolidate_feerate = CFeeRate(*consolidate_feerate);
} else {
error = AmountErrMsg("consolidatefeerate", args.GetArg("-consolidatefeerate", ""));
error = AmountErrMsg("consolidatefeerate", *arg);
return nullptr;
}
}

View File

@ -88,7 +88,7 @@ class InitTest(BitcoinTestFramework):
args = ['-txindex=1', '-blockfilterindex=1', '-coinstatsindex=1']
for terminate_line in lines_to_terminate_after:
self.log.info(f"Starting node and will exit after line {terminate_line}")
self.log.info(f"Starting node and will terminate after line {terminate_line}")
with node.busy_wait_for_debug_log([terminate_line]):
if platform.system() == 'Windows':
# CREATE_NEW_PROCESS_GROUP is required in order to be able
@ -108,12 +108,22 @@ class InitTest(BitcoinTestFramework):
'blocks/index/*.ldb': 'Error opening block database.',
'chainstate/*.ldb': 'Error opening coins database.',
'blocks/blk*.dat': 'Error loading block database.',
'indexes/txindex/MANIFEST*': 'LevelDB error: Corruption: CURRENT points to a non-existent file',
# Removing these files does not result in a startup error:
# 'indexes/blockfilter/basic/*.dat', 'indexes/blockfilter/basic/db/*.*', 'indexes/coinstats/db/*.*',
# 'indexes/txindex/*.log', 'indexes/txindex/CURRENT', 'indexes/txindex/LOCK'
}
files_to_perturb = {
'blocks/index/*.ldb': 'Error loading block database.',
'chainstate/*.ldb': 'Error opening coins database.',
'blocks/blk*.dat': 'Corrupted block database detected.',
'indexes/blockfilter/basic/db/*.*': 'LevelDB error: Corruption',
'indexes/coinstats/db/*.*': 'LevelDB error: Corruption',
'indexes/txindex/*.log': 'LevelDB error: Corruption',
'indexes/txindex/CURRENT': 'LevelDB error: Corruption',
# Perturbing these files does not result in a startup error:
# 'indexes/blockfilter/basic/*.dat', 'indexes/txindex/MANIFEST*', 'indexes/txindex/LOCK'
}
for file_patt, err_fragment in files_to_delete.items():
@ -135,9 +145,10 @@ class InitTest(BitcoinTestFramework):
self.stop_node(0)
self.log.info("Test startup errors after perturbing certain essential files")
dirs = ["blocks", "chainstate", "indexes"]
for file_patt, err_fragment in files_to_perturb.items():
shutil.copytree(node.chain_path / "blocks", node.chain_path / "blocks_bak")
shutil.copytree(node.chain_path / "chainstate", node.chain_path / "chainstate_bak")
for dir in dirs:
shutil.copytree(node.chain_path / dir, node.chain_path / f"{dir}_bak")
target_files = list(node.chain_path.glob(file_patt))
for target_file in target_files:
@ -151,10 +162,9 @@ class InitTest(BitcoinTestFramework):
start_expecting_error(err_fragment)
shutil.rmtree(node.chain_path / "blocks")
shutil.rmtree(node.chain_path / "chainstate")
shutil.move(node.chain_path / "blocks_bak", node.chain_path / "blocks")
shutil.move(node.chain_path / "chainstate_bak", node.chain_path / "chainstate")
for dir in dirs:
shutil.rmtree(node.chain_path / dir)
shutil.move(node.chain_path / f"{dir}_bak", node.chain_path / dir)
def init_pid_test(self):
BITCOIN_PID_FILENAME_CUSTOM = "my_fancy_bitcoin_pid_file.foobar"

View File

@ -45,6 +45,7 @@ from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
sync_txindex,
)
from test_framework.wallet import MiniWallet
from test_framework.wallet_util import generate_keypair
@ -270,6 +271,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
self.log.info('A coinbase transaction')
# Pick the input of the first tx we created, so it has to be a coinbase tx
sync_txindex(self, node)
raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid'])
tx = tx_from_hex(raw_tx_coinbase_spent)
self.check_mempool_result(

View File

@ -34,6 +34,7 @@ from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
sync_txindex,
)
from test_framework.wallet import (
getnewdestination,
@ -70,7 +71,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.num_nodes = 3
self.extra_args = [
["-txindex"],
["-txindex"],
[],
["-fastprune", "-prune=1"],
]
# whitelist peers to speed up tx relay / mempool sync
@ -109,6 +110,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex")
if n == 0:
sync_txindex(self, self.nodes[n])
# With -txindex.
# 1. valid parameters - only supply txid
assert_equal(self.nodes[n].getrawtransaction(txId), tx['hex'])

View File

@ -12,6 +12,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
sync_txindex,
)
from test_framework.wallet import MiniWallet
@ -77,6 +78,7 @@ class MerkleBlockTest(BitcoinTestFramework):
assert_equal(sorted(self.nodes[0].verifytxoutproof(self.nodes[0].gettxoutproof([txid1, txid2]))), sorted(txlist))
assert_equal(sorted(self.nodes[0].verifytxoutproof(self.nodes[0].gettxoutproof([txid2, txid1]))), sorted(txlist))
# We can always get a proof if we have a -txindex
sync_txindex(self, self.nodes[1])
assert_equal(self.nodes[0].verifytxoutproof(self.nodes[1].gettxoutproof([txid_spent])), [txid_spent])
# We can't get a proof if we specify transactions from different blocks
assert_raises_rpc_error(-5, "Not all transactions found in specified or retrieved block", self.nodes[0].gettxoutproof, [txid1, txid3])

View File

@ -592,3 +592,10 @@ def find_vout_for_address(node, txid, addr):
if addr == tx["vout"][i]["scriptPubKey"]["address"]:
return i
raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr))
def sync_txindex(test_framework, node):
test_framework.log.debug("Waiting for node txindex to sync")
sync_start = int(time.time())
test_framework.wait_until(lambda: node.getindexinfo("txindex")["txindex"]["synced"])
test_framework.log.debug(f"Synced in {time.time() - sync_start} seconds")

View File

@ -117,7 +117,6 @@ class AddressInputTypeGrouping(BitcoinTestFramework):
self.extra_args = [
[
"-addresstype=bech32",
"-txindex",
],
[
"-addresstype=p2sh-segwit",