diff options
-rw-r--r-- | src/rpc/blockchain.cpp | 4 | ||||
-rw-r--r-- | src/rpc/util.cpp | 21 | ||||
-rw-r--r-- | src/rpc/util.h | 10 | ||||
-rwxr-xr-x | test/functional/feature_maxtipage.py | 56 | ||||
-rwxr-xr-x | test/functional/rpc_rawtransaction.py | 39 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 1 | ||||
-rw-r--r-- | test/sanitizer_suppressions/ubsan | 4 |
7 files changed, 102 insertions, 33 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index df8a8fffb4..128cae6715 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -790,7 +790,7 @@ static RPCHelpMan getblockfrompeer() { return RPCHelpMan{ "getblockfrompeer", - "\nAttempt to fetch block from a given peer.\n" + "Attempt to fetch block from a given peer.\n" "\nWe must have the header for this block, e.g. using submitheader.\n" "Subsequent calls for the same block and a new peer will cause the response from the previous peer to be ignored.\n" "\nReturns an empty JSON object if the request was successfully scheduled.", @@ -798,7 +798,7 @@ static RPCHelpMan getblockfrompeer() {"block_hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"}, {"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"}, }, - RPCResult{RPCResult::Type::OBJ_EMPTY, "", /*optional=*/ false, "", {}}, + RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}}, RPCExamples{ HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0") + HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0") diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index ae2f319f6c..5ef7e26ce8 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -830,16 +830,15 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const return; } case Type::OBJ_DYN: - case Type::OBJ_EMPTY: { - sections.PushSection({indent + maybe_key + "{}", Description("empty JSON object")}); - return; - } case Type::OBJ: { + if (m_inner.empty()) { + sections.PushSection({indent + maybe_key + "{}", Description("empty JSON object")}); + return; + } sections.PushSection({indent + maybe_key + "{", Description("json object")}); for (const auto& i : m_inner) { i.ToSections(sections, OuterType::OBJ, current_indent + 2); } - CHECK_NONFATAL(!m_inner.empty()); if (m_type == Type::OBJ_DYN && m_inner.back().m_type != Type::ELISION) { // If the dictionary keys are dynamic, use three dots for continuation sections.PushSection({indent_next + "...", ""}); @@ -883,7 +882,6 @@ bool RPCResult::MatchesType(const UniValue& result) const return UniValue::VARR == result.getType(); } case Type::OBJ_DYN: - case Type::OBJ_EMPTY: case Type::OBJ: { return UniValue::VOBJ == result.getType(); } @@ -891,6 +889,17 @@ bool RPCResult::MatchesType(const UniValue& result) const CHECK_NONFATAL(false); } +void RPCResult::CheckInnerDoc() const +{ + if (m_type == Type::OBJ) { + // May or may not be empty + return; + } + // Everything else must either be empty or not + const bool inner_needed{m_type == Type::ARR || m_type == Type::ARR_FIXED || m_type == Type::OBJ_DYN}; + CHECK_NONFATAL(inner_needed != m_inner.empty()); +} + std::string RPCArg::ToStringObj(const bool oneline) const { std::string res; diff --git a/src/rpc/util.h b/src/rpc/util.h index 352a3e4e4e..25ebf78fa1 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -240,7 +240,6 @@ struct RPCResult { STR_AMOUNT, //!< Special string to represent a floating point amount STR_HEX, //!< Special string with only hex chars OBJ_DYN, //!< Special dictionary with keys that are not literals - OBJ_EMPTY, //!< Special type to allow empty OBJ ARR_FIXED, //!< Special array that has a fixed number of entries NUM_TIME, //!< Special numeric to denote unix epoch time ELISION, //!< Special type to denote elision (...) @@ -268,8 +267,7 @@ struct RPCResult { m_cond{std::move(cond)} { CHECK_NONFATAL(!m_cond.empty()); - const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN}; - CHECK_NONFATAL(inner_needed != inner.empty()); + CheckInnerDoc(); } RPCResult( @@ -293,8 +291,7 @@ struct RPCResult { m_description{std::move(description)}, m_cond{} { - const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN}; - CHECK_NONFATAL(inner_needed != inner.empty()); + CheckInnerDoc(); } RPCResult( @@ -312,6 +309,9 @@ struct RPCResult { std::string ToDescriptionString() const; /** Check whether the result JSON type matches. */ bool MatchesType(const UniValue& result) const; + +private: + void CheckInnerDoc() const; }; struct RPCResults { diff --git a/test/functional/feature_maxtipage.py b/test/functional/feature_maxtipage.py new file mode 100755 index 0000000000..87f9d6962d --- /dev/null +++ b/test/functional/feature_maxtipage.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test logic for setting nMaxTipAge on command line. + +Nodes don't consider themselves out of "initial block download" as long as +their best known block header time is more than nMaxTipAge in the past. +""" + +import time + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +DEFAULT_MAX_TIP_AGE = 24 * 60 * 60 + + +class MaxTipAgeTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def test_maxtipage(self, maxtipage, set_parameter=True): + node_miner = self.nodes[0] + node_ibd = self.nodes[1] + + self.restart_node(1, [f'-maxtipage={maxtipage}'] if set_parameter else None) + self.connect_nodes(0, 1) + + # tips older than maximum age -> stay in IBD + cur_time = int(time.time()) + node_ibd.setmocktime(cur_time) + for delta in [5, 4, 3, 2, 1]: + node_miner.setmocktime(cur_time - maxtipage - delta) + self.generate(node_miner, 1) + assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], True) + + # tip within maximum age -> leave IBD + node_miner.setmocktime(cur_time - maxtipage) + self.generate(node_miner, 1) + assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], False) + + def run_test(self): + self.log.info("Test IBD with maximum tip age of 24 hours (default).") + self.test_maxtipage(DEFAULT_MAX_TIP_AGE, set_parameter=False) + + for hours in [20, 10, 5, 2, 1]: + maxtipage = hours * 60 * 60 + self.log.info(f"Test IBD with maximum tip age of {hours} hours (-maxtipage={maxtipage}).") + self.test_maxtipage(maxtipage) + + +if __name__ == '__main__': + MaxTipAgeTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 96691b2686..a839af0288 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -99,25 +99,36 @@ class RawTransactionsTest(BitcoinTestFramework): rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999}) rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx) txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex']) - self.generate(self.nodes[0], 1) + self.generateblock(self.nodes[0], output=self.nodes[0].getnewaddress(), transactions=[rawTxSigned['hex']]) + err_msg = ( + "No such mempool transaction. Use -txindex or provide a block hash to enable" + " blockchain transaction queries. Use gettransaction for wallet transactions." + ) for n in [0, 3]: self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex") - # 1. valid parameters - only supply txid - assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex']) - # 2. valid parameters - supply txid and 0 for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex']) + if n == 0: + # With -txindex. + # 1. valid parameters - only supply txid + assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex']) - # 3. valid parameters - supply txid and False for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex']) + # 2. valid parameters - supply txid and 0 for non-verbose + assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex']) - # 4. valid parameters - supply txid and 1 for verbose. - # We only check the "hex" field of the output so we don't need to update this test every time the output format changes. - assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) + # 3. valid parameters - supply txid and False for non-verbose + assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex']) - # 5. valid parameters - supply txid and True for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex']) + # 4. valid parameters - supply txid and 1 for verbose. + # We only check the "hex" field of the output so we don't need to update this test every time the output format changes. + assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) + + # 5. valid parameters - supply txid and True for non-verbose + assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex']) + else: + # Without -txindex, expect to raise. + for verbose in [None, 0, False, 1, True]: + assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txId, verbose) # 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose for value in ["True", "False"]: @@ -145,10 +156,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert 'in_active_chain' not in gottx else: self.log.info("Test getrawtransaction without -txindex, without blockhash: expect the call to raise") - err_msg = ( - "No such mempool transaction. Use -txindex or provide a block hash to enable" - " blockchain transaction queries. Use gettransaction for wallet transactions." - ) assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txid=tx, verbose=True) # We should not get the tx if we provide an unrelated block assert_raises_rpc_error(-5, "No such transaction found", self.nodes[n].getrawtransaction, txid=tx, blockhash=block2) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 32497ec96e..e833128063 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -198,6 +198,7 @@ BASE_SCRIPTS = [ 'wallet_keypool.py --legacy-wallet', 'wallet_keypool.py --descriptors', 'wallet_descriptor.py --descriptors', + 'feature_maxtipage.py', 'p2p_nobloomfilter_messages.py', 'p2p_filter.py', 'rpc_setban.py', diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index 393278bd6a..c557f2de92 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -85,10 +85,6 @@ implicit-signed-integer-truncation:addrman.cpp implicit-signed-integer-truncation:addrman.h implicit-signed-integer-truncation:chain.h implicit-signed-integer-truncation:crypto/ -implicit-signed-integer-truncation:node/miner.cpp -implicit-signed-integer-truncation:net.cpp -implicit-signed-integer-truncation:streams.h -implicit-signed-integer-truncation:torcontrol.cpp implicit-unsigned-integer-truncation:crypto/ shift-base:arith_uint256.cpp shift-base:crypto/ |