aboutsummaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/test')
-rw-r--r--src/test/.gitignore2
-rw-r--r--src/test/README.md53
-rw-r--r--src/test/addrman_tests.cpp28
-rw-r--r--src/test/argsman_tests.cpp4
-rw-r--r--src/test/blockencodings_tests.cpp10
-rw-r--r--src/test/bswap_tests.cpp6
-rw-r--r--src/test/coinstatsindex_tests.cpp2
-rw-r--r--src/test/common_url_tests.cpp75
-rw-r--r--src/test/compress_tests.cpp33
-rw-r--r--src/test/crypto_tests.cpp22
-rw-r--r--src/test/data/tx_valid.json4
-rw-r--r--src/test/denialofservice_tests.cpp2
-rw-r--r--src/test/feefrac_tests.cpp85
-rw-r--r--src/test/fuzz/addition_overflow.cpp10
-rw-r--r--src/test/fuzz/addrman.cpp25
-rw-r--r--src/test/fuzz/banman.cpp12
-rw-r--r--src/test/fuzz/bip324.cpp2
-rw-r--r--src/test/fuzz/descriptor_parse.cpp2
-rw-r--r--src/test/fuzz/deserialize.cpp1
-rw-r--r--src/test/fuzz/feefrac.cpp123
-rw-r--r--src/test/fuzz/feeratediagram.cpp133
-rw-r--r--src/test/fuzz/fuzz.cpp11
-rw-r--r--src/test/fuzz/headerssync.cpp2
-rw-r--r--src/test/fuzz/integer.cpp2
-rw-r--r--src/test/fuzz/key.cpp2
-rw-r--r--src/test/fuzz/key_io.cpp2
-rw-r--r--src/test/fuzz/message.cpp2
-rw-r--r--src/test/fuzz/miniscript.cpp44
-rw-r--r--src/test/fuzz/multiplication_overflow.cpp6
-rw-r--r--src/test/fuzz/net.cpp37
-rw-r--r--src/test/fuzz/net_permissions.cpp6
-rw-r--r--src/test/fuzz/netaddress.cpp12
-rw-r--r--src/test/fuzz/p2p_transport_serialization.cpp3
-rw-r--r--src/test/fuzz/package_eval.cpp55
-rw-r--r--src/test/fuzz/partially_downloaded_block.cpp6
-rw-r--r--src/test/fuzz/poolresource.cpp4
-rw-r--r--src/test/fuzz/process_message.cpp4
-rw-r--r--src/test/fuzz/process_messages.cpp4
-rw-r--r--src/test/fuzz/rbf.cpp141
-rw-r--r--src/test/fuzz/script_bitcoin_consensus.cpp50
-rw-r--r--src/test/fuzz/script_sign.cpp2
-rw-r--r--src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp3
-rw-r--r--src/test/fuzz/string.cpp2
-rw-r--r--src/test/fuzz/timedata.cpp31
-rw-r--r--src/test/fuzz/timeoffsets.cpp28
-rw-r--r--src/test/fuzz/tx_pool.cpp23
-rw-r--r--src/test/fuzz/txorphan.cpp75
-rw-r--r--src/test/fuzz/util.cpp2
-rw-r--r--src/test/fuzz/util/descriptor.cpp2
-rw-r--r--src/test/fuzz/util/net.cpp78
-rw-r--r--src/test/fuzz/util/net.h10
-rw-r--r--src/test/i2p_tests.cpp21
-rw-r--r--src/test/ipc_test.capnp18
-rw-r--r--src/test/ipc_test.cpp67
-rw-r--r--src/test/ipc_test.h21
-rw-r--r--src/test/ipc_tests.cpp13
-rw-r--r--src/test/main.cpp7
-rw-r--r--src/test/mempool_tests.cpp16
-rw-r--r--src/test/miner_tests.cpp1
-rw-r--r--src/test/miniscript_tests.cpp1
-rw-r--r--src/test/net_tests.cpp5
-rw-r--r--src/test/netbase_tests.cpp18
-rw-r--r--src/test/orphanage_tests.cpp204
-rw-r--r--src/test/peerman_tests.cpp76
-rw-r--r--src/test/policyestimator_tests.cpp20
-rw-r--r--src/test/rbf_tests.cpp478
-rw-r--r--src/test/rpc_tests.cpp69
-rw-r--r--src/test/sanity_tests.cpp2
-rw-r--r--src/test/scheduler_tests.cpp10
-rw-r--r--src/test/script_tests.cpp248
-rw-r--r--src/test/serfloat_tests.cpp116
-rw-r--r--src/test/settings_tests.cpp4
-rw-r--r--src/test/span_tests.cpp2
-rw-r--r--src/test/streams_tests.cpp13
-rw-r--r--src/test/system_tests.cpp48
-rw-r--r--src/test/timedata_tests.cpp105
-rw-r--r--src/test/timeoffsets_tests.cpp69
-rw-r--r--src/test/transaction_tests.cpp2
-rw-r--r--src/test/txindex_tests.cpp2
-rw-r--r--src/test/txpackage_tests.cpp138
-rw-r--r--src/test/txvalidation_tests.cpp339
-rw-r--r--src/test/util/chainstate.h17
-rw-r--r--src/test/util/mining.cpp6
-rw-r--r--src/test/util/setup_common.cpp84
-rw-r--r--src/test/util/setup_common.h9
-rw-r--r--src/test/util/txmempool.cpp33
-rw-r--r--src/test/util/txmempool.h10
-rw-r--r--src/test/util_tests.cpp5
-rw-r--r--src/test/util_threadnames_tests.cpp4
-rw-r--r--src/test/validation_block_tests.cpp9
-rw-r--r--src/test/validation_chainstate_tests.cpp7
-rw-r--r--src/test/validation_chainstatemanager_tests.cpp28
-rw-r--r--src/test/validation_tests.cpp215
-rw-r--r--src/test/validationinterface_tests.cpp31
94 files changed, 2970 insertions, 904 deletions
diff --git a/src/test/.gitignore b/src/test/.gitignore
new file mode 100644
index 0000000000..036df1430c
--- /dev/null
+++ b/src/test/.gitignore
@@ -0,0 +1,2 @@
+# capnp generated files
+*.capnp.*
diff --git a/src/test/README.md b/src/test/README.md
index 90d0e7102d..bab1a28f61 100644
--- a/src/test/README.md
+++ b/src/test/README.md
@@ -15,7 +15,8 @@ that runs all of the unit tests. The main source file for the test library is fo
Unit tests will be automatically compiled if dependencies were met in `./configure`
and tests weren't explicitly disabled.
-After configuring, they can be run with `make check`.
+After configuring, they can be run with `make check`, which includes unit tests from
+subtrees, or `make && make -C src check-unit` for just the unit tests.
To run the unit tests manually, launch `src/test/test_bitcoin`. To recompile
after a test file was modified, run `make` and then run the test again. If you
@@ -41,17 +42,18 @@ test_bitcoin --log_level=all --run_test=getarg_tests
```
`log_level` controls the verbosity of the test framework, which logs when a
-test case is entered, for example. `test_bitcoin` also accepts the command
-line arguments accepted by `bitcoind`. Use `--` to separate both types of
-arguments:
+test case is entered, for example.
+
+`test_bitcoin` also accepts some of the command line arguments accepted by
+`bitcoind`. Use `--` to separate these sets of arguments:
```bash
test_bitcoin --log_level=all --run_test=getarg_tests -- -printtoconsole=1
```
-The `-printtoconsole=1` after the two dashes redirects the debug log, which
-would normally go to a file in the test datadir
-(`BasicTestingSetup::m_path_root`), to the standard terminal output.
+The `-printtoconsole=1` after the two dashes sends debug logging, which
+normally goes only to `debug.log` within the data directory, also to the
+standard terminal output.
... or to run just the doubledash test:
@@ -59,7 +61,42 @@ would normally go to a file in the test datadir
test_bitcoin --run_test=getarg_tests/doubledash
```
-Run `test_bitcoin --help` for the full list.
+`test_bitcoin` creates a temporary working (data) directory with a randomly
+generated pathname within `test_common_Bitcoin Core/`, which in turn is within
+the system's temporary directory (see
+[`temp_directory_path`](https://en.cppreference.com/w/cpp/filesystem/temp_directory_path)).
+This data directory looks like a simplified form of the standard `bitcoind` data
+directory. Its content will vary depending on the test, but it will always
+have a `debug.log` file, for example.
+
+The location of the temporary data directory can be specified with the
+`-testdatadir` option. This can make debugging easier. The directory
+path used is the argument path appended with
+`/test_common_Bitcoin Core/<test-name>/datadir`.
+The directory path is created if necessary.
+Specifying this argument also causes the data directory
+not to be removed after the last test. This is useful for looking at
+what the test wrote to `debug.log` after it completes, for example.
+(The directory is removed at the start of the next test run,
+so no leftover state is used.)
+
+```bash
+$ test_bitcoin --run_test=getarg_tests/doubledash -- -testdatadir=/somewhere/mydatadir
+Test directory (will not be deleted): "/somewhere/mydatadir/test_common_Bitcoin Core/getarg_tests/doubledash/datadir
+Running 1 test case...
+
+*** No errors detected
+$ ls -l '/somewhere/mydatadir/test_common_Bitcoin Core/getarg_tests/doubledash/datadir'
+total 8
+drwxrwxr-x 2 admin admin 4096 Nov 27 22:45 blocks
+-rw-rw-r-- 1 admin admin 1003 Nov 27 22:45 debug.log
+```
+
+If you run an entire test suite, such as `--run_test=getarg_tests`, or all the test suites
+(by not specifying `--run_test`), a separate directory
+will be created for each individual test.
+
+Run `test_bitcoin --help` for the full list of tests.
### Adding test cases
diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp
index 5bd4f271cd..9668a85484 100644
--- a/src/test/addrman_tests.cpp
+++ b/src/test/addrman_tests.cpp
@@ -1068,7 +1068,7 @@ BOOST_AUTO_TEST_CASE(load_addrman_corrupted)
BOOST_AUTO_TEST_CASE(addrman_update_address)
{
- // Tests updating nTime via Connected() and nServices via SetServices()
+ // Tests updating nTime via Connected() and nServices via SetServices() and Add()
auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node));
CNetAddr source{ResolveIP("252.2.2.2")};
CAddress addr{CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE)};
@@ -1095,6 +1095,32 @@ BOOST_AUTO_TEST_CASE(addrman_update_address)
BOOST_CHECK_EQUAL(vAddr2.size(), 1U);
BOOST_CHECK(vAddr2.at(0).nTime >= start_time + 10000s);
BOOST_CHECK_EQUAL(vAddr2.at(0).nServices, NODE_NETWORK_LIMITED);
+
+ // Updating an existing addr through Add() (used in gossip relay) can add additional services but can't remove existing ones.
+ CAddress addr_v2{CAddress(ResolveService("250.1.1.1", 8333), NODE_P2P_V2)};
+ addr_v2.nTime = start_time;
+ BOOST_CHECK(!addrman->Add({addr_v2}, source));
+ std::vector<CAddress> vAddr3{addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt)};
+ BOOST_CHECK_EQUAL(vAddr3.size(), 1U);
+ BOOST_CHECK_EQUAL(vAddr3.at(0).nServices, NODE_P2P_V2 | NODE_NETWORK_LIMITED);
+
+ // SetServices() (used when we connected to them) overwrites existing service flags
+ addrman->SetServices(addr, NODE_NETWORK);
+ std::vector<CAddress> vAddr4{addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt)};
+ BOOST_CHECK_EQUAL(vAddr4.size(), 1U);
+ BOOST_CHECK_EQUAL(vAddr4.at(0).nServices, NODE_NETWORK);
+
+ // Promoting to Tried does not affect the service flags
+ BOOST_CHECK(addrman->Good(addr)); // addr has NODE_NONE
+ std::vector<CAddress> vAddr5{addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt)};
+ BOOST_CHECK_EQUAL(vAddr5.size(), 1U);
+ BOOST_CHECK_EQUAL(vAddr5.at(0).nServices, NODE_NETWORK);
+
+ // Adding service flags even works when the addr is in Tried
+ BOOST_CHECK(!addrman->Add({addr_v2}, source));
+ std::vector<CAddress> vAddr6{addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt)};
+ BOOST_CHECK_EQUAL(vAddr6.size(), 1U);
+ BOOST_CHECK_EQUAL(vAddr6.at(0).nServices, NODE_NETWORK | NODE_P2P_V2);
}
BOOST_AUTO_TEST_CASE(addrman_size)
diff --git a/src/test/argsman_tests.cpp b/src/test/argsman_tests.cpp
index 1f46efe464..340208a1c9 100644
--- a/src/test/argsman_tests.cpp
+++ b/src/test/argsman_tests.cpp
@@ -904,7 +904,7 @@ BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup)
// If check below fails, should manually dump the results with:
//
- // ARGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ArgsMerge
+ // ARGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=argsman_tests/util_ArgsMerge
//
// And verify diff against previous results to make sure the changes are expected.
//
@@ -1007,7 +1007,7 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup)
// If check below fails, should manually dump the results with:
//
- // CHAIN_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ChainMerge
+ // CHAIN_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=argsman_tests/util_ChainMerge
//
// And verify diff against previous results to make sure the changes are expected.
//
diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp
index 763f0f897e..05355fb21d 100644
--- a/src/test/blockencodings_tests.cpp
+++ b/src/test/blockencodings_tests.cpp
@@ -14,7 +14,7 @@
#include <boost/test/unit_test.hpp>
-std::vector<std::pair<uint256, CTransactionRef>> extra_txn;
+std::vector<CTransactionRef> extra_txn;
BOOST_FIXTURE_TEST_SUITE(blockencodings_tests, RegTestingSetup)
@@ -126,7 +126,7 @@ public:
explicit TestHeaderAndShortIDs(const CBlock& block) :
TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs{block}) {}
- uint64_t GetShortID(const uint256& txhash) const {
+ uint64_t GetShortID(const Wtxid& txhash) const {
DataStream stream{};
stream << *this;
CBlockHeaderAndShortTxIDs base;
@@ -155,8 +155,8 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
shortIDs.prefilledtxn.resize(1);
shortIDs.prefilledtxn[0] = {1, block.vtx[1]};
shortIDs.shorttxids.resize(2);
- shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[0]->GetHash());
- shortIDs.shorttxids[1] = shortIDs.GetShortID(block.vtx[2]->GetHash());
+ shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[0]->GetWitnessHash());
+ shortIDs.shorttxids[1] = shortIDs.GetShortID(block.vtx[2]->GetWitnessHash());
DataStream stream{};
stream << shortIDs;
@@ -226,7 +226,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
shortIDs.prefilledtxn[0] = {0, block.vtx[0]};
shortIDs.prefilledtxn[1] = {1, block.vtx[2]}; // id == 1 as it is 1 after index 1
shortIDs.shorttxids.resize(1);
- shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[1]->GetHash());
+ shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[1]->GetWitnessHash());
DataStream stream{};
stream << shortIDs;
diff --git a/src/test/bswap_tests.cpp b/src/test/bswap_tests.cpp
index 2be7122fc1..fe48e39c41 100644
--- a/src/test/bswap_tests.cpp
+++ b/src/test/bswap_tests.cpp
@@ -16,9 +16,9 @@ BOOST_AUTO_TEST_CASE(bswap_tests)
uint16_t e1 = 0x3412;
uint32_t e2 = 0xbc9a7856;
uint64_t e3 = 0xbc9a78563412f0de;
- BOOST_CHECK(bswap_16(u1) == e1);
- BOOST_CHECK(bswap_32(u2) == e2);
- BOOST_CHECK(bswap_64(u3) == e3);
+ BOOST_CHECK(internal_bswap_16(u1) == e1);
+ BOOST_CHECK(internal_bswap_32(u2) == e2);
+ BOOST_CHECK(internal_bswap_64(u3) == e3);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/coinstatsindex_tests.cpp b/src/test/coinstatsindex_tests.cpp
index cc1ec49d41..08814c1499 100644
--- a/src/test/coinstatsindex_tests.cpp
+++ b/src/test/coinstatsindex_tests.cpp
@@ -70,7 +70,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
// SyncWithValidationInterfaceQueue() call below is also needed to ensure
// TSAN always sees the test thread waiting for the notification thread, and
// avoid potential false positive reports.
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
// Shutdown sequence (c.f. Shutdown() in init.cpp)
coin_stats_index.Stop();
diff --git a/src/test/common_url_tests.cpp b/src/test/common_url_tests.cpp
new file mode 100644
index 0000000000..065c7d97bc
--- /dev/null
+++ b/src/test/common_url_tests.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) 2024-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or https://opensource.org/license/mit/.
+
+#include <common/url.h>
+
+#include <string>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_SUITE(common_url_tests)
+
+// These test vectors were ported from test/regress.c in the libevent library
+// which used to be a dependency of the UrlDecode function.
+
+BOOST_AUTO_TEST_CASE(encode_decode_test) {
+ BOOST_CHECK_EQUAL(UrlDecode("Hello"), "Hello");
+ BOOST_CHECK_EQUAL(UrlDecode("99"), "99");
+ BOOST_CHECK_EQUAL(UrlDecode(""), "");
+ BOOST_CHECK_EQUAL(UrlDecode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-.~_"),
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-.~_");
+ BOOST_CHECK_EQUAL(UrlDecode("%20"), " ");
+ BOOST_CHECK_EQUAL(UrlDecode("%FF%F0%E0"), "\xff\xf0\xe0");
+ BOOST_CHECK_EQUAL(UrlDecode("%01%19"), "\x01\x19");
+ BOOST_CHECK_EQUAL(UrlDecode("http%3A%2F%2Fwww.ietf.org%2Frfc%2Frfc3986.txt"),
+ "http://www.ietf.org/rfc/rfc3986.txt");
+ BOOST_CHECK_EQUAL(UrlDecode("1%2B2%3D3"), "1+2=3");
+}
+
+BOOST_AUTO_TEST_CASE(decode_malformed_test) {
+ BOOST_CHECK_EQUAL(UrlDecode("%%xhello th+ere \xff"), "%%xhello th+ere \xff");
+
+ BOOST_CHECK_EQUAL(UrlDecode("%"), "%");
+ BOOST_CHECK_EQUAL(UrlDecode("%%"), "%%");
+ BOOST_CHECK_EQUAL(UrlDecode("%%%"), "%%%");
+ BOOST_CHECK_EQUAL(UrlDecode("%%%%"), "%%%%");
+
+ BOOST_CHECK_EQUAL(UrlDecode("+"), "+");
+ BOOST_CHECK_EQUAL(UrlDecode("++"), "++");
+
+ BOOST_CHECK_EQUAL(UrlDecode("?"), "?");
+ BOOST_CHECK_EQUAL(UrlDecode("??"), "??");
+
+ BOOST_CHECK_EQUAL(UrlDecode("%G1"), "%G1");
+ BOOST_CHECK_EQUAL(UrlDecode("%2"), "%2");
+ BOOST_CHECK_EQUAL(UrlDecode("%ZX"), "%ZX");
+
+ BOOST_CHECK_EQUAL(UrlDecode("valid%20string%G1"), "valid string%G1");
+ BOOST_CHECK_EQUAL(UrlDecode("%20invalid%ZX"), " invalid%ZX");
+ BOOST_CHECK_EQUAL(UrlDecode("%20%G1%ZX"), " %G1%ZX");
+
+ BOOST_CHECK_EQUAL(UrlDecode("%1 "), "%1 ");
+ BOOST_CHECK_EQUAL(UrlDecode("% 9"), "% 9");
+ BOOST_CHECK_EQUAL(UrlDecode(" %Z "), " %Z ");
+ BOOST_CHECK_EQUAL(UrlDecode(" % X"), " % X");
+
+ BOOST_CHECK_EQUAL(UrlDecode("%%ffg"), "%\xffg");
+ BOOST_CHECK_EQUAL(UrlDecode("%fg"), "%fg");
+
+ BOOST_CHECK_EQUAL(UrlDecode("%-1"), "%-1");
+ BOOST_CHECK_EQUAL(UrlDecode("%1-"), "%1-");
+}
+
+BOOST_AUTO_TEST_CASE(decode_lowercase_hex_test) {
+ BOOST_CHECK_EQUAL(UrlDecode("%f0%a0%b0"), "\xf0\xa0\xb0");
+}
+
+BOOST_AUTO_TEST_CASE(decode_internal_nulls_test) {
+ std::string result1{"\0\0x\0\0", 5};
+ BOOST_CHECK_EQUAL(UrlDecode("%00%00x%00%00"), result1);
+ std::string result2{"abc\0\0", 5};
+ BOOST_CHECK_EQUAL(UrlDecode("abc%00%00"), result2);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/compress_tests.cpp b/src/test/compress_tests.cpp
index 264b47b07c..13c2740553 100644
--- a/src/test/compress_tests.cpp
+++ b/src/test/compress_tests.cpp
@@ -4,6 +4,7 @@
#include <compressor.h>
#include <script/script.h>
+#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <stdint.h>
@@ -131,4 +132,36 @@ BOOST_AUTO_TEST_CASE(compress_script_to_uncompressed_pubkey_id)
BOOST_CHECK_EQUAL(out[0], 0x04 | (script[65] & 0x01)); // least significant bit (lsb) of last char of pubkey is mapped into out[0]
}
+BOOST_AUTO_TEST_CASE(compress_p2pk_scripts_not_on_curve)
+{
+ XOnlyPubKey x_not_on_curve;
+ do {
+ x_not_on_curve = XOnlyPubKey(g_insecure_rand_ctx.randbytes(32));
+ } while (x_not_on_curve.IsFullyValid());
+
+ // Check that P2PK script with uncompressed pubkey [=> OP_PUSH65 <0x04 .....> OP_CHECKSIG]
+ // which is not fully valid (i.e. point is not on curve) can't be compressed
+ std::vector<unsigned char> pubkey_raw(65, 0);
+ pubkey_raw[0] = 4;
+ std::copy(x_not_on_curve.begin(), x_not_on_curve.end(), &pubkey_raw[1]);
+ CPubKey pubkey_not_on_curve(pubkey_raw);
+ assert(pubkey_not_on_curve.IsValid());
+ assert(!pubkey_not_on_curve.IsFullyValid());
+ CScript script = CScript() << ToByteVector(pubkey_not_on_curve) << OP_CHECKSIG;
+ BOOST_CHECK_EQUAL(script.size(), 67U);
+
+ CompressedScript out;
+ bool done = CompressScript(script, out);
+ BOOST_CHECK_EQUAL(done, false);
+
+ // Check that compressed P2PK script with uncompressed pubkey that is not fully
+ // valid (i.e. x coordinate of the pubkey is not on curve) can't be decompressed
+ CompressedScript compressed_script(x_not_on_curve.begin(), x_not_on_curve.end());
+ for (unsigned int compression_id : {4, 5}) {
+ CScript uncompressed_script;
+ bool success = DecompressScript(uncompressed_script, compression_id, compressed_script);
+ BOOST_CHECK_EQUAL(success, false);
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp
index 0a6378adf4..46acc6fc9f 100644
--- a/src/test/crypto_tests.cpp
+++ b/src/test/crypto_tests.cpp
@@ -1060,28 +1060,6 @@ BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests)
"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d");
}
-BOOST_AUTO_TEST_CASE(countbits_tests)
-{
- FastRandomContext ctx;
- for (unsigned int i = 0; i <= 64; ++i) {
- if (i == 0) {
- // Check handling of zero.
- BOOST_CHECK_EQUAL(CountBits(0), 0U);
- } else if (i < 10) {
- for (uint64_t j = uint64_t{1} << (i - 1); (j >> i) == 0; ++j) {
- // Exhaustively test up to 10 bits
- BOOST_CHECK_EQUAL(CountBits(j), i);
- }
- } else {
- for (int k = 0; k < 1000; k++) {
- // Randomly test 1000 samples of each length above 10 bits.
- uint64_t j = (uint64_t{1}) << (i - 1) | ctx.randbits(i - 1);
- BOOST_CHECK_EQUAL(CountBits(j), i);
- }
- }
- }
-}
-
BOOST_AUTO_TEST_CASE(sha256d64)
{
for (int i = 0; i <= 32; ++i) {
diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json
index b874f6f26c..70df0d0f69 100644
--- a/src/test/data/tx_valid.json
+++ b/src/test/data/tx_valid.json
@@ -319,6 +319,10 @@
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7c17aff532f22beb54069942f9bf567a66133eaf EQUAL"]],
"0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000", "NONE"],
+["Valid CHECKSEQUENCEVERIFY even with negative tx version number"],
+[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7c17aff532f22beb54069942f9bf567a66133eaf EQUAL"]],
+"ffffffff01000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000", "NONE"],
+
["Valid P2WPKH (Private key of segwit tests is L5AQtV2HDm4xGsseLokK2VAT2EtYKcTm3c7HwqnJBFt9LdaQULsM)"],
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1000]],
"0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000", "NONE"],
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index 0fef8c5906..5e9ae78681 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -16,7 +16,6 @@
#include <test/util/net.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
-#include <timedata.h>
#include <util/string.h>
#include <util/time.h>
#include <validation.h>
@@ -72,7 +71,6 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
/*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
/*version=*/PROTOCOL_VERSION,
/*relay_txs=*/true);
- TestOnlyResetTimeData();
// This test requires that we have a chain with non-zero work.
{
diff --git a/src/test/feefrac_tests.cpp b/src/test/feefrac_tests.cpp
new file mode 100644
index 0000000000..5af3c3d7ed
--- /dev/null
+++ b/src/test/feefrac_tests.cpp
@@ -0,0 +1,85 @@
+// Copyright (c) 2024-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <util/feefrac.h>
+#include <random.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_SUITE(feefrac_tests)
+
+BOOST_AUTO_TEST_CASE(feefrac_operators)
+{
+ FeeFrac p1{1000, 100}, p2{500, 300};
+ FeeFrac sum{1500, 400};
+ FeeFrac diff{500, -200};
+ FeeFrac empty{0, 0};
+ FeeFrac zero_fee{0, 1}; // zero-fee allowed
+
+ BOOST_CHECK(empty == FeeFrac{}); // same as no-args
+
+ BOOST_CHECK(p1 == p1);
+ BOOST_CHECK(p1 + p2 == sum);
+ BOOST_CHECK(p1 - p2 == diff);
+
+ FeeFrac p3{2000, 200};
+ BOOST_CHECK(p1 != p3); // feefracs only equal if both fee and size are same
+ BOOST_CHECK(p2 != p3);
+
+ FeeFrac p4{3000, 300};
+ BOOST_CHECK(p1 == p4-p3);
+ BOOST_CHECK(p1 + p3 == p4);
+
+ // Fee-rate comparison
+ BOOST_CHECK(p1 > p2);
+ BOOST_CHECK(p1 >= p2);
+ BOOST_CHECK(p1 >= p4-p3);
+ BOOST_CHECK(!(p1 >> p3)); // not strictly better
+ BOOST_CHECK(p1 >> p2); // strictly greater feerate
+
+ BOOST_CHECK(p2 < p1);
+ BOOST_CHECK(p2 <= p1);
+ BOOST_CHECK(p1 <= p4-p3);
+ BOOST_CHECK(!(p3 << p1)); // not strictly worse
+ BOOST_CHECK(p2 << p1); // strictly lower feerate
+
+ // "empty" comparisons
+ BOOST_CHECK(!(p1 >> empty)); // << will always result in false
+ BOOST_CHECK(!(p1 << empty));
+ BOOST_CHECK(!(empty >> empty));
+ BOOST_CHECK(!(empty << empty));
+
+ // empty is always bigger than everything else
+ BOOST_CHECK(empty > p1);
+ BOOST_CHECK(empty > p2);
+ BOOST_CHECK(empty > p3);
+ BOOST_CHECK(empty >= p1);
+ BOOST_CHECK(empty >= p2);
+ BOOST_CHECK(empty >= p3);
+
+ // check "max" values for comparison
+ FeeFrac oversized_1{4611686000000, 4000000};
+ FeeFrac oversized_2{184467440000000, 100000};
+
+ BOOST_CHECK(oversized_1 < oversized_2);
+ BOOST_CHECK(oversized_1 <= oversized_2);
+ BOOST_CHECK(oversized_1 << oversized_2);
+ BOOST_CHECK(oversized_1 != oversized_2);
+
+ // Tests paths that use double arithmetic
+ FeeFrac busted{(static_cast<int64_t>(INT32_MAX)) + 1, INT32_MAX};
+ BOOST_CHECK(!(busted < busted));
+
+ FeeFrac max_fee{2100000000000000, INT32_MAX};
+ BOOST_CHECK(!(max_fee < max_fee));
+ BOOST_CHECK(!(max_fee > max_fee));
+ BOOST_CHECK(max_fee <= max_fee);
+ BOOST_CHECK(max_fee >= max_fee);
+
+ FeeFrac max_fee2{1, 1};
+ BOOST_CHECK(max_fee >= max_fee2);
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/fuzz/addition_overflow.cpp b/src/test/fuzz/addition_overflow.cpp
index 7b84bfda20..071e5fb029 100644
--- a/src/test/fuzz/addition_overflow.cpp
+++ b/src/test/fuzz/addition_overflow.cpp
@@ -11,14 +11,6 @@
#include <string>
#include <vector>
-#if defined(__has_builtin)
-#if __has_builtin(__builtin_add_overflow)
-#define HAVE_BUILTIN_ADD_OVERFLOW
-#endif
-#elif defined(__GNUC__)
-#define HAVE_BUILTIN_ADD_OVERFLOW
-#endif
-
namespace {
template <typename T>
void TestAdditionOverflow(FuzzedDataProvider& fuzzed_data_provider)
@@ -32,7 +24,7 @@ void TestAdditionOverflow(FuzzedDataProvider& fuzzed_data_provider)
assert(is_addition_overflow_custom == AdditionOverflow(j, i));
assert(maybe_add == CheckedAdd(j, i));
assert(sat_add == SaturatingAdd(j, i));
-#if defined(HAVE_BUILTIN_ADD_OVERFLOW)
+#ifndef _MSC_VER
T result_builtin;
const bool is_addition_overflow_builtin = __builtin_add_overflow(i, j, &result_builtin);
assert(is_addition_overflow_custom == is_addition_overflow_builtin);
diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp
index ece396aadf..8a54cc656d 100644
--- a/src/test/fuzz/addrman.cpp
+++ b/src/test/fuzz/addrman.cpp
@@ -64,26 +64,13 @@ FUZZ_TARGET(data_stream_addr_man, .init = initialize_addrman)
CNetAddr RandAddr(FuzzedDataProvider& fuzzed_data_provider, FastRandomContext& fast_random_context)
{
CNetAddr addr;
- if (fuzzed_data_provider.remaining_bytes() > 1 && fuzzed_data_provider.ConsumeBool()) {
- addr = ConsumeNetAddr(fuzzed_data_provider);
- } else {
- // The networks [1..6] correspond to CNetAddr::BIP155Network (private).
- static const std::map<uint8_t, uint8_t> net_len_map = {{1, ADDR_IPV4_SIZE},
- {2, ADDR_IPV6_SIZE},
- {4, ADDR_TORV3_SIZE},
- {5, ADDR_I2P_SIZE},
- {6, ADDR_CJDNS_SIZE}};
- uint8_t net = fast_random_context.randrange(5) + 1; // [1..5]
- if (net == 3) {
- net = 6;
+ assert(!addr.IsValid());
+ for (size_t i = 0; i < 8 && !addr.IsValid(); ++i) {
+ if (fuzzed_data_provider.remaining_bytes() > 1 && fuzzed_data_provider.ConsumeBool()) {
+ addr = ConsumeNetAddr(fuzzed_data_provider);
+ } else {
+ addr = ConsumeNetAddr(fuzzed_data_provider, &fast_random_context);
}
-
- DataStream s{};
-
- s << net;
- s << fast_random_context.randbytes(net_len_map.at(net));
-
- s >> CAddress::V2_NETWORK(addr);
}
// Return a dummy IPv4 5.5.5.5 if we generated an invalid address.
diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp
index 4a040c56de..b26151f63c 100644
--- a/src/test/fuzz/banman.cpp
+++ b/src/test/fuzz/banman.cpp
@@ -70,11 +70,13 @@ FUZZ_TARGET(banman, .init = initialize_banman)
fuzzed_data_provider,
[&] {
CNetAddr net_addr{ConsumeNetAddr(fuzzed_data_provider)};
- const std::optional<CNetAddr>& addr{LookupHost(net_addr.ToStringAddr(), /*fAllowLookup=*/false)};
- if (addr.has_value() && addr->IsValid()) {
- net_addr = *addr;
- } else {
- contains_invalid = true;
+ if (!net_addr.IsCJDNS() || !net_addr.IsValid()) {
+ const std::optional<CNetAddr>& addr{LookupHost(net_addr.ToStringAddr(), /*fAllowLookup=*/false)};
+ if (addr.has_value() && addr->IsValid()) {
+ net_addr = *addr;
+ } else {
+ contains_invalid = true;
+ }
}
ban_man.Ban(net_addr, ConsumeBanTimeOffset(fuzzed_data_provider), fuzzed_data_provider.ConsumeBool());
},
diff --git a/src/test/fuzz/bip324.cpp b/src/test/fuzz/bip324.cpp
index 37c41f3895..8210e75cee 100644
--- a/src/test/fuzz/bip324.cpp
+++ b/src/test/fuzz/bip324.cpp
@@ -17,7 +17,7 @@ namespace {
void Initialize()
{
- ECC_Start();
+ static ECC_Context ecc_context{};
SelectParams(ChainType::MAIN);
}
diff --git a/src/test/fuzz/descriptor_parse.cpp b/src/test/fuzz/descriptor_parse.cpp
index 6ea315d4e2..b9a5560ffb 100644
--- a/src/test/fuzz/descriptor_parse.cpp
+++ b/src/test/fuzz/descriptor_parse.cpp
@@ -55,7 +55,7 @@ static void TestDescriptor(const Descriptor& desc, FlatSigningProvider& sig_prov
void initialize_descriptor_parse()
{
- ECC_Start();
+ static ECC_Context ecc_context{};
SelectParams(ChainType::MAIN);
}
diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp
index ebc5673e71..c9a3bc86ac 100644
--- a/src/test/fuzz/deserialize.cpp
+++ b/src/test/fuzz/deserialize.cpp
@@ -33,7 +33,6 @@
#include <optional>
#include <stdexcept>
#include <stdint.h>
-#include <unistd.h>
using node::SnapshotMetadata;
diff --git a/src/test/fuzz/feefrac.cpp b/src/test/fuzz/feefrac.cpp
new file mode 100644
index 0000000000..2c7553360e
--- /dev/null
+++ b/src/test/fuzz/feefrac.cpp
@@ -0,0 +1,123 @@
+// Copyright (c) 2024 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <util/feefrac.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <compare>
+#include <cstdint>
+#include <iostream>
+
+namespace {
+
+/** Compute a * b, represented in 4x32 bits, highest limb first. */
+std::array<uint32_t, 4> Mul128(uint64_t a, uint64_t b)
+{
+ std::array<uint32_t, 4> ret{0, 0, 0, 0};
+
+ /** Perform ret += v << (32 * pos), at 128-bit precision. */
+ auto add_fn = [&](uint64_t v, int pos) {
+ uint64_t accum{0};
+ for (int i = 0; i + pos < 4; ++i) {
+ // Add current value at limb pos in ret.
+ accum += ret[3 - pos - i];
+ // Add low or high half of v.
+ if (i == 0) accum += v & 0xffffffff;
+ if (i == 1) accum += v >> 32;
+ // Store lower half of result in limb pos in ret.
+ ret[3 - pos - i] = accum & 0xffffffff;
+ // Leave carry in accum.
+ accum >>= 32;
+ }
+ // Make sure no overflow.
+ assert(accum == 0);
+ };
+
+ // Multiply the 4 individual limbs (schoolbook multiply, with base 2^32).
+ add_fn((a & 0xffffffff) * (b & 0xffffffff), 0);
+ add_fn((a >> 32) * (b & 0xffffffff), 1);
+ add_fn((a & 0xffffffff) * (b >> 32), 1);
+ add_fn((a >> 32) * (b >> 32), 2);
+ return ret;
+}
+
+/* comparison helper for std::array */
+std::strong_ordering compare_arrays(const std::array<uint32_t, 4>& a, const std::array<uint32_t, 4>& b) {
+ for (size_t i = 0; i < a.size(); ++i) {
+ if (a[i] != b[i]) return a[i] <=> b[i];
+ }
+ return std::strong_ordering::equal;
+}
+
+std::strong_ordering MulCompare(int64_t a1, int64_t a2, int64_t b1, int64_t b2)
+{
+ // Compute and compare signs.
+ int sign_a = (a1 == 0 ? 0 : a1 < 0 ? -1 : 1) * (a2 == 0 ? 0 : a2 < 0 ? -1 : 1);
+ int sign_b = (b1 == 0 ? 0 : b1 < 0 ? -1 : 1) * (b2 == 0 ? 0 : b2 < 0 ? -1 : 1);
+ if (sign_a != sign_b) return sign_a <=> sign_b;
+
+ // Compute absolute values.
+ uint64_t abs_a1 = static_cast<uint64_t>(a1), abs_a2 = static_cast<uint64_t>(a2);
+ uint64_t abs_b1 = static_cast<uint64_t>(b1), abs_b2 = static_cast<uint64_t>(b2);
+ // Use (~x + 1) instead of the equivalent (-x) to silence the linter; mod 2^64 behavior is
+ // intentional here.
+ if (a1 < 0) abs_a1 = ~abs_a1 + 1;
+ if (a2 < 0) abs_a2 = ~abs_a2 + 1;
+ if (b1 < 0) abs_b1 = ~abs_b1 + 1;
+ if (b2 < 0) abs_b2 = ~abs_b2 + 1;
+
+ // Compute products of absolute values.
+ auto mul_abs_a = Mul128(abs_a1, abs_a2);
+ auto mul_abs_b = Mul128(abs_b1, abs_b2);
+ if (sign_a < 0) {
+ return compare_arrays(mul_abs_b, mul_abs_a);
+ } else {
+ return compare_arrays(mul_abs_a, mul_abs_b);
+ }
+}
+
+} // namespace
+
+FUZZ_TARGET(feefrac)
+{
+ FuzzedDataProvider provider(buffer.data(), buffer.size());
+
+ int64_t f1 = provider.ConsumeIntegral<int64_t>();
+ int32_t s1 = provider.ConsumeIntegral<int32_t>();
+ if (s1 == 0) f1 = 0;
+ FeeFrac fr1(f1, s1);
+ assert(fr1.IsEmpty() == (s1 == 0));
+
+ int64_t f2 = provider.ConsumeIntegral<int64_t>();
+ int32_t s2 = provider.ConsumeIntegral<int32_t>();
+ if (s2 == 0) f2 = 0;
+ FeeFrac fr2(f2, s2);
+ assert(fr2.IsEmpty() == (s2 == 0));
+
+ // Feerate comparisons
+ auto cmp_feerate = MulCompare(f1, s2, f2, s1);
+ assert(FeeRateCompare(fr1, fr2) == cmp_feerate);
+ assert((fr1 << fr2) == std::is_lt(cmp_feerate));
+ assert((fr1 >> fr2) == std::is_gt(cmp_feerate));
+
+ // Compare with manual invocation of FeeFrac::Mul.
+ auto cmp_mul = FeeFrac::Mul(f1, s2) <=> FeeFrac::Mul(f2, s1);
+ assert(cmp_mul == cmp_feerate);
+
+ // Same, but using FeeFrac::MulFallback.
+ auto cmp_fallback = FeeFrac::MulFallback(f1, s2) <=> FeeFrac::MulFallback(f2, s1);
+ assert(cmp_fallback == cmp_feerate);
+
+ // Total order comparisons
+ auto cmp_total = std::is_eq(cmp_feerate) ? (s2 <=> s1) : cmp_feerate;
+ assert((fr1 <=> fr2) == cmp_total);
+ assert((fr1 < fr2) == std::is_lt(cmp_total));
+ assert((fr1 > fr2) == std::is_gt(cmp_total));
+ assert((fr1 <= fr2) == std::is_lteq(cmp_total));
+ assert((fr1 >= fr2) == std::is_gteq(cmp_total));
+ assert((fr1 == fr2) == std::is_eq(cmp_total));
+ assert((fr1 != fr2) == std::is_neq(cmp_total));
+}
diff --git a/src/test/fuzz/feeratediagram.cpp b/src/test/fuzz/feeratediagram.cpp
new file mode 100644
index 0000000000..1a9c5ee946
--- /dev/null
+++ b/src/test/fuzz/feeratediagram.cpp
@@ -0,0 +1,133 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <stdint.h>
+
+#include <vector>
+
+#include <util/feefrac.h>
+#include <policy/rbf.h>
+
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <assert.h>
+
+namespace {
+
+/** Takes the pre-computed and topologically-valid chunks and generates a fee diagram which starts at FeeFrac of (0, 0) */
+std::vector<FeeFrac> BuildDiagramFromChunks(const Span<const FeeFrac> chunks)
+{
+ std::vector<FeeFrac> diagram;
+ diagram.reserve(chunks.size() + 1);
+
+ diagram.emplace_back(0, 0);
+ for (auto& chunk : chunks) {
+ diagram.emplace_back(diagram.back() + chunk);
+ }
+ return diagram;
+}
+
+
+/** Evaluate a diagram at a specific size, returning the fee as a fraction.
+ *
+ * Fees in diagram cannot exceed 2^32, as the returned evaluation could overflow
+ * the FeeFrac::fee field in the result. */
+FeeFrac EvaluateDiagram(int32_t size, Span<const FeeFrac> diagram)
+{
+ assert(diagram.size() > 0);
+ unsigned not_above = 0;
+ unsigned not_below = diagram.size() - 1;
+ // If outside the range of diagram, extend begin/end.
+ if (size < diagram[not_above].size) return {diagram[not_above].fee, 1};
+ if (size > diagram[not_below].size) return {diagram[not_below].fee, 1};
+ // Perform bisection search to locate the diagram segment that size is in.
+ while (not_below > not_above + 1) {
+ unsigned mid = (not_below + not_above) / 2;
+ if (diagram[mid].size <= size) not_above = mid;
+ if (diagram[mid].size >= size) not_below = mid;
+ }
+ // If the size matches a transition point between segments, return its fee.
+ if (not_below == not_above) return {diagram[not_below].fee, 1};
+ // Otherwise, interpolate.
+ auto dir_coef = diagram[not_below] - diagram[not_above];
+ assert(dir_coef.size > 0);
+ // Let A = diagram[not_above] and B = diagram[not_below]
+ const auto& point_a = diagram[not_above];
+ // We want to return:
+ // A.fee + (B.fee - A.fee) / (B.size - A.size) * (size - A.size)
+ // = A.fee + dir_coef.fee / dir_coef.size * (size - A.size)
+ // = (A.fee * dir_coef.size + dir_coef.fee * (size - A.size)) / dir_coef.size
+ assert(size >= point_a.size);
+ return {point_a.fee * dir_coef.size + dir_coef.fee * (size - point_a.size), dir_coef.size};
+}
+
+std::weak_ordering CompareFeeFracWithDiagram(const FeeFrac& ff, Span<const FeeFrac> diagram)
+{
+ return FeeRateCompare(FeeFrac{ff.fee, 1}, EvaluateDiagram(ff.size, diagram));
+}
+
+std::partial_ordering CompareDiagrams(Span<const FeeFrac> dia1, Span<const FeeFrac> dia2)
+{
+ bool all_ge = true;
+ bool all_le = true;
+ for (const auto p1 : dia1) {
+ auto cmp = CompareFeeFracWithDiagram(p1, dia2);
+ if (std::is_lt(cmp)) all_ge = false;
+ if (std::is_gt(cmp)) all_le = false;
+ }
+ for (const auto p2 : dia2) {
+ auto cmp = CompareFeeFracWithDiagram(p2, dia1);
+ if (std::is_lt(cmp)) all_le = false;
+ if (std::is_gt(cmp)) all_ge = false;
+ }
+ if (all_ge && all_le) return std::partial_ordering::equivalent;
+ if (all_ge && !all_le) return std::partial_ordering::greater;
+ if (!all_ge && all_le) return std::partial_ordering::less;
+ return std::partial_ordering::unordered;
+}
+
+void PopulateChunks(FuzzedDataProvider& fuzzed_data_provider, std::vector<FeeFrac>& chunks)
+{
+ chunks.clear();
+
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 50)
+ {
+ chunks.emplace_back(fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(INT32_MIN>>1, INT32_MAX>>1), fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(1, 1000000));
+ }
+ return;
+}
+
+} // namespace
+
+FUZZ_TARGET(build_and_compare_feerate_diagram)
+{
+ // Generate a random set of chunks
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+ std::vector<FeeFrac> chunks1, chunks2;
+ FeeFrac empty{0, 0};
+
+ PopulateChunks(fuzzed_data_provider, chunks1);
+ PopulateChunks(fuzzed_data_provider, chunks2);
+
+ std::vector<FeeFrac> diagram1{BuildDiagramFromChunks(chunks1)};
+ std::vector<FeeFrac> diagram2{BuildDiagramFromChunks(chunks2)};
+
+ assert(diagram1.front() == empty);
+ assert(diagram2.front() == empty);
+
+ auto real = CompareChunks(chunks1, chunks2);
+ auto sim = CompareDiagrams(diagram1, diagram2);
+ assert(real == sim);
+
+ // Do explicit evaluation at up to 1000 points, and verify consistency with the result.
+ LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 1000) {
+ int32_t size = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(0, diagram2.back().size);
+ auto eval1 = EvaluateDiagram(size, diagram1);
+ auto eval2 = EvaluateDiagram(size, diagram2);
+ auto cmp = FeeRateCompare(eval1, eval2);
+ if (std::is_lt(cmp)) assert(!std::is_gt(real));
+ if (std::is_gt(cmp)) assert(!std::is_lt(real));
+ }
+}
diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp
index 6de480ff15..f9915187bd 100644
--- a/src/test/fuzz/fuzz.cpp
+++ b/src/test/fuzz/fuzz.cpp
@@ -25,7 +25,6 @@
#include <memory>
#include <string>
#include <tuple>
-#include <unistd.h>
#include <utility>
#include <vector>
@@ -35,6 +34,8 @@ __AFL_FUZZ_INIT();
const std::function<void(const std::string&)> G_TEST_LOG_FUN{};
+const std::function<std::string()> G_TEST_GET_FULL_NAME{};
+
/**
* A copy of the command line arguments that start with `--`.
* First `LLVMFuzzerInitialize()` is called, which saves the arguments to `g_args`.
@@ -81,7 +82,7 @@ static const TypeTestOneInput* g_test_one_input{nullptr};
void initialize()
{
// Terminate immediately if a fuzzing harness ever tries to create a TCP socket.
- CreateSock = [](const CService&) -> std::unique_ptr<Sock> { std::terminate(); };
+ CreateSock = [](const sa_family_t&) -> std::unique_ptr<Sock> { std::terminate(); };
// Terminate immediately if a fuzzing harness ever tries to perform a DNS lookup.
g_dns_lookup = [](const std::string& name, bool allow_lookup) {
@@ -133,9 +134,9 @@ void initialize()
#if defined(PROVIDE_FUZZ_MAIN_FUNCTION)
static bool read_stdin(std::vector<uint8_t>& data)
{
- uint8_t buffer[1024];
- ssize_t length = 0;
- while ((length = read(STDIN_FILENO, buffer, 1024)) > 0) {
+ std::istream::char_type buffer[1024];
+ std::streamsize length;
+ while ((std::cin.read(buffer, 1024), length = std::cin.gcount()) > 0) {
data.insert(data.end(), buffer, buffer + length);
}
return length == 0;
diff --git a/src/test/fuzz/headerssync.cpp b/src/test/fuzz/headerssync.cpp
index 62f6bbaffe..1aa878bd6d 100644
--- a/src/test/fuzz/headerssync.cpp
+++ b/src/test/fuzz/headerssync.cpp
@@ -108,7 +108,7 @@ FUZZ_TARGET(headers_sync_state, .init = initialize_headers_sync_state_fuzz)
// If we get to redownloading, the presynced headers need
// to have the min amount of work on them.
- assert(CalculateHeadersWork(all_headers) >= min_work);
+ assert(CalculateClaimedHeadersWork(all_headers) >= min_work);
}
}
diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp
index 9b97958a5d..db246bb84e 100644
--- a/src/test/fuzz/integer.cpp
+++ b/src/test/fuzz/integer.cpp
@@ -80,7 +80,6 @@ FUZZ_TARGET(integer, .init = initialize_integer)
static const uint256 u256_max(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
const std::vector<uint256> v256{u256, u256_min, u256_max};
(void)ComputeMerkleRoot(v256);
- (void)CountBits(u64);
(void)DecompressAmount(u64);
{
if (std::optional<CAmount> parsed = ParseMoney(FormatMoney(i64))) {
@@ -213,7 +212,6 @@ FUZZ_TARGET(integer, .init = initialize_integer)
{
const ServiceFlags service_flags = (ServiceFlags)u64;
- (void)HasAllDesirableServiceFlags(service_flags);
(void)MayHaveUsefulAddressDB(service_flags);
}
diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp
index 9e1e318e02..d389a29575 100644
--- a/src/test/fuzz/key.cpp
+++ b/src/test/fuzz/key.cpp
@@ -32,7 +32,7 @@
void initialize_key()
{
- ECC_Start();
+ static ECC_Context ecc_context{};
SelectParams(ChainType::REGTEST);
}
diff --git a/src/test/fuzz/key_io.cpp b/src/test/fuzz/key_io.cpp
index 5f98f2b7f1..aefdefe233 100644
--- a/src/test/fuzz/key_io.cpp
+++ b/src/test/fuzz/key_io.cpp
@@ -14,7 +14,7 @@
void initialize_key_io()
{
- ECC_Start();
+ static ECC_Context ecc_context{};
SelectParams(ChainType::MAIN);
}
diff --git a/src/test/fuzz/message.cpp b/src/test/fuzz/message.cpp
index b5c95441f8..75baaa2754 100644
--- a/src/test/fuzz/message.cpp
+++ b/src/test/fuzz/message.cpp
@@ -19,7 +19,7 @@
void initialize_message()
{
- ECC_Start();
+ static ECC_Context ecc_context{};
SelectParams(ChainType::REGTEST);
}
diff --git a/src/test/fuzz/miniscript.cpp b/src/test/fuzz/miniscript.cpp
index 0d0ee59ab3..f10007222c 100644
--- a/src/test/fuzz/miniscript.cpp
+++ b/src/test/fuzz/miniscript.cpp
@@ -391,6 +391,7 @@ std::optional<NodeInfo> ConsumeNodeStable(MsCtx script_ctx, FuzzedDataProvider&
bool allow_K = (type_needed == ""_mst) || (type_needed << "K"_mst);
bool allow_V = (type_needed == ""_mst) || (type_needed << "V"_mst);
bool allow_W = (type_needed == ""_mst) || (type_needed << "W"_mst);
+ static constexpr auto B{"B"_mst}, K{"K"_mst}, V{"V"_mst}, W{"W"_mst};
switch (provider.ConsumeIntegral<uint8_t>()) {
case 0:
@@ -440,22 +441,22 @@ std::optional<NodeInfo> ConsumeNodeStable(MsCtx script_ctx, FuzzedDataProvider&
}
case 11:
if (!(allow_B || allow_K || allow_V)) return {};
- return {{{"B"_mst, type_needed, type_needed}, Fragment::ANDOR}};
+ return {{{B, type_needed, type_needed}, Fragment::ANDOR}};
case 12:
if (!(allow_B || allow_K || allow_V)) return {};
- return {{{"V"_mst, type_needed}, Fragment::AND_V}};
+ return {{{V, type_needed}, Fragment::AND_V}};
case 13:
if (!allow_B) return {};
- return {{{"B"_mst, "W"_mst}, Fragment::AND_B}};
+ return {{{B, W}, Fragment::AND_B}};
case 15:
if (!allow_B) return {};
- return {{{"B"_mst, "W"_mst}, Fragment::OR_B}};
+ return {{{B, W}, Fragment::OR_B}};
case 16:
if (!allow_V) return {};
- return {{{"B"_mst, "V"_mst}, Fragment::OR_C}};
+ return {{{B, V}, Fragment::OR_C}};
case 17:
if (!allow_B) return {};
- return {{{"B"_mst, "B"_mst}, Fragment::OR_D}};
+ return {{{B, B}, Fragment::OR_D}};
case 18:
if (!(allow_B || allow_K || allow_V)) return {};
return {{{type_needed, type_needed}, Fragment::OR_I}};
@@ -472,25 +473,25 @@ std::optional<NodeInfo> ConsumeNodeStable(MsCtx script_ctx, FuzzedDataProvider&
}
case 20:
if (!allow_W) return {};
- return {{{"B"_mst}, Fragment::WRAP_A}};
+ return {{{B}, Fragment::WRAP_A}};
case 21:
if (!allow_W) return {};
- return {{{"B"_mst}, Fragment::WRAP_S}};
+ return {{{B}, Fragment::WRAP_S}};
case 22:
if (!allow_B) return {};
- return {{{"K"_mst}, Fragment::WRAP_C}};
+ return {{{K}, Fragment::WRAP_C}};
case 23:
if (!allow_B) return {};
- return {{{"V"_mst}, Fragment::WRAP_D}};
+ return {{{V}, Fragment::WRAP_D}};
case 24:
if (!allow_V) return {};
- return {{{"B"_mst}, Fragment::WRAP_V}};
+ return {{{B}, Fragment::WRAP_V}};
case 25:
if (!allow_B) return {};
- return {{{"B"_mst}, Fragment::WRAP_J}};
+ return {{{B}, Fragment::WRAP_J}};
case 26:
if (!allow_B) return {};
- return {{{"B"_mst}, Fragment::WRAP_N}};
+ return {{{B}, Fragment::WRAP_N}};
case 27: {
if (!allow_B || !IsTapscript(script_ctx)) return {};
const auto k = provider.ConsumeIntegral<uint16_t>();
@@ -528,20 +529,23 @@ struct SmartInfo
{
/* Construct a set of interesting type requirements to reason with (sections of BKVWzondu). */
std::vector<Type> types;
+ static constexpr auto B_mst{"B"_mst}, K_mst{"K"_mst}, V_mst{"V"_mst}, W_mst{"W"_mst};
+ static constexpr auto d_mst{"d"_mst}, n_mst{"n"_mst}, o_mst{"o"_mst}, u_mst{"u"_mst}, z_mst{"z"_mst};
+ static constexpr auto NONE_mst{""_mst};
for (int base = 0; base < 4; ++base) { /* select from B,K,V,W */
- Type type_base = base == 0 ? "B"_mst : base == 1 ? "K"_mst : base == 2 ? "V"_mst : "W"_mst;
+ Type type_base = base == 0 ? B_mst : base == 1 ? K_mst : base == 2 ? V_mst : W_mst;
for (int zo = 0; zo < 3; ++zo) { /* select from z,o,(none) */
- Type type_zo = zo == 0 ? "z"_mst : zo == 1 ? "o"_mst : ""_mst;
+ Type type_zo = zo == 0 ? z_mst : zo == 1 ? o_mst : NONE_mst;
for (int n = 0; n < 2; ++n) { /* select from (none),n */
if (zo == 0 && n == 1) continue; /* z conflicts with n */
if (base == 3 && n == 1) continue; /* W conflicts with n */
- Type type_n = n == 0 ? ""_mst : "n"_mst;
+ Type type_n = n == 0 ? NONE_mst : n_mst;
for (int d = 0; d < 2; ++d) { /* select from (none),d */
if (base == 2 && d == 1) continue; /* V conflicts with d */
- Type type_d = d == 0 ? ""_mst : "d"_mst;
+ Type type_d = d == 0 ? NONE_mst : d_mst;
for (int u = 0; u < 2; ++u) { /* select from (none),u */
if (base == 2 && u == 1) continue; /* V conflicts with u */
- Type type_u = u == 0 ? ""_mst : "u"_mst;
+ Type type_u = u == 0 ? NONE_mst : u_mst;
Type type = type_base | type_zo | type_n | type_d | type_u;
types.push_back(type);
}
@@ -683,7 +687,7 @@ struct SmartInfo
/* Find which types are useful. The fuzzer logic only cares about constructing
* B,V,K,W nodes, so any type that isn't needed in any recipe (directly or
* indirectly) for the construction of those is uninteresting. */
- std::set<Type> useful_types{"B"_mst, "V"_mst, "K"_mst, "W"_mst};
+ std::set<Type> useful_types{B_mst, V_mst, K_mst, W_mst};
// Find the transitive closure by adding types until the set of types does not change.
while (true) {
size_t set_size = useful_types.size();
@@ -1197,7 +1201,7 @@ void TestNode(const MsCtx script_ctx, const NodeRef& node, FuzzedDataProvider& p
void FuzzInit()
{
- ECC_Start();
+ static ECC_Context ecc_context{};
TEST_DATA.Init();
}
diff --git a/src/test/fuzz/multiplication_overflow.cpp b/src/test/fuzz/multiplication_overflow.cpp
index fbe4d061bf..a762a4dfe3 100644
--- a/src/test/fuzz/multiplication_overflow.cpp
+++ b/src/test/fuzz/multiplication_overflow.cpp
@@ -2,10 +2,6 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
-
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
@@ -21,7 +17,7 @@ void TestMultiplicationOverflow(FuzzedDataProvider& fuzzed_data_provider)
const T i = fuzzed_data_provider.ConsumeIntegral<T>();
const T j = fuzzed_data_provider.ConsumeIntegral<T>();
const bool is_multiplication_overflow_custom = MultiplicationOverflow(i, j);
-#if defined(HAVE_BUILTIN_MUL_OVERFLOW)
+#ifndef _MSC_VER
T result_builtin;
const bool is_multiplication_overflow_builtin = __builtin_mul_overflow(i, j, &result_builtin);
assert(is_multiplication_overflow_custom == is_multiplication_overflow_builtin);
diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp
index c882bd766a..e8b1480c5b 100644
--- a/src/test/fuzz/net.cpp
+++ b/src/test/fuzz/net.cpp
@@ -77,3 +77,40 @@ FUZZ_TARGET(net, .init = initialize_net)
(void)node.HasPermission(net_permission_flags);
(void)node.ConnectedThroughNetwork();
}
+
+FUZZ_TARGET(local_address, .init = initialize_net)
+{
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+ CService service{ConsumeService(fuzzed_data_provider)};
+ CNode node{ConsumeNode(fuzzed_data_provider)};
+ {
+ LOCK(g_maplocalhost_mutex);
+ mapLocalHost.clear();
+ }
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
+ CallOneOf(
+ fuzzed_data_provider,
+ [&] {
+ service = ConsumeService(fuzzed_data_provider);
+ },
+ [&] {
+ const bool added{AddLocal(service, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, LOCAL_MAX - 1))};
+ if (!added) return;
+ assert(service.IsRoutable());
+ assert(IsLocal(service));
+ assert(SeenLocal(service));
+ },
+ [&] {
+ (void)RemoveLocal(service);
+ },
+ [&] {
+ (void)SeenLocal(service);
+ },
+ [&] {
+ (void)IsLocal(service);
+ },
+ [&] {
+ (void)GetLocalAddress(node);
+ });
+ }
+}
diff --git a/src/test/fuzz/net_permissions.cpp b/src/test/fuzz/net_permissions.cpp
index ae343602e9..811c0de4b9 100644
--- a/src/test/fuzz/net_permissions.cpp
+++ b/src/test/fuzz/net_permissions.cpp
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <net_permissions.h>
+#include <netbase.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
@@ -17,7 +18,7 @@
FUZZ_TARGET(net_permissions)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
- const std::string s = fuzzed_data_provider.ConsumeRandomLengthString(32);
+ const std::string s = fuzzed_data_provider.ConsumeRandomLengthString(1000);
const NetPermissionFlags net_permission_flags = ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS);
NetWhitebindPermissions net_whitebind_permissions;
@@ -31,8 +32,9 @@ FUZZ_TARGET(net_permissions)
}
NetWhitelistPermissions net_whitelist_permissions;
+ ConnectionDirection connection_direction;
bilingual_str error_net_whitelist_permissions;
- if (NetWhitelistPermissions::TryParse(s, net_whitelist_permissions, error_net_whitelist_permissions)) {
+ if (NetWhitelistPermissions::TryParse(s, net_whitelist_permissions, connection_direction, error_net_whitelist_permissions)) {
(void)NetPermissions::ToStrings(net_whitelist_permissions.m_flags);
(void)NetPermissions::AddFlag(net_whitelist_permissions.m_flags, net_permission_flags);
assert(NetPermissions::HasFlag(net_whitelist_permissions.m_flags, net_permission_flags));
diff --git a/src/test/fuzz/netaddress.cpp b/src/test/fuzz/netaddress.cpp
index 5141d3362d..4803cdccad 100644
--- a/src/test/fuzz/netaddress.cpp
+++ b/src/test/fuzz/netaddress.cpp
@@ -26,6 +26,12 @@ FUZZ_TARGET(netaddress)
if (net_addr.GetNetwork() == Network::NET_ONION) {
assert(net_addr.IsTor());
}
+ if (net_addr.GetNetwork() == Network::NET_I2P) {
+ assert(net_addr.IsI2P());
+ }
+ if (net_addr.GetNetwork() == Network::NET_CJDNS) {
+ assert(net_addr.IsCJDNS());
+ }
if (net_addr.GetNetwork() == Network::NET_INTERNAL) {
assert(net_addr.IsInternal());
}
@@ -69,6 +75,12 @@ FUZZ_TARGET(netaddress)
if (net_addr.IsTor()) {
assert(net_addr.GetNetwork() == Network::NET_ONION);
}
+ if (net_addr.IsI2P()) {
+ assert(net_addr.GetNetwork() == Network::NET_I2P);
+ }
+ if (net_addr.IsCJDNS()) {
+ assert(net_addr.GetNetwork() == Network::NET_CJDNS);
+ }
(void)net_addr.IsValid();
(void)net_addr.ToStringAddr();
diff --git a/src/test/fuzz/p2p_transport_serialization.cpp b/src/test/fuzz/p2p_transport_serialization.cpp
index a205ce19f4..f6d82c3001 100644
--- a/src/test/fuzz/p2p_transport_serialization.cpp
+++ b/src/test/fuzz/p2p_transport_serialization.cpp
@@ -25,7 +25,7 @@ std::vector<std::string> g_all_messages;
void initialize_p2p_transport_serialization()
{
- ECC_Start();
+ static ECC_Context ecc_context{};
SelectParams(ChainType::REGTEST);
g_all_messages = getAllNetMessageTypes();
std::sort(g_all_messages.begin(), g_all_messages.end());
@@ -354,6 +354,7 @@ std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& r
} else {
// If it's longer, generate it from the RNG. This avoids having large amounts of
// (hopefully) irrelevant data needing to be stored in the fuzzer data.
+ garb.resize(garb_len);
for (auto& v : garb) v = uint8_t(rng());
}
// Retrieve entropy
diff --git a/src/test/fuzz/package_eval.cpp b/src/test/fuzz/package_eval.cpp
index 5a08d0ff44..c201118bce 100644
--- a/src/test/fuzz/package_eval.cpp
+++ b/src/test/fuzz/package_eval.cpp
@@ -6,6 +6,7 @@
#include <node/context.h>
#include <node/mempool_args.h>
#include <node/miner.h>
+#include <policy/v3_policy.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
@@ -46,7 +47,7 @@ void initialize_tx_pool()
g_outpoints_coinbase_init_mature.push_back(prevout);
}
}
- SyncWithValidationInterfaceQueue();
+ g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
}
struct OutpointsUpdater final : public CValidationInterface {
@@ -119,7 +120,8 @@ CTxMemPool MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeConte
mempool_opts.limits.descendant_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;
mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
- nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(1, 999);
+ // Only interested in 2 cases: sigop cost 0 or when single legacy sigop cost is >> 1KvB
+ nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 1) * 10'000;
mempool_opts.check_ratio = 1;
mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
@@ -145,7 +147,7 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
}
auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
- RegisterSharedValidationInterface(outpoints_updater);
+ node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
CTxMemPool tx_pool_{MakeMempool(fuzzed_data_provider, node)};
MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
@@ -171,11 +173,11 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
// Create transaction to add to the mempool
const CTransactionRef tx = [&] {
CMutableTransaction tx_mut;
- tx_mut.nVersion = CTransaction::CURRENT_VERSION;
+ tx_mut.nVersion = fuzzed_data_provider.ConsumeBool() ? 3 : CTransaction::CURRENT_VERSION;
tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
// Last tx will sweep all outpoints in package
const auto num_in = last_tx ? package_outpoints.size() : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size());
- const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size() * 2);
+ auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size() * 2);
auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
@@ -211,17 +213,24 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
tx_mut.vin.push_back(tx_mut.vin.back());
}
- // Refer to a non-existant input
+ // Refer to a non-existent input
if (fuzzed_data_provider.ConsumeBool()) {
tx_mut.vin.emplace_back();
}
+ // Make a p2pk output to make sigops adjusted vsize to violate v3, potentially, which is never spent
+ if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool()) {
+ tx_mut.vout.emplace_back(1000, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG);
+ // Don't add any other outputs.
+ num_out = 1;
+ amount_in -= 1000;
+ }
+
const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
const auto amount_out = (amount_in - amount_fee) / num_out;
for (int i = 0; i < num_out; ++i) {
tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
}
- // TODO vary transaction sizes to catch size-related issues
auto tx = MakeTransactionRef(tx_mut);
// Restore previously removed outpoints, except in-package outpoints
if (!last_tx) {
@@ -260,28 +269,36 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
// Remember all added transactions
std::set<CTransactionRef> added;
auto txr = std::make_shared<TransactionsDelta>(added);
- RegisterSharedValidationInterface(txr);
- const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
+ node.validation_signals->RegisterSharedValidationInterface(txr);
// When there are multiple transactions in the package, we call ProcessNewPackage(txs, test_accept=false)
// and AcceptToMemoryPool(txs.back(), test_accept=true). When there is only 1 transaction, we might flip it
// (the package is a test accept and ATMP is a submission).
auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
+ // Exercise client_maxfeerate logic
+ std::optional<CFeeRate> client_maxfeerate{};
+ if (fuzzed_data_provider.ConsumeBool()) {
+ client_maxfeerate = CFeeRate(fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1, 50 * COIN), 100);
+ }
+
const auto result_package = WITH_LOCK(::cs_main,
- return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit));
+ return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, client_maxfeerate));
- const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(), bypass_limits, /*test_accept=*/!single_submit));
- const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
+ // Always set bypass_limits to false because it is not supported in ProcessNewPackage and
+ // can be a source of divergence.
+ const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
+ /*bypass_limits=*/false, /*test_accept=*/!single_submit));
+ const bool passed = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
- SyncWithValidationInterfaceQueue();
- UnregisterSharedValidationInterface(txr);
+ node.validation_signals->SyncWithValidationInterfaceQueue();
+ node.validation_signals->UnregisterSharedValidationInterface(txr);
// There is only 1 transaction in the package. We did a test-package-accept and a ATMP
if (single_submit) {
- Assert(accepted != added.empty());
- Assert(accepted == res.m_state.IsValid());
- if (accepted) {
+ Assert(passed != added.empty());
+ Assert(passed == res.m_state.IsValid());
+ if (passed) {
Assert(added.size() == 1);
Assert(txs.back() == *added.begin());
}
@@ -295,9 +312,11 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
// This is empty if it fails early checks, or "full" if transactions are looked at deeper
Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty());
}
+
+ CheckMempoolV3Invariants(tx_pool);
}
- UnregisterSharedValidationInterface(outpoints_updater);
+ node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
}
diff --git a/src/test/fuzz/partially_downloaded_block.cpp b/src/test/fuzz/partially_downloaded_block.cpp
index 4a4b46da60..2bf47930f4 100644
--- a/src/test/fuzz/partially_downloaded_block.cpp
+++ b/src/test/fuzz/partially_downloaded_block.cpp
@@ -60,7 +60,7 @@ FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb)
// The coinbase is always available
available.insert(0);
- std::vector<std::pair<uint256, CTransactionRef>> extra_txn;
+ std::vector<CTransactionRef> extra_txn;
for (size_t i = 1; i < block->vtx.size(); ++i) {
auto tx{block->vtx[i]};
@@ -68,11 +68,11 @@ FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb)
bool add_to_mempool{fuzzed_data_provider.ConsumeBool()};
if (add_to_extra_txn) {
- extra_txn.emplace_back(tx->GetWitnessHash(), tx);
+ extra_txn.emplace_back(tx);
available.insert(i);
}
- if (add_to_mempool) {
+ if (add_to_mempool && !pool.exists(GenTxid::Txid(tx->GetHash()))) {
LOCK2(cs_main, pool.cs);
pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, *tx));
available.insert(i);
diff --git a/src/test/fuzz/poolresource.cpp b/src/test/fuzz/poolresource.cpp
index ce64ef6472..f764d9f8db 100644
--- a/src/test/fuzz/poolresource.cpp
+++ b/src/test/fuzz/poolresource.cpp
@@ -63,9 +63,9 @@ public:
{
if (m_total_allocated > 0x1000000) return;
size_t alignment_bits = m_provider.ConsumeIntegralInRange<size_t>(0, 7);
- size_t alignment = 1 << alignment_bits;
+ size_t alignment = size_t{1} << alignment_bits;
size_t size_bits = m_provider.ConsumeIntegralInRange<size_t>(0, 16 - alignment_bits);
- size_t size = m_provider.ConsumeIntegralInRange<size_t>(1U << size_bits, (1U << (size_bits + 1)) - 1U) << alignment_bits;
+ size_t size = m_provider.ConsumeIntegralInRange<size_t>(size_t{1} << size_bits, (size_t{1} << (size_bits + 1)) - 1U) << alignment_bits;
Allocate(size, alignment);
}
diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp
index 56b391ed5c..a467fd5382 100644
--- a/src/test/fuzz/process_message.cpp
+++ b/src/test/fuzz/process_message.cpp
@@ -47,7 +47,7 @@ void initialize_process_message()
for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
MineBlock(g_setup->m_node, CScript() << OP_TRUE);
}
- SyncWithValidationInterfaceQueue();
+ g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
}
FUZZ_TARGET(process_message, .init = initialize_process_message)
@@ -89,6 +89,6 @@ FUZZ_TARGET(process_message, .init = initialize_process_message)
}
g_setup->m_node.peerman->SendMessages(&p2p_node);
}
- SyncWithValidationInterfaceQueue();
+ g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
g_setup->m_node.connman->StopNodes();
}
diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp
index 6b264907b5..38acd432fa 100644
--- a/src/test/fuzz/process_messages.cpp
+++ b/src/test/fuzz/process_messages.cpp
@@ -37,7 +37,7 @@ void initialize_process_messages()
for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
MineBlock(g_setup->m_node, CScript() << OP_TRUE);
}
- SyncWithValidationInterfaceQueue();
+ g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
}
FUZZ_TARGET(process_messages, .init = initialize_process_messages)
@@ -89,6 +89,6 @@ FUZZ_TARGET(process_messages, .init = initialize_process_messages)
g_setup->m_node.peerman->SendMessages(&random_node);
}
}
- SyncWithValidationInterfaceQueue();
+ g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
g_setup->m_node.connman->StopNodes();
}
diff --git a/src/test/fuzz/rbf.cpp b/src/test/fuzz/rbf.cpp
index aa6385d12d..64785948f6 100644
--- a/src/test/fuzz/rbf.cpp
+++ b/src/test/fuzz/rbf.cpp
@@ -23,12 +23,30 @@ namespace {
const BasicTestingSetup* g_setup;
} // namespace
+const int NUM_ITERS = 10000;
+
+std::vector<COutPoint> g_outpoints;
+
void initialize_rbf()
{
static const auto testing_setup = MakeNoLogFileContext<>();
g_setup = testing_setup.get();
}
+void initialize_package_rbf()
+{
+ static const auto testing_setup = MakeNoLogFileContext<>();
+ g_setup = testing_setup.get();
+
+ // Create a fixed set of unique "UTXOs" to source parents from
+ // to avoid fuzzer giving circular references
+ for (int i = 0; i < NUM_ITERS; ++i) {
+ g_outpoints.emplace_back();
+ g_outpoints.back().n = i;
+ }
+
+}
+
FUZZ_TARGET(rbf, .init = initialize_rbf)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
@@ -40,7 +58,7 @@ FUZZ_TARGET(rbf, .init = initialize_rbf)
CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node)};
- LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
{
const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
if (!another_mtx) {
@@ -63,3 +81,124 @@ FUZZ_TARGET(rbf, .init = initialize_rbf)
(void)IsRBFOptIn(tx, pool);
}
}
+
+FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
+{
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+ SetMockTime(ConsumeTime(fuzzed_data_provider));
+
+ std::optional<CMutableTransaction> child = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
+ if (!child) return;
+
+ CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node)};
+
+ // Add a bunch of parent-child pairs to the mempool, and remember them.
+ std::vector<CTransaction> mempool_txs;
+ size_t iter{0};
+
+ int32_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(1, 1000000);
+
+ // Keep track of the total vsize of CTxMemPoolEntry's being added to the mempool to avoid overflow
+ // Add replacement_vsize since this is added to new diagram during RBF check
+ int64_t running_vsize_total{replacement_vsize};
+
+ LOCK2(cs_main, pool.cs);
+
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
+ {
+ // Make sure txns only have one input, and that a unique input is given to avoid circular references
+ std::optional<CMutableTransaction> parent = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
+ if (!parent) {
+ return;
+ }
+ assert(iter <= g_outpoints.size());
+ parent->vin.resize(1);
+ parent->vin[0].prevout = g_outpoints[iter++];
+
+ mempool_txs.emplace_back(*parent);
+ const auto parent_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
+ running_vsize_total += parent_entry.GetTxSize();
+ if (running_vsize_total > std::numeric_limits<int32_t>::max()) {
+ // We aren't adding this final tx to mempool, so we don't want to conflict with it
+ mempool_txs.pop_back();
+ break;
+ }
+ pool.addUnchecked(parent_entry);
+ if (fuzzed_data_provider.ConsumeBool() && !child->vin.empty()) {
+ child->vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0};
+ }
+ mempool_txs.emplace_back(*child);
+ const auto child_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
+ running_vsize_total += child_entry.GetTxSize();
+ if (running_vsize_total > std::numeric_limits<int32_t>::max()) {
+ // We aren't adding this final tx to mempool, so we don't want to conflict with it
+ mempool_txs.pop_back();
+ break;
+ }
+ pool.addUnchecked(child_entry);
+
+ if (fuzzed_data_provider.ConsumeBool()) {
+ pool.PrioritiseTransaction(mempool_txs.back().GetHash().ToUint256(), fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(-100000, 100000));
+ }
+ }
+
+ // Pick some transactions at random to be the direct conflicts
+ CTxMemPool::setEntries direct_conflicts;
+ for (auto& tx : mempool_txs) {
+ if (fuzzed_data_provider.ConsumeBool()) {
+ direct_conflicts.insert(*pool.GetIter(tx.GetHash()));
+ }
+ }
+
+ // Calculate all conflicts:
+ CTxMemPool::setEntries all_conflicts;
+ for (auto& txiter : direct_conflicts) {
+ pool.CalculateDescendants(txiter, all_conflicts);
+ }
+
+ // Calculate the chunks for a replacement.
+ CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);
+ auto calc_results{pool.CalculateChunksForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
+
+ if (calc_results.has_value()) {
+ // Sanity checks on the chunks.
+
+ // Feerates are monotonically decreasing.
+ FeeFrac first_sum;
+ for (size_t i = 0; i < calc_results->first.size(); ++i) {
+ first_sum += calc_results->first[i];
+ if (i) assert(!(calc_results->first[i - 1] << calc_results->first[i]));
+ }
+ FeeFrac second_sum;
+ for (size_t i = 0; i < calc_results->second.size(); ++i) {
+ second_sum += calc_results->second[i];
+ if (i) assert(!(calc_results->second[i - 1] << calc_results->second[i]));
+ }
+
+ FeeFrac replaced;
+ for (auto txiter : all_conflicts) {
+ replaced.fee += txiter->GetModifiedFee();
+ replaced.size += txiter->GetTxSize();
+ }
+ // The total fee & size of the new diagram minus replaced fee & size should be the total
+ // fee & size of the old diagram minus replacement fee & size.
+ assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_vsize}));
+ }
+
+ // If internals report error, wrapper should too
+ auto err_tuple{ImprovesFeerateDiagram(pool, direct_conflicts, all_conflicts, replacement_fees, replacement_vsize)};
+ if (!calc_results.has_value()) {
+ assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);
+ } else {
+ // Diagram check succeeded
+ auto old_sum = std::accumulate(calc_results->first.begin(), calc_results->first.end(), FeeFrac{});
+ auto new_sum = std::accumulate(calc_results->second.begin(), calc_results->second.end(), FeeFrac{});
+ if (!err_tuple.has_value()) {
+ // New diagram's final fee should always match or exceed old diagram's
+ assert(old_sum.fee <= new_sum.fee);
+ } else if (old_sum.fee > new_sum.fee) {
+ // Or it failed, and if old diagram had higher fees, it should be a failure
+ assert(err_tuple.value().first == DiagramCheckError::FAILURE);
+ }
+ }
+}
diff --git a/src/test/fuzz/script_bitcoin_consensus.cpp b/src/test/fuzz/script_bitcoin_consensus.cpp
deleted file mode 100644
index 846389863d..0000000000
--- a/src/test/fuzz/script_bitcoin_consensus.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 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.
-
-#include <script/bitcoinconsensus.h>
-#include <script/interpreter.h>
-#include <test/fuzz/FuzzedDataProvider.h>
-#include <test/fuzz/fuzz.h>
-#include <test/fuzz/util.h>
-
-#include <cstdint>
-#include <string>
-#include <vector>
-
-FUZZ_TARGET(script_bitcoin_consensus)
-{
- FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
- const std::vector<uint8_t> random_bytes_1 = ConsumeRandomLengthByteVector(fuzzed_data_provider);
- const std::vector<uint8_t> random_bytes_2 = ConsumeRandomLengthByteVector(fuzzed_data_provider);
- const CAmount money = ConsumeMoney(fuzzed_data_provider);
- bitcoinconsensus_error err;
- bitcoinconsensus_error* err_p = fuzzed_data_provider.ConsumeBool() ? &err : nullptr;
- const unsigned int n_in = fuzzed_data_provider.ConsumeIntegral<unsigned int>();
- const unsigned int flags = fuzzed_data_provider.ConsumeIntegral<unsigned int>();
- assert(bitcoinconsensus_version() == BITCOINCONSENSUS_API_VER);
- if ((flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
- return;
- }
- (void)bitcoinconsensus_verify_script(random_bytes_1.data(), random_bytes_1.size(), random_bytes_2.data(), random_bytes_2.size(), n_in, flags, err_p);
- (void)bitcoinconsensus_verify_script_with_amount(random_bytes_1.data(), random_bytes_1.size(), money, random_bytes_2.data(), random_bytes_2.size(), n_in, flags, err_p);
-
- std::vector<UTXO> spent_outputs;
- std::vector<std::vector<unsigned char>> spent_spks;
- if (n_in <= 24386) {
- spent_outputs.reserve(n_in);
- spent_spks.reserve(n_in);
- for (size_t i = 0; i < n_in; ++i) {
- spent_spks.push_back(ConsumeRandomLengthByteVector(fuzzed_data_provider));
- const CAmount value{ConsumeMoney(fuzzed_data_provider)};
- const auto spk_size{static_cast<unsigned>(spent_spks.back().size())};
- spent_outputs.push_back({.scriptPubKey = spent_spks.back().data(), .scriptPubKeySize = spk_size, .value = value});
- }
- }
-
- const auto spent_outs_size{static_cast<unsigned>(spent_outputs.size())};
-
- (void)bitcoinconsensus_verify_script_with_spent_outputs(
- random_bytes_1.data(), random_bytes_1.size(), money, random_bytes_2.data(), random_bytes_2.size(),
- spent_outputs.data(), spent_outs_size, n_in, flags, err_p);
-}
diff --git a/src/test/fuzz/script_sign.cpp b/src/test/fuzz/script_sign.cpp
index 9ae150e553..4695bc611b 100644
--- a/src/test/fuzz/script_sign.cpp
+++ b/src/test/fuzz/script_sign.cpp
@@ -26,7 +26,7 @@
void initialize_script_sign()
{
- ECC_Start();
+ static ECC_Context ecc_context{};
SelectParams(ChainType::REGTEST);
}
diff --git a/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp b/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp
index 74ef6bfd4e..ae0c8479cb 100644
--- a/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp
+++ b/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp
@@ -24,8 +24,7 @@ FUZZ_TARGET(secp256k1_ecdsa_signature_parse_der_lax)
secp256k1_ecdsa_signature sig_der_lax;
const bool parsed_der_lax = ecdsa_signature_parse_der_lax(&sig_der_lax, signature_bytes.data(), signature_bytes.size()) == 1;
if (parsed_der_lax) {
- ECC_Start();
+ ECC_Context ecc_context{};
(void)SigHasLowR(&sig_der_lax);
- ECC_Stop();
}
}
diff --git a/src/test/fuzz/string.cpp b/src/test/fuzz/string.cpp
index e81efac6e0..631da13803 100644
--- a/src/test/fuzz/string.cpp
+++ b/src/test/fuzz/string.cpp
@@ -90,7 +90,7 @@ FUZZ_TARGET(string)
(void)ToUpper(random_string_1);
(void)TrimString(random_string_1);
(void)TrimString(random_string_1, random_string_2);
- (void)urlDecode(random_string_1);
+ (void)UrlDecode(random_string_1);
(void)ContainsNoNUL(random_string_1);
(void)_(random_string_1.c_str());
try {
diff --git a/src/test/fuzz/timedata.cpp b/src/test/fuzz/timedata.cpp
deleted file mode 100644
index f5d005296b..0000000000
--- a/src/test/fuzz/timedata.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2020-2021 The Bitcoin Core developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#include <test/fuzz/FuzzedDataProvider.h>
-#include <test/fuzz/fuzz.h>
-#include <test/fuzz/util.h>
-#include <timedata.h>
-
-#include <cstdint>
-#include <string>
-#include <vector>
-
-FUZZ_TARGET(timedata)
-{
- FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
- const unsigned int max_size = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 1000);
- // A max_size of 0 implies no limit, so cap the max number of insertions to avoid timeouts
- auto max_to_insert = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 4000);
- // Divide by 2 to avoid signed integer overflow in .median()
- const int64_t initial_value = fuzzed_data_provider.ConsumeIntegral<int64_t>() / 2;
- CMedianFilter<int64_t> median_filter{max_size, initial_value};
- while (fuzzed_data_provider.remaining_bytes() > 0 && --max_to_insert >= 0) {
- (void)median_filter.median();
- assert(median_filter.size() > 0);
- assert(static_cast<size_t>(median_filter.size()) == median_filter.sorted().size());
- assert(static_cast<unsigned int>(median_filter.size()) <= max_size || max_size == 0);
- // Divide by 2 to avoid signed integer overflow in .median()
- median_filter.input(fuzzed_data_provider.ConsumeIntegral<int64_t>() / 2);
- }
-}
diff --git a/src/test/fuzz/timeoffsets.cpp b/src/test/fuzz/timeoffsets.cpp
new file mode 100644
index 0000000000..019337a94a
--- /dev/null
+++ b/src/test/fuzz/timeoffsets.cpp
@@ -0,0 +1,28 @@
+// Copyright (c) 2024-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <node/timeoffsets.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/util/setup_common.h>
+
+#include <chrono>
+#include <cstdint>
+#include <functional>
+
+void initialize_timeoffsets()
+{
+ static const auto testing_setup = MakeNoLogFileContext<>(ChainType::MAIN);
+}
+
+FUZZ_TARGET(timeoffsets, .init = initialize_timeoffsets)
+{
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+ TimeOffsets offsets{};
+ LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 4'000) {
+ (void)offsets.Median();
+ offsets.Add(std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<std::chrono::seconds::rep>()});
+ offsets.WarnIfOutOfSync();
+ }
+}
diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
index 4ad0956201..9f0aedf29b 100644
--- a/src/test/fuzz/tx_pool.cpp
+++ b/src/test/fuzz/tx_pool.cpp
@@ -6,6 +6,7 @@
#include <node/context.h>
#include <node/mempool_args.h>
#include <node/miner.h>
+#include <policy/v3_policy.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
@@ -49,7 +50,7 @@ void initialize_tx_pool()
g_outpoints_coinbase_init_immature;
outpoints.push_back(prevout);
}
- SyncWithValidationInterfaceQueue();
+ g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
}
struct TransactionsDelta final : public CValidationInterface {
@@ -101,12 +102,10 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Cha
if (!info_all.empty()) {
const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx;
WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, MemPoolRemovalReason::BLOCK /* dummy */));
- std::vector<uint256> all_txids;
- tx_pool.queryHashes(all_txids);
- assert(all_txids.size() < info_all.size());
+ assert(tx_pool.size() < info_all.size());
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
}
- SyncWithValidationInterfaceQueue();
+ g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
}
void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
@@ -140,7 +139,6 @@ void CheckATMPInvariants(const MempoolAcceptResult& res, bool txid_in_mempool, b
Assert(wtxid_in_mempool);
Assert(res.m_state.IsValid());
Assert(!res.m_state.IsInvalid());
- Assert(res.m_replaced_transactions);
Assert(res.m_vsize);
Assert(res.m_base_fees);
Assert(res.m_effective_feerate);
@@ -155,7 +153,6 @@ void CheckATMPInvariants(const MempoolAcceptResult& res, bool txid_in_mempool, b
Assert(res.m_state.IsInvalid());
const bool is_reconsiderable{res.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE};
- Assert(!res.m_replaced_transactions);
Assert(!res.m_vsize);
Assert(!res.m_base_fees);
// Fee information is provided if the failure is TX_RECONSIDERABLE.
@@ -229,7 +226,7 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
// Create transaction to add to the mempool
const CTransactionRef tx = [&] {
CMutableTransaction tx_mut;
- tx_mut.nVersion = CTransaction::CURRENT_VERSION;
+ tx_mut.nVersion = fuzzed_data_provider.ConsumeBool() ? 3 : CTransaction::CURRENT_VERSION;
tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size());
const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2);
@@ -286,13 +283,13 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
std::set<CTransactionRef> removed;
std::set<CTransactionRef> added;
auto txr = std::make_shared<TransactionsDelta>(removed, added);
- RegisterSharedValidationInterface(txr);
+ node.validation_signals->RegisterSharedValidationInterface(txr);
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
// Make sure ProcessNewPackage on one transaction works.
// The result is not guaranteed to be the same as what is returned by ATMP.
const auto result_package = WITH_LOCK(::cs_main,
- return ProcessNewPackage(chainstate, tx_pool, {tx}, true));
+ return ProcessNewPackage(chainstate, tx_pool, {tx}, true, /*client_maxfeerate=*/{}));
// If something went wrong due to a package-specific policy, it might not return a
// validation result for the transaction.
if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
@@ -304,8 +301,8 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false));
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
- SyncWithValidationInterfaceQueue();
- UnregisterSharedValidationInterface(txr);
+ node.validation_signals->SyncWithValidationInterfaceQueue();
+ node.validation_signals->UnregisterSharedValidationInterface(txr);
bool txid_in_mempool = tx_pool.exists(GenTxid::Txid(tx->GetHash()));
bool wtxid_in_mempool = tx_pool.exists(GenTxid::Wtxid(tx->GetWitnessHash()));
@@ -315,6 +312,7 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
if (accepted) {
Assert(added.size() == 1); // For now, no package acceptance
Assert(tx == *added.begin());
+ CheckMempoolV3Invariants(tx_pool);
} else {
// Do not consider rejected transaction removed
removed.erase(tx);
@@ -407,6 +405,7 @@ FUZZ_TARGET(tx_pool, .init = initialize_tx_pool)
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
if (accepted) {
txids.push_back(tx->GetHash());
+ CheckMempoolV3Invariants(tx_pool);
}
}
Finish(fuzzed_data_provider, tx_pool, chainstate);
diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp
index 5423ba8920..7bebb987b1 100644
--- a/src/test/fuzz/txorphan.cpp
+++ b/src/test/fuzz/txorphan.cpp
@@ -37,13 +37,14 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
SetMockTime(ConsumeTime(fuzzed_data_provider));
TxOrphanage orphanage;
- std::vector<COutPoint> outpoints;
+ std::set<COutPoint> outpoints;
+
// initial outpoints used to construct transactions later
for (uint8_t i = 0; i < 4; i++) {
- outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0);
+ outpoints.emplace(Txid::FromUint256(uint256{i}), 0);
}
- // if true, allow duplicate input when constructing tx
- const bool duplicate_input = fuzzed_data_provider.ConsumeBool();
+
+ CTransactionRef ptx_potential_parent = nullptr;
LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS)
{
@@ -51,33 +52,47 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
const CTransactionRef tx = [&] {
CMutableTransaction tx_mut;
const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size());
- const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size());
- // pick unique outpoints from outpoints as input
+ const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256);
+ // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not
+ // running any transaction validation logic before adding transactions to the orphanage
for (uint32_t i = 0; i < num_in; i++) {
auto& prevout = PickValue(fuzzed_data_provider, outpoints);
- tx_mut.vin.emplace_back(prevout);
- // pop the picked outpoint if duplicate input is not allowed
- if (!duplicate_input) {
- std::swap(prevout, outpoints.back());
- outpoints.pop_back();
- }
+ // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen
+ tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL));
}
// output amount will not affect txorphanage
for (uint32_t i = 0; i < num_out; i++) {
tx_mut.vout.emplace_back(CAmount{0}, CScript{});
}
- // restore previously popped outpoints
- for (auto& in : tx_mut.vin) {
- outpoints.push_back(in.prevout);
- }
auto new_tx = MakeTransactionRef(tx_mut);
- // add newly constructed transaction to outpoints
+ // add newly constructed outpoints to the coin pool
for (uint32_t i = 0; i < num_out; i++) {
- outpoints.emplace_back(new_tx->GetHash(), i);
+ outpoints.emplace(new_tx->GetHash(), i);
}
return new_tx;
}();
+ // Trigger orphanage functions that are called using parents. ptx_potential_parent is a tx we constructed in a
+ // previous loop and potentially the parent of this tx.
+ if (ptx_potential_parent) {
+ // Set up future GetTxToReconsider call.
+ orphanage.AddChildrenToWorkSet(*ptx_potential_parent);
+
+ // Check that all txns returned from GetChildrenFrom* are indeed a direct child of this tx.
+ NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
+ for (const auto& child : orphanage.GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) {
+ assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) {
+ return input.prevout.hash == ptx_potential_parent->GetHash();
+ }));
+ }
+ for (const auto& [child, peer] : orphanage.GetChildrenFromDifferentPeer(ptx_potential_parent, peer_id)) {
+ assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) {
+ return input.prevout.hash == ptx_potential_parent->GetHash();
+ }));
+ assert(peer != peer_id);
+ }
+ }
+
// trigger orphanage functions
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS)
{
@@ -86,19 +101,15 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
CallOneOf(
fuzzed_data_provider,
[&] {
- orphanage.AddChildrenToWorkSet(*tx);
- },
- [&] {
{
CTransactionRef ref = orphanage.GetTxToReconsider(peer_id);
if (ref) {
- bool have_tx = orphanage.HaveTx(GenTxid::Txid(ref->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(ref->GetWitnessHash()));
- Assert(have_tx);
+ Assert(orphanage.HaveTx(ref->GetWitnessHash()));
}
}
},
[&] {
- bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash()));
+ bool have_tx = orphanage.HaveTx(tx->GetWitnessHash());
// AddTx should return false if tx is too big or already have it
// tx weight is unknown, we only check when tx is already in orphanage
{
@@ -106,7 +117,7 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
// have_tx == true -> add_tx == false
Assert(!have_tx || !add_tx);
}
- have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash()));
+ have_tx = orphanage.HaveTx(tx->GetWitnessHash());
{
bool add_tx = orphanage.AddTx(tx, peer_id);
// if have_tx is still false, it must be too big
@@ -115,15 +126,15 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
}
},
[&] {
- bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash()));
+ bool have_tx = orphanage.HaveTx(tx->GetWitnessHash());
// EraseTx should return 0 if m_orphans doesn't have the tx
{
- Assert(have_tx == orphanage.EraseTx(tx->GetHash()));
+ Assert(have_tx == orphanage.EraseTx(tx->GetWitnessHash()));
}
- have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash()));
+ have_tx = orphanage.HaveTx(tx->GetWitnessHash());
// have_tx should be false and EraseTx should fail
{
- Assert(!have_tx && !orphanage.EraseTx(tx->GetHash()));
+ Assert(!have_tx && !orphanage.EraseTx(tx->GetWitnessHash()));
}
},
[&] {
@@ -136,6 +147,12 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
orphanage.LimitOrphans(limit, limit_orphans_rng);
Assert(orphanage.Size() <= limit);
});
+
+ }
+ // Set tx as potential parent to be used for future GetChildren() calls.
+ if (!ptx_potential_parent || fuzzed_data_provider.ConsumeBool()) {
+ ptx_potential_parent = tx;
}
+
}
}
diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp
index 23b0761355..259b00fcae 100644
--- a/src/test/fuzz/util.cpp
+++ b/src/test/fuzz/util.cpp
@@ -272,7 +272,7 @@ FILE* FuzzedFileProvider::open()
[&] {
mode = "a+";
});
-#if defined _GNU_SOURCE && !defined __ANDROID__
+#if defined _GNU_SOURCE && (defined(__linux__) || defined(__FreeBSD__))
const cookie_io_functions_t io_hooks = {
FuzzedFileProvider::read,
FuzzedFileProvider::write,
diff --git a/src/test/fuzz/util/descriptor.cpp b/src/test/fuzz/util/descriptor.cpp
index df78bdf314..0fed2bc5e1 100644
--- a/src/test/fuzz/util/descriptor.cpp
+++ b/src/test/fuzz/util/descriptor.cpp
@@ -15,7 +15,7 @@ void MockedDescriptorConverter::Init() {
// an extended one.
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i) || IdIsXOnlyPubKey(i) || IdIsConstPrivKey(i)) {
CKey privkey;
- privkey.Set(UCharCast(key_data.begin()), UCharCast(key_data.end()), !IdIsUnCompPubKey(i));
+ privkey.Set(key_data.begin(), key_data.end(), !IdIsUnCompPubKey(i));
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i)) {
CPubKey pubkey{privkey.GetPubKey()};
keys_str[i] = HexStr(pubkey);
diff --git a/src/test/fuzz/util/net.cpp b/src/test/fuzz/util/net.cpp
index eb0f14ede0..99151bb84d 100644
--- a/src/test/fuzz/util/net.cpp
+++ b/src/test/fuzz/util/net.cpp
@@ -25,33 +25,63 @@
class CNode;
-CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept
+CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider, FastRandomContext* rand) noexcept
{
- const Network network = fuzzed_data_provider.PickValueInArray({Network::NET_IPV4, Network::NET_IPV6, Network::NET_INTERNAL, Network::NET_ONION});
- CNetAddr net_addr;
- if (network == Network::NET_IPV4) {
- in_addr v4_addr = {};
- v4_addr.s_addr = fuzzed_data_provider.ConsumeIntegral<uint32_t>();
- net_addr = CNetAddr{v4_addr};
- } else if (network == Network::NET_IPV6) {
- if (fuzzed_data_provider.remaining_bytes() >= 16) {
- in6_addr v6_addr = {};
- auto addr_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(16);
- if (addr_bytes[0] == CJDNS_PREFIX) { // Avoid generating IPv6 addresses that look like CJDNS.
- addr_bytes[0] = 0x55; // Just an arbitrary number, anything != CJDNS_PREFIX would do.
- }
- memcpy(v6_addr.s6_addr, addr_bytes.data(), 16);
- net_addr = CNetAddr{v6_addr, fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
+ struct NetAux {
+ Network net;
+ CNetAddr::BIP155Network bip155;
+ size_t len;
+ };
+
+ static constexpr std::array<NetAux, 6> nets{
+ NetAux{.net = Network::NET_IPV4, .bip155 = CNetAddr::BIP155Network::IPV4, .len = ADDR_IPV4_SIZE},
+ NetAux{.net = Network::NET_IPV6, .bip155 = CNetAddr::BIP155Network::IPV6, .len = ADDR_IPV6_SIZE},
+ NetAux{.net = Network::NET_ONION, .bip155 = CNetAddr::BIP155Network::TORV3, .len = ADDR_TORV3_SIZE},
+ NetAux{.net = Network::NET_I2P, .bip155 = CNetAddr::BIP155Network::I2P, .len = ADDR_I2P_SIZE},
+ NetAux{.net = Network::NET_CJDNS, .bip155 = CNetAddr::BIP155Network::CJDNS, .len = ADDR_CJDNS_SIZE},
+ NetAux{.net = Network::NET_INTERNAL, .bip155 = CNetAddr::BIP155Network{0}, .len = 0},
+ };
+
+ const size_t nets_index{rand == nullptr
+ ? fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, nets.size() - 1)
+ : static_cast<size_t>(rand->randrange(nets.size()))};
+
+ const auto& aux = nets[nets_index];
+
+ CNetAddr addr;
+
+ if (aux.net == Network::NET_INTERNAL) {
+ if (rand == nullptr) {
+ addr.SetInternal(fuzzed_data_provider.ConsumeBytesAsString(32));
+ } else {
+ const auto v = rand->randbytes(32);
+ addr.SetInternal(std::string{v.begin(), v.end()});
}
- } else if (network == Network::NET_INTERNAL) {
- net_addr.SetInternal(fuzzed_data_provider.ConsumeBytesAsString(32));
- } else if (network == Network::NET_ONION) {
- auto pub_key{fuzzed_data_provider.ConsumeBytes<uint8_t>(ADDR_TORV3_SIZE)};
- pub_key.resize(ADDR_TORV3_SIZE);
- const bool ok{net_addr.SetSpecial(OnionToString(pub_key))};
- assert(ok);
+ return addr;
+ }
+
+ DataStream s;
+
+ s << static_cast<uint8_t>(aux.bip155);
+
+ std::vector<uint8_t> addr_bytes;
+ if (rand == nullptr) {
+ addr_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(aux.len);
+ addr_bytes.resize(aux.len);
+ } else {
+ addr_bytes = rand->randbytes(aux.len);
}
- return net_addr;
+ if (aux.net == NET_IPV6 && addr_bytes[0] == CJDNS_PREFIX) { // Avoid generating IPv6 addresses that look like CJDNS.
+ addr_bytes[0] = 0x55; // Just an arbitrary number, anything != CJDNS_PREFIX would do.
+ }
+ if (aux.net == NET_CJDNS) { // Avoid generating CJDNS addresses that don't start with CJDNS_PREFIX because those are !IsValid().
+ addr_bytes[0] = CJDNS_PREFIX;
+ }
+ s << addr_bytes;
+
+ s >> CAddress::V2_NETWORK(addr);
+
+ return addr;
}
CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept
diff --git a/src/test/fuzz/util/net.h b/src/test/fuzz/util/net.h
index 47e4a2fac0..a6c9e23f2e 100644
--- a/src/test/fuzz/util/net.h
+++ b/src/test/fuzz/util/net.h
@@ -24,7 +24,15 @@
#include <optional>
#include <string>
-CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept;
+/**
+ * Create a CNetAddr. It may have `addr.IsValid() == false`.
+ * @param[in,out] fuzzed_data_provider Take data for the address from this, if `rand` is `nullptr`.
+ * @param[in,out] rand If not nullptr, take data from it instead of from `fuzzed_data_provider`.
+ * Prefer generating addresses using `fuzzed_data_provider` because it is not uniform. Only use
+ * `rand` if `fuzzed_data_provider` is exhausted or its data is needed for other things.
+ * @return a "random" network address.
+ */
+CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider, FastRandomContext* rand = nullptr) noexcept;
class FuzzedSock : public Sock
{
diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp
index f80f07d190..d7249d88f4 100644
--- a/src/test/i2p_tests.cpp
+++ b/src/test/i2p_tests.cpp
@@ -6,6 +6,7 @@
#include <i2p.h>
#include <logging.h>
#include <netaddress.h>
+#include <netbase.h>
#include <test/util/logging.h>
#include <test/util/net.h>
#include <test/util/setup_common.h>
@@ -38,7 +39,7 @@ public:
private:
const BCLog::Level m_prev_log_level;
- const std::function<std::unique_ptr<Sock>(const CService&)> m_create_sock_orig;
+ const std::function<std::unique_ptr<Sock>(const sa_family_t&)> m_create_sock_orig;
};
BOOST_FIXTURE_TEST_SUITE(i2p_tests, EnvTestingSetup)
@@ -46,12 +47,14 @@ BOOST_FIXTURE_TEST_SUITE(i2p_tests, EnvTestingSetup)
BOOST_AUTO_TEST_CASE(unlimited_recv)
{
// Mock CreateSock() to create MockSock.
- CreateSock = [](const CService&) {
+ CreateSock = [](const sa_family_t&) {
return std::make_unique<StaticContentsSock>(std::string(i2p::sam::MAX_MSG_SIZE + 1, 'a'));
};
CThreadInterrupt interrupt;
- i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", CService{}, &interrupt);
+ const std::optional<CService> addr{Lookup("127.0.0.1", 9000, false)};
+ const Proxy sam_proxy(addr.value(), false);
+ i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", sam_proxy, &interrupt);
{
ASSERT_DEBUG_LOG("Creating persistent SAM session");
@@ -66,7 +69,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv)
BOOST_AUTO_TEST_CASE(listen_ok_accept_fail)
{
size_t num_sockets{0};
- CreateSock = [&num_sockets](const CService&) {
+ CreateSock = [&num_sockets](const sa_family_t&) {
// clang-format off
++num_sockets;
// First socket is the control socket for creating the session.
@@ -111,8 +114,10 @@ BOOST_AUTO_TEST_CASE(listen_ok_accept_fail)
};
CThreadInterrupt interrupt;
+ const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656};
+ const Proxy sam_proxy(addr, false);
i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key",
- CService{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656},
+ sam_proxy,
&interrupt);
i2p::Connection conn;
@@ -130,7 +135,7 @@ BOOST_AUTO_TEST_CASE(damaged_private_key)
{
const auto CreateSockOrig = CreateSock;
- CreateSock = [](const CService&) {
+ CreateSock = [](const sa_family_t&) {
return std::make_unique<StaticContentsSock>("HELLO REPLY RESULT=OK VERSION=3.1\n"
"SESSION STATUS RESULT=OK DESTINATION=\n");
};
@@ -154,7 +159,9 @@ BOOST_AUTO_TEST_CASE(damaged_private_key)
BOOST_REQUIRE(WriteBinaryFile(i2p_private_key_file, file_contents));
CThreadInterrupt interrupt;
- i2p::sam::Session session(i2p_private_key_file, CService{}, &interrupt);
+ const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656};
+ const Proxy sam_proxy{addr, false};
+ i2p::sam::Session session(i2p_private_key_file, sam_proxy, &interrupt);
{
ASSERT_DEBUG_LOG("Creating persistent SAM session");
diff --git a/src/test/ipc_test.capnp b/src/test/ipc_test.capnp
new file mode 100644
index 0000000000..55a3dc2683
--- /dev/null
+++ b/src/test/ipc_test.capnp
@@ -0,0 +1,18 @@
+# Copyright (c) 2023 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+@0xd71b0fc8727fdf83;
+
+using Cxx = import "/capnp/c++.capnp";
+$Cxx.namespace("gen");
+
+using Proxy = import "/mp/proxy.capnp";
+$Proxy.include("test/ipc_test.h");
+$Proxy.includeTypes("ipc/capnp/common-types.h");
+
+interface FooInterface $Proxy.wrap("FooImplementation") {
+ add @0 (a :Int32, b :Int32) -> (result :Int32);
+ passOutPoint @1 (arg :Data) -> (result :Data);
+ passUniValue @2 (arg :Text) -> (result :Text);
+}
diff --git a/src/test/ipc_test.cpp b/src/test/ipc_test.cpp
new file mode 100644
index 0000000000..ce4edaceb0
--- /dev/null
+++ b/src/test/ipc_test.cpp
@@ -0,0 +1,67 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <logging.h>
+#include <mp/proxy-types.h>
+#include <test/ipc_test.capnp.h>
+#include <test/ipc_test.capnp.proxy.h>
+#include <test/ipc_test.h>
+
+#include <future>
+#include <kj/common.h>
+#include <kj/memory.h>
+#include <kj/test.h>
+
+#include <boost/test/unit_test.hpp>
+
+//! Unit test that tests execution of IPC calls without actually creating a
+//! separate process. This test is primarily intended to verify behavior of type
+//! conversion code that converts C++ objects to Cap'n Proto messages and vice
+//! versa.
+//!
+//! The test creates a thread which creates a FooImplementation object (defined
+//! in ipc_test.h) and a two-way pipe accepting IPC requests which call methods
+//! on the object through FooInterface (defined in ipc_test.capnp).
+void IpcTest()
+{
+ // Setup: create FooImplemention object and listen for FooInterface requests
+ std::promise<std::unique_ptr<mp::ProxyClient<gen::FooInterface>>> foo_promise;
+ std::function<void()> disconnect_client;
+ std::thread thread([&]() {
+ mp::EventLoop loop("IpcTest", [](bool raise, const std::string& log) { LogPrintf("LOG%i: %s\n", raise, log); });
+ auto pipe = loop.m_io_context.provider->newTwoWayPipe();
+
+ auto connection_client = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[0]));
+ auto foo_client = std::make_unique<mp::ProxyClient<gen::FooInterface>>(
+ connection_client->m_rpc_system.bootstrap(mp::ServerVatId().vat_id).castAs<gen::FooInterface>(),
+ connection_client.get(), /* destroy_connection= */ false);
+ foo_promise.set_value(std::move(foo_client));
+ disconnect_client = [&] { loop.sync([&] { connection_client.reset(); }); };
+
+ auto connection_server = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[1]), [&](mp::Connection& connection) {
+ auto foo_server = kj::heap<mp::ProxyServer<gen::FooInterface>>(std::make_shared<FooImplementation>(), connection);
+ return capnp::Capability::Client(kj::mv(foo_server));
+ });
+ connection_server->onDisconnect([&] { connection_server.reset(); });
+ loop.loop();
+ });
+ std::unique_ptr<mp::ProxyClient<gen::FooInterface>> foo{foo_promise.get_future().get()};
+
+ // Test: make sure arguments were sent and return value is received
+ BOOST_CHECK_EQUAL(foo->add(1, 2), 3);
+
+ COutPoint txout1{Txid::FromUint256(uint256{100}), 200};
+ COutPoint txout2{foo->passOutPoint(txout1)};
+ BOOST_CHECK(txout1 == txout2);
+
+ UniValue uni1{UniValue::VOBJ};
+ uni1.pushKV("i", 1);
+ uni1.pushKV("s", "two");
+ UniValue uni2{foo->passUniValue(uni1)};
+ BOOST_CHECK_EQUAL(uni1.write(), uni2.write());
+
+ // Test cleanup: disconnect pipe and join thread
+ disconnect_client();
+ thread.join();
+}
diff --git a/src/test/ipc_test.h b/src/test/ipc_test.h
new file mode 100644
index 0000000000..bcfcc2125c
--- /dev/null
+++ b/src/test/ipc_test.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_TEST_IPC_TEST_H
+#define BITCOIN_TEST_IPC_TEST_H
+
+#include <primitives/transaction.h>
+#include <univalue.h>
+
+class FooImplementation
+{
+public:
+ int add(int a, int b) { return a + b; }
+ COutPoint passOutPoint(COutPoint o) { return o; }
+ UniValue passUniValue(UniValue v) { return v; }
+};
+
+void IpcTest();
+
+#endif // BITCOIN_TEST_IPC_TEST_H
diff --git a/src/test/ipc_tests.cpp b/src/test/ipc_tests.cpp
new file mode 100644
index 0000000000..6e144b0f41
--- /dev/null
+++ b/src/test/ipc_tests.cpp
@@ -0,0 +1,13 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <test/ipc_test.h>
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_SUITE(ipc_tests)
+BOOST_AUTO_TEST_CASE(ipc_tests)
+{
+ IpcTest();
+}
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/main.cpp b/src/test/main.cpp
index 0809f83c93..67740ece93 100644
--- a/src/test/main.cpp
+++ b/src/test/main.cpp
@@ -39,3 +39,10 @@ const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS =
}
return args;
};
+
+/**
+ * Retrieve the boost unit test name.
+ */
+const std::function<std::string()> G_TEST_GET_FULL_NAME = []() {
+ return boost::unit_test::framework::current_test_case().full_name();
+};
diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp
index 217e4a6d22..9f8d434213 100644
--- a/src/test/mempool_tests.cpp
+++ b/src/test/mempool_tests.cpp
@@ -202,9 +202,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx7.vout[1].nValue = 1 * COIN;
- auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), CTxMemPool::Limits::NoLimits())};
- BOOST_REQUIRE(ancestors_calculated.has_value());
- BOOST_CHECK(*ancestors_calculated == setAncestors);
+ {
+ auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), CTxMemPool::Limits::NoLimits())};
+ BOOST_REQUIRE(ancestors_calculated.has_value());
+ BOOST_CHECK(*ancestors_calculated == setAncestors);
+ }
pool.addUnchecked(entry.FromTx(tx7), setAncestors);
BOOST_CHECK_EQUAL(pool.size(), 7U);
@@ -260,9 +262,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx10.vout[0].nValue = 10 * COIN;
- ancestors_calculated = pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits());
- BOOST_REQUIRE(ancestors_calculated);
- BOOST_CHECK(*ancestors_calculated == setAncestors);
+ {
+ auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits())};
+ BOOST_REQUIRE(ancestors_calculated);
+ BOOST_CHECK(*ancestors_calculated == setAncestors);
+ }
pool.addUnchecked(entry.FromTx(tx10), setAncestors);
diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp
index 342d2514ed..d50af4c175 100644
--- a/src/test/miner_tests.cpp
+++ b/src/test/miner_tests.cpp
@@ -12,7 +12,6 @@
#include <policy/policy.h>
#include <test/util/random.h>
#include <test/util/txmempool.h>
-#include <timedata.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/strencodings.h>
diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp
index 1f28e61bc9..a3699f84b6 100644
--- a/src/test/miniscript_tests.cpp
+++ b/src/test/miniscript_tests.cpp
@@ -297,6 +297,7 @@ using miniscript::operator"" _mst;
using Node = miniscript::Node<CPubKey>;
/** Compute all challenges (pubkeys, hashes, timelocks) that occur in a given Miniscript. */
+// NOLINTNEXTLINE(misc-no-recursion)
std::set<Challenge> FindChallenges(const NodeRef& ref) {
std::set<Challenge> chal;
for (const auto& key : ref->keys) {
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index 70a8279f04..b9dff96610 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -19,7 +19,6 @@
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <test/util/validation.h>
-#include <timedata.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <validation.h>
@@ -902,10 +901,6 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
chainman.ResetIbd();
m_node.args->ForceSetArg("-capturemessages", "0");
m_node.args->ForceSetArg("-bind", "");
- // PeerManager::ProcessMessage() calls AddTimeData() which changes the internal state
- // in timedata.cpp and later confuses the test "timedata_tests/addtimedata". Thus reset
- // that state as it was before our test was run.
- TestOnlyResetTimeData();
}
diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index fa70f62eb4..3422cb8023 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -366,6 +366,7 @@ BOOST_AUTO_TEST_CASE(netpermissions_test)
bilingual_str error;
NetWhitebindPermissions whitebindPermissions;
NetWhitelistPermissions whitelistPermissions;
+ ConnectionDirection connection_direction;
// Detect invalid white bind
BOOST_CHECK(!NetWhitebindPermissions::TryParse("", whitebindPermissions, error));
@@ -435,24 +436,33 @@ BOOST_AUTO_TEST_CASE(netpermissions_test)
BOOST_CHECK(NetWhitebindPermissions::TryParse(",,@1.2.3.4:32", whitebindPermissions, error));
BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, NetPermissionFlags::None);
+ BOOST_CHECK(!NetWhitebindPermissions::TryParse("out,forcerelay@1.2.3.4:32", whitebindPermissions, error));
+ BOOST_CHECK(error.original.find("whitebind may only be used for incoming connections (\"out\" was passed)") != std::string::npos);
+
// Detect invalid flag
BOOST_CHECK(!NetWhitebindPermissions::TryParse("bloom,forcerelay,oopsie@1.2.3.4:32", whitebindPermissions, error));
BOOST_CHECK(error.original.find("Invalid P2P permission") != std::string::npos);
// Check netmask error
- BOOST_CHECK(!NetWhitelistPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitelistPermissions, error));
+ BOOST_CHECK(!NetWhitelistPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitelistPermissions, connection_direction, error));
BOOST_CHECK(error.original.find("Invalid netmask specified in -whitelist") != std::string::npos);
// Happy path for whitelist parsing
- BOOST_CHECK(NetWhitelistPermissions::TryParse("noban@1.2.3.4", whitelistPermissions, error));
+ BOOST_CHECK(NetWhitelistPermissions::TryParse("noban@1.2.3.4", whitelistPermissions, connection_direction, error));
BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, NetPermissionFlags::NoBan);
BOOST_CHECK(NetPermissions::HasFlag(whitelistPermissions.m_flags, NetPermissionFlags::NoBan));
- BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay@1.2.3.4/32", whitelistPermissions, error));
+ BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay@1.2.3.4/32", whitelistPermissions, connection_direction, error));
BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, NetPermissionFlags::BloomFilter | NetPermissionFlags::ForceRelay | NetPermissionFlags::NoBan | NetPermissionFlags::Relay);
BOOST_CHECK(error.empty());
BOOST_CHECK_EQUAL(whitelistPermissions.m_subnet.ToString(), "1.2.3.4/32");
- BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay,mempool@1.2.3.4/32", whitelistPermissions, error));
+ BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay,mempool@1.2.3.4/32", whitelistPermissions, connection_direction, error));
+ BOOST_CHECK(NetWhitelistPermissions::TryParse("in,relay@1.2.3.4", whitelistPermissions, connection_direction, error));
+ BOOST_CHECK_EQUAL(connection_direction, ConnectionDirection::In);
+ BOOST_CHECK(NetWhitelistPermissions::TryParse("out,bloom@1.2.3.4", whitelistPermissions, connection_direction, error));
+ BOOST_CHECK_EQUAL(connection_direction, ConnectionDirection::Out);
+ BOOST_CHECK(NetWhitelistPermissions::TryParse("in,out,bloom@1.2.3.4", whitelistPermissions, connection_direction, error));
+ BOOST_CHECK_EQUAL(connection_direction, ConnectionDirection::Both);
const auto strings = NetPermissions::ToStrings(NetPermissionFlags::All);
BOOST_CHECK_EQUAL(strings.size(), 7U);
diff --git a/src/test/orphanage_tests.cpp b/src/test/orphanage_tests.cpp
index 4231fcc909..450bf6a4fc 100644
--- a/src/test/orphanage_tests.cpp
+++ b/src/test/orphanage_tests.cpp
@@ -30,22 +30,74 @@ public:
CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
{
LOCK(m_mutex);
- std::map<Txid, OrphanTx>::iterator it;
- it = m_orphans.lower_bound(Txid::FromUint256(InsecureRand256()));
+ std::map<Wtxid, OrphanTx>::iterator it;
+ it = m_orphans.lower_bound(Wtxid::FromUint256(InsecureRand256()));
if (it == m_orphans.end())
it = m_orphans.begin();
return it->second.tx;
}
};
-static void MakeNewKeyWithFastRandomContext(CKey& key)
+static void MakeNewKeyWithFastRandomContext(CKey& key, FastRandomContext& rand_ctx = g_insecure_rand_ctx)
{
std::vector<unsigned char> keydata;
- keydata = g_insecure_rand_ctx.randbytes(32);
+ keydata = rand_ctx.randbytes(32);
key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn=*/true);
assert(key.IsValid());
}
+// Creates a transaction with 2 outputs. Spends all outpoints. If outpoints is empty, spends a random one.
+static CTransactionRef MakeTransactionSpending(const std::vector<COutPoint>& outpoints, FastRandomContext& det_rand)
+{
+ CKey key;
+ MakeNewKeyWithFastRandomContext(key, det_rand);
+ CMutableTransaction tx;
+ // If no outpoints are given, create a random one.
+ if (outpoints.empty()) {
+ tx.vin.emplace_back(Txid::FromUint256(det_rand.rand256()), 0);
+ } else {
+ for (const auto& outpoint : outpoints) {
+ tx.vin.emplace_back(outpoint);
+ }
+ }
+ // Ensure txid != wtxid
+ tx.vin[0].scriptWitness.stack.push_back({1});
+ tx.vout.resize(2);
+ tx.vout[0].nValue = CENT;
+ tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
+ tx.vout[1].nValue = 3 * CENT;
+ tx.vout[1].scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(key.GetPubKey()));
+ return MakeTransactionRef(tx);
+}
+
+// Make another (not necessarily valid) tx with the same txid but different wtxid.
+static CTransactionRef MakeMutation(const CTransactionRef& ptx)
+{
+ CMutableTransaction tx(*ptx);
+ tx.vin[0].scriptWitness.stack.push_back({5});
+ auto mutated_tx = MakeTransactionRef(tx);
+ assert(ptx->GetHash() == mutated_tx->GetHash());
+ return mutated_tx;
+}
+
+static bool EqualTxns(const std::set<CTransactionRef>& set_txns, const std::vector<CTransactionRef>& vec_txns)
+{
+ if (vec_txns.size() != set_txns.size()) return false;
+ for (const auto& tx : vec_txns) {
+ if (!set_txns.contains(tx)) return false;
+ }
+ return true;
+}
+static bool EqualTxns(const std::set<CTransactionRef>& set_txns,
+ const std::vector<std::pair<CTransactionRef, NodeId>>& vec_txns)
+{
+ if (vec_txns.size() != set_txns.size()) return false;
+ for (const auto& [tx, nodeid] : vec_txns) {
+ if (!set_txns.contains(tx)) return false;
+ }
+ return true;
+}
+
BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
{
// This test had non-deterministic coverage due to
@@ -138,4 +190,148 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
BOOST_CHECK(orphanage.CountOrphans() == 0);
}
+BOOST_AUTO_TEST_CASE(same_txid_diff_witness)
+{
+ FastRandomContext det_rand{true};
+ TxOrphanage orphanage;
+ NodeId peer{0};
+
+ std::vector<COutPoint> empty_outpoints;
+ auto parent = MakeTransactionSpending(empty_outpoints, det_rand);
+
+ // Create children to go into orphanage.
+ auto child_normal = MakeTransactionSpending({{parent->GetHash(), 0}}, det_rand);
+ auto child_mutated = MakeMutation(child_normal);
+
+ const auto& normal_wtxid = child_normal->GetWitnessHash();
+ const auto& mutated_wtxid = child_mutated->GetWitnessHash();
+ BOOST_CHECK(normal_wtxid != mutated_wtxid);
+
+ BOOST_CHECK(orphanage.AddTx(child_normal, peer));
+ // EraseTx fails as transaction by this wtxid doesn't exist.
+ BOOST_CHECK_EQUAL(orphanage.EraseTx(mutated_wtxid), 0);
+ BOOST_CHECK(orphanage.HaveTx(normal_wtxid));
+ BOOST_CHECK(!orphanage.HaveTx(mutated_wtxid));
+
+ // Must succeed. Both transactions should be present in orphanage.
+ BOOST_CHECK(orphanage.AddTx(child_mutated, peer));
+ BOOST_CHECK(orphanage.HaveTx(normal_wtxid));
+ BOOST_CHECK(orphanage.HaveTx(mutated_wtxid));
+
+ // Outpoints map should track all entries: check that both are returned as children of the parent.
+ std::set<CTransactionRef> expected_children{child_normal, child_mutated};
+ BOOST_CHECK(EqualTxns(expected_children, orphanage.GetChildrenFromSamePeer(parent, peer)));
+
+ // Erase by wtxid: mutated first
+ BOOST_CHECK_EQUAL(orphanage.EraseTx(mutated_wtxid), 1);
+ BOOST_CHECK(orphanage.HaveTx(normal_wtxid));
+ BOOST_CHECK(!orphanage.HaveTx(mutated_wtxid));
+
+ BOOST_CHECK_EQUAL(orphanage.EraseTx(normal_wtxid), 1);
+ BOOST_CHECK(!orphanage.HaveTx(normal_wtxid));
+ BOOST_CHECK(!orphanage.HaveTx(mutated_wtxid));
+}
+
+
+BOOST_AUTO_TEST_CASE(get_children)
+{
+ FastRandomContext det_rand{true};
+ std::vector<COutPoint> empty_outpoints;
+
+ auto parent1 = MakeTransactionSpending(empty_outpoints, det_rand);
+ auto parent2 = MakeTransactionSpending(empty_outpoints, det_rand);
+
+ // Make sure these parents have different txids otherwise this test won't make sense.
+ while (parent1->GetHash() == parent2->GetHash()) {
+ parent2 = MakeTransactionSpending(empty_outpoints, det_rand);
+ }
+
+ // Create children to go into orphanage.
+ auto child_p1n0 = MakeTransactionSpending({{parent1->GetHash(), 0}}, det_rand);
+ auto child_p2n1 = MakeTransactionSpending({{parent2->GetHash(), 1}}, det_rand);
+ // Spends the same tx twice. Should not cause duplicates.
+ auto child_p1n0_p1n1 = MakeTransactionSpending({{parent1->GetHash(), 0}, {parent1->GetHash(), 1}}, det_rand);
+ // Spends the same outpoint as previous tx. Should still be returned; don't assume outpoints are unique.
+ auto child_p1n0_p2n0 = MakeTransactionSpending({{parent1->GetHash(), 0}, {parent2->GetHash(), 0}}, det_rand);
+
+ const NodeId node1{1};
+ const NodeId node2{2};
+
+ // All orphans provided by node1
+ {
+ TxOrphanage orphanage;
+ BOOST_CHECK(orphanage.AddTx(child_p1n0, node1));
+ BOOST_CHECK(orphanage.AddTx(child_p2n1, node1));
+ BOOST_CHECK(orphanage.AddTx(child_p1n0_p1n1, node1));
+ BOOST_CHECK(orphanage.AddTx(child_p1n0_p2n0, node1));
+
+ std::set<CTransactionRef> expected_parent1_children{child_p1n0, child_p1n0_p2n0, child_p1n0_p1n1};
+ std::set<CTransactionRef> expected_parent2_children{child_p2n1, child_p1n0_p2n0};
+
+ BOOST_CHECK(EqualTxns(expected_parent1_children, orphanage.GetChildrenFromSamePeer(parent1, node1)));
+ BOOST_CHECK(EqualTxns(expected_parent2_children, orphanage.GetChildrenFromSamePeer(parent2, node1)));
+
+ BOOST_CHECK(EqualTxns(expected_parent1_children, orphanage.GetChildrenFromDifferentPeer(parent1, node2)));
+ BOOST_CHECK(EqualTxns(expected_parent2_children, orphanage.GetChildrenFromDifferentPeer(parent2, node2)));
+
+ // The peer must match
+ BOOST_CHECK(orphanage.GetChildrenFromSamePeer(parent1, node2).empty());
+ BOOST_CHECK(orphanage.GetChildrenFromSamePeer(parent2, node2).empty());
+
+ // There shouldn't be any children of this tx in the orphanage
+ BOOST_CHECK(orphanage.GetChildrenFromSamePeer(child_p1n0_p2n0, node1).empty());
+ BOOST_CHECK(orphanage.GetChildrenFromSamePeer(child_p1n0_p2n0, node2).empty());
+ BOOST_CHECK(orphanage.GetChildrenFromDifferentPeer(child_p1n0_p2n0, node1).empty());
+ BOOST_CHECK(orphanage.GetChildrenFromDifferentPeer(child_p1n0_p2n0, node2).empty());
+ }
+
+ // Orphans provided by node1 and node2
+ {
+ TxOrphanage orphanage;
+ BOOST_CHECK(orphanage.AddTx(child_p1n0, node1));
+ BOOST_CHECK(orphanage.AddTx(child_p2n1, node1));
+ BOOST_CHECK(orphanage.AddTx(child_p1n0_p1n1, node2));
+ BOOST_CHECK(orphanage.AddTx(child_p1n0_p2n0, node2));
+
+ // +----------------+---------------+----------------------------------+
+ // | | sender=node1 | sender=node2 |
+ // +----------------+---------------+----------------------------------+
+ // | spends parent1 | child_p1n0 | child_p1n0_p1n1, child_p1n0_p2n0 |
+ // | spends parent2 | child_p2n1 | child_p1n0_p2n0 |
+ // +----------------+---------------+----------------------------------+
+
+ // Children of parent1 from node1:
+ {
+ std::set<CTransactionRef> expected_parent1_node1{child_p1n0};
+
+ BOOST_CHECK(EqualTxns(expected_parent1_node1, orphanage.GetChildrenFromSamePeer(parent1, node1)));
+ BOOST_CHECK(EqualTxns(expected_parent1_node1, orphanage.GetChildrenFromDifferentPeer(parent1, node2)));
+ }
+
+ // Children of parent2 from node1:
+ {
+ std::set<CTransactionRef> expected_parent2_node1{child_p2n1};
+
+ BOOST_CHECK(EqualTxns(expected_parent2_node1, orphanage.GetChildrenFromSamePeer(parent2, node1)));
+ BOOST_CHECK(EqualTxns(expected_parent2_node1, orphanage.GetChildrenFromDifferentPeer(parent2, node2)));
+ }
+
+ // Children of parent1 from node2:
+ {
+ std::set<CTransactionRef> expected_parent1_node2{child_p1n0_p1n1, child_p1n0_p2n0};
+
+ BOOST_CHECK(EqualTxns(expected_parent1_node2, orphanage.GetChildrenFromSamePeer(parent1, node2)));
+ BOOST_CHECK(EqualTxns(expected_parent1_node2, orphanage.GetChildrenFromDifferentPeer(parent1, node1)));
+ }
+
+ // Children of parent2 from node2:
+ {
+ std::set<CTransactionRef> expected_parent2_node2{child_p1n0_p2n0};
+
+ BOOST_CHECK(EqualTxns(expected_parent2_node2, orphanage.GetChildrenFromSamePeer(parent2, node2)));
+ BOOST_CHECK(EqualTxns(expected_parent2_node2, orphanage.GetChildrenFromDifferentPeer(parent2, node1)));
+ }
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/peerman_tests.cpp b/src/test/peerman_tests.cpp
new file mode 100644
index 0000000000..28866695bc
--- /dev/null
+++ b/src/test/peerman_tests.cpp
@@ -0,0 +1,76 @@
+// Copyright (c) 2024-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or https://www.opensource.org/licenses/mit-license.php.
+
+#include <chainparams.h>
+#include <node/miner.h>
+#include <net_processing.h>
+#include <pow.h>
+#include <test/util/setup_common.h>
+#include <validation.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(peerman_tests, RegTestingSetup)
+
+/** Window, in blocks, for connecting to NODE_NETWORK_LIMITED peers */
+static constexpr int64_t NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS = 144;
+
+static void mineBlock(const node::NodeContext& node, std::chrono::seconds block_time)
+{
+ auto curr_time = GetTime<std::chrono::seconds>();
+ SetMockTime(block_time); // update time so the block is created with it
+ CBlock block = node::BlockAssembler{node.chainman->ActiveChainstate(), nullptr}.CreateNewBlock(CScript() << OP_TRUE)->block;
+ while (!CheckProofOfWork(block.GetHash(), block.nBits, node.chainman->GetConsensus())) ++block.nNonce;
+ block.fChecked = true; // little speedup
+ SetMockTime(curr_time); // process block at current time
+ Assert(node.chainman->ProcessNewBlock(std::make_shared<const CBlock>(block), /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
+ node.validation_signals->SyncWithValidationInterfaceQueue(); // drain events queue
+}
+
+// Verifying when network-limited peer connections are desirable based on the node's proximity to the tip
+BOOST_AUTO_TEST_CASE(connections_desirable_service_flags)
+{
+ std::unique_ptr<PeerManager> peerman = PeerManager::make(*m_node.connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, {});
+ auto consensus = m_node.chainman->GetParams().GetConsensus();
+
+ // Check we start connecting to full nodes
+ ServiceFlags peer_flags{NODE_WITNESS | NODE_NETWORK_LIMITED};
+ BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS));
+
+ // Make peerman aware of the initial best block and verify we accept limited peers when we start close to the tip time.
+ auto tip = WITH_LOCK(::cs_main, return m_node.chainman->ActiveChain().Tip());
+ uint64_t tip_block_time = tip->GetBlockTime();
+ int tip_block_height = tip->nHeight;
+ peerman->SetBestBlock(tip_block_height, std::chrono::seconds{tip_block_time});
+
+ SetMockTime(tip_block_time + 1); // Set node time to tip time
+ BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS));
+
+ // Check we don't disallow limited peers connections when we are behind but still recoverable (below the connection safety window)
+ SetMockTime(GetTime<std::chrono::seconds>() + std::chrono::seconds{consensus.nPowTargetSpacing * (NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS - 1)});
+ BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS));
+
+ // Check we disallow limited peers connections when we are further than the limited peers safety window
+ SetMockTime(GetTime<std::chrono::seconds>() + std::chrono::seconds{consensus.nPowTargetSpacing * 2});
+ BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS));
+
+ // By now, we tested that the connections desirable services flags change based on the node's time proximity to the tip.
+ // Now, perform the same tests for when the node receives a block.
+ m_node.validation_signals->RegisterValidationInterface(peerman.get());
+
+ // First, verify a block in the past doesn't enable limited peers connections
+ // At this point, our time is (NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS + 1) * 10 minutes ahead the tip's time.
+ mineBlock(m_node, /*block_time=*/std::chrono::seconds{tip_block_time + 1});
+ BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS));
+
+ // Verify a block close to the tip enables limited peers connections
+ mineBlock(m_node, /*block_time=*/GetTime<std::chrono::seconds>());
+ BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS));
+
+ // Lastly, verify the stale tip checks can disallow limited peers connections after not receiving blocks for a prolonged period.
+ SetMockTime(GetTime<std::chrono::seconds>() + std::chrono::seconds{consensus.nPowTargetSpacing * NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS + 1});
+ BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp
index ede73c6895..6cadc3290a 100644
--- a/src/test/policyestimator_tests.cpp
+++ b/src/test/policyestimator_tests.cpp
@@ -20,7 +20,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
{
CBlockPolicyEstimator& feeEst = *Assert(m_node.fee_estimator);
CTxMemPool& mpool = *Assert(m_node.mempool);
- RegisterValidationInterface(&feeEst);
+ m_node.validation_signals->RegisterValidationInterface(&feeEst);
TestMemPoolEntryHelper entry;
CAmount basefee(2000);
CAmount deltaFee(100);
@@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
/*submitted_in_package=*/false,
/*chainstate_is_current=*/true,
/*has_no_mempool_parents=*/true)};
- GetMainSignals().TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
+ m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
}
uint256 hash = tx.GetHash();
txHashes[j].push_back(hash);
@@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
// Check after just a few txs that combining buckets works as expected
if (blocknum == 3) {
// Wait for fee estimator to catch up
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
// At this point we should need to combine 3 buckets to get enough data points
// So estimateFee(1) should fail and estimateFee(2) should return somewhere around
// 9*baserate. estimateFee(2) %'s are 100,100,90 = average 97%
@@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
}
// Wait for fee estimator to catch up
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
std::vector<CAmount> origFeeEst;
// Highest feerate is 10*baseRate and gets in all blocks,
@@ -146,7 +146,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
}
// Wait for fee estimator to catch up
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 10;i++) {
@@ -175,7 +175,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
/*submitted_in_package=*/false,
/*chainstate_is_current=*/true,
/*has_no_mempool_parents=*/true)};
- GetMainSignals().TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
+ m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
}
uint256 hash = tx.GetHash();
txHashes[j].push_back(hash);
@@ -188,7 +188,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
}
// Wait for fee estimator to catch up
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
for (int i = 1; i < 10;i++) {
BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
@@ -212,7 +212,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
block.clear();
// Wait for fee estimator to catch up
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 10;i++) {
@@ -239,7 +239,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
/*submitted_in_package=*/false,
/*chainstate_is_current=*/true,
/*has_no_mempool_parents=*/true)};
- GetMainSignals().TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
+ m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
}
uint256 hash = tx.GetHash();
CTransactionRef ptx = mpool.get(hash);
@@ -257,7 +257,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
block.clear();
}
// Wait for fee estimator to catch up
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2)
BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee);
diff --git a/src/test/rbf_tests.cpp b/src/test/rbf_tests.cpp
index fb6a3614c0..19e45c550a 100644
--- a/src/test/rbf_tests.cpp
+++ b/src/test/rbf_tests.cpp
@@ -37,7 +37,38 @@ static inline CTransactionRef make_tx(const std::vector<CTransactionRef>& inputs
return MakeTransactionRef(tx);
}
-static void add_descendants(const CTransactionRef& tx, int32_t num_descendants, CTxMemPool& pool)
+// Make two child transactions from parent (which must have at least 2 outputs).
+// Each tx will have the same outputs, using the amounts specified in output_values.
+static inline std::pair<CTransactionRef, CTransactionRef> make_two_siblings(const CTransactionRef parent,
+ const std::vector<CAmount>& output_values)
+{
+ assert(parent->vout.size() >= 2);
+
+ // First tx takes first parent output
+ CMutableTransaction tx1 = CMutableTransaction();
+ tx1.vin.resize(1);
+ tx1.vout.resize(output_values.size());
+
+ tx1.vin[0].prevout.hash = parent->GetHash();
+ tx1.vin[0].prevout.n = 0;
+ // Add a witness so wtxid != txid
+ CScriptWitness witness;
+ witness.stack.emplace_back(10);
+ tx1.vin[0].scriptWitness = witness;
+
+ for (size_t i = 0; i < output_values.size(); ++i) {
+ tx1.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
+ tx1.vout[i].nValue = output_values[i];
+ }
+
+ // Second tx takes second parent output
+ CMutableTransaction tx2 = tx1;
+ tx2.vin[0].prevout.n = 1;
+
+ return std::make_pair(MakeTransactionRef(tx1), MakeTransactionRef(tx2));
+}
+
+static CTransactionRef add_descendants(const CTransactionRef& tx, int32_t num_descendants, CTxMemPool& pool)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs)
{
AssertLockHeld(::cs_main);
@@ -50,6 +81,35 @@ static void add_descendants(const CTransactionRef& tx, int32_t num_descendants,
pool.addUnchecked(entry.FromTx(next_tx));
tx_to_spend = next_tx;
}
+ // Return last created tx
+ return tx_to_spend;
+}
+
+static CTransactionRef add_descendant_to_parents(const std::vector<CTransactionRef>& parents, CTxMemPool& pool)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs)
+{
+ AssertLockHeld(::cs_main);
+ AssertLockHeld(pool.cs);
+ TestMemPoolEntryHelper entry;
+ // Assumes this isn't already spent in mempool
+ auto child_tx = make_tx(/*inputs=*/parents, /*output_values=*/{50 * CENT});
+ pool.addUnchecked(entry.FromTx(child_tx));
+ // Return last created tx
+ return child_tx;
+}
+
+// Makes two children for a single parent
+static std::pair<CTransactionRef, CTransactionRef> add_children_to_parent(const CTransactionRef parent, CTxMemPool& pool)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs)
+{
+ AssertLockHeld(::cs_main);
+ AssertLockHeld(pool.cs);
+ TestMemPoolEntryHelper entry;
+ // Assumes this isn't already spent in mempool
+ auto children_tx = make_two_siblings(/*parent=*/parent, /*output_values=*/{50 * CENT});
+ pool.addUnchecked(entry.FromTx(children_tx.first));
+ pool.addUnchecked(entry.FromTx(children_tx.second));
+ return children_tx;
}
BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
@@ -89,28 +149,51 @@ BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
const auto tx8 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {999 * CENT});
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx8));
- const auto entry1 = pool.GetIter(tx1->GetHash()).value();
- const auto entry2 = pool.GetIter(tx2->GetHash()).value();
- const auto entry3 = pool.GetIter(tx3->GetHash()).value();
- const auto entry4 = pool.GetIter(tx4->GetHash()).value();
- const auto entry5 = pool.GetIter(tx5->GetHash()).value();
- const auto entry6 = pool.GetIter(tx6->GetHash()).value();
- const auto entry7 = pool.GetIter(tx7->GetHash()).value();
- const auto entry8 = pool.GetIter(tx8->GetHash()).value();
-
- BOOST_CHECK_EQUAL(entry1->GetFee(), normal_fee);
- BOOST_CHECK_EQUAL(entry2->GetFee(), normal_fee);
- BOOST_CHECK_EQUAL(entry3->GetFee(), low_fee);
- BOOST_CHECK_EQUAL(entry4->GetFee(), high_fee);
- BOOST_CHECK_EQUAL(entry5->GetFee(), low_fee);
- BOOST_CHECK_EQUAL(entry6->GetFee(), low_fee);
- BOOST_CHECK_EQUAL(entry7->GetFee(), high_fee);
- BOOST_CHECK_EQUAL(entry8->GetFee(), high_fee);
-
- CTxMemPool::setEntries set_12_normal{entry1, entry2};
- CTxMemPool::setEntries set_34_cpfp{entry3, entry4};
- CTxMemPool::setEntries set_56_low{entry5, entry6};
- CTxMemPool::setEntries all_entries{entry1, entry2, entry3, entry4, entry5, entry6, entry7, entry8};
+ // Normal txs, will chain txns right before CheckConflictTopology test
+ const auto tx9 = make_tx(/*inputs=*/ {m_coinbase_txns[5]}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx9));
+ const auto tx10 = make_tx(/*inputs=*/ {m_coinbase_txns[6]}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx10));
+
+ // Will make these two parents of single child
+ const auto tx11 = make_tx(/*inputs=*/ {m_coinbase_txns[7]}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx11));
+ const auto tx12 = make_tx(/*inputs=*/ {m_coinbase_txns[8]}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx12));
+
+ // Will make two children of this single parent
+ const auto tx13 = make_tx(/*inputs=*/ {m_coinbase_txns[9]}, /*output_values=*/ {995 * CENT, 995 * CENT});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx13));
+
+ const auto entry1_normal = pool.GetIter(tx1->GetHash()).value();
+ const auto entry2_normal = pool.GetIter(tx2->GetHash()).value();
+ const auto entry3_low = pool.GetIter(tx3->GetHash()).value();
+ const auto entry4_high = pool.GetIter(tx4->GetHash()).value();
+ const auto entry5_low = pool.GetIter(tx5->GetHash()).value();
+ const auto entry6_low_prioritised = pool.GetIter(tx6->GetHash()).value();
+ const auto entry7_high = pool.GetIter(tx7->GetHash()).value();
+ const auto entry8_high = pool.GetIter(tx8->GetHash()).value();
+ const auto entry9_unchained = pool.GetIter(tx9->GetHash()).value();
+ const auto entry10_unchained = pool.GetIter(tx10->GetHash()).value();
+ const auto entry11_unchained = pool.GetIter(tx11->GetHash()).value();
+ const auto entry12_unchained = pool.GetIter(tx12->GetHash()).value();
+ const auto entry13_unchained = pool.GetIter(tx13->GetHash()).value();
+
+ BOOST_CHECK_EQUAL(entry1_normal->GetFee(), normal_fee);
+ BOOST_CHECK_EQUAL(entry2_normal->GetFee(), normal_fee);
+ BOOST_CHECK_EQUAL(entry3_low->GetFee(), low_fee);
+ BOOST_CHECK_EQUAL(entry4_high->GetFee(), high_fee);
+ BOOST_CHECK_EQUAL(entry5_low->GetFee(), low_fee);
+ BOOST_CHECK_EQUAL(entry6_low_prioritised->GetFee(), low_fee);
+ BOOST_CHECK_EQUAL(entry7_high->GetFee(), high_fee);
+ BOOST_CHECK_EQUAL(entry8_high->GetFee(), high_fee);
+
+ CTxMemPool::setEntries set_12_normal{entry1_normal, entry2_normal};
+ CTxMemPool::setEntries set_34_cpfp{entry3_low, entry4_high};
+ CTxMemPool::setEntries set_56_low{entry5_low, entry6_low_prioritised};
+ CTxMemPool::setEntries set_78_high{entry7_high, entry8_high};
+ CTxMemPool::setEntries all_entries{entry1_normal, entry2_normal, entry3_low, entry4_high,
+ entry5_low, entry6_low_prioritised, entry7_high, entry8_high};
CTxMemPool::setEntries empty_set;
const auto unused_txid{GetRandHash()};
@@ -118,31 +201,29 @@ BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
// Tests for PaysMoreThanConflicts
// These tests use feerate, not absolute fee.
BOOST_CHECK(PaysMoreThanConflicts(/*iters_conflicting=*/set_12_normal,
- /*replacement_feerate=*/CFeeRate(entry1->GetModifiedFee() + 1, entry1->GetTxSize() + 2),
+ /*replacement_feerate=*/CFeeRate(entry1_normal->GetModifiedFee() + 1, entry1_normal->GetTxSize() + 2),
/*txid=*/unused_txid).has_value());
// Replacement must be strictly greater than the originals.
- BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1->GetModifiedFee(), entry1->GetTxSize()), unused_txid).has_value());
- BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1->GetModifiedFee() + 1, entry1->GetTxSize()), unused_txid) == std::nullopt);
+ BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1_normal->GetModifiedFee(), entry1_normal->GetTxSize()), unused_txid).has_value());
+ BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1_normal->GetModifiedFee() + 1, entry1_normal->GetTxSize()), unused_txid) == std::nullopt);
// These tests use modified fees (including prioritisation), not base fees.
- BOOST_CHECK(PaysMoreThanConflicts({entry5}, CFeeRate(entry5->GetModifiedFee() + 1, entry5->GetTxSize()), unused_txid) == std::nullopt);
- BOOST_CHECK(PaysMoreThanConflicts({entry6}, CFeeRate(entry6->GetFee() + 1, entry6->GetTxSize()), unused_txid).has_value());
- BOOST_CHECK(PaysMoreThanConflicts({entry6}, CFeeRate(entry6->GetModifiedFee() + 1, entry6->GetTxSize()), unused_txid) == std::nullopt);
+ BOOST_CHECK(PaysMoreThanConflicts({entry5_low}, CFeeRate(entry5_low->GetModifiedFee() + 1, entry5_low->GetTxSize()), unused_txid) == std::nullopt);
+ BOOST_CHECK(PaysMoreThanConflicts({entry6_low_prioritised}, CFeeRate(entry6_low_prioritised->GetFee() + 1, entry6_low_prioritised->GetTxSize()), unused_txid).has_value());
+ BOOST_CHECK(PaysMoreThanConflicts({entry6_low_prioritised}, CFeeRate(entry6_low_prioritised->GetModifiedFee() + 1, entry6_low_prioritised->GetTxSize()), unused_txid) == std::nullopt);
// PaysMoreThanConflicts checks individual feerate, not ancestor feerate. This test compares
- // replacement_feerate and entry4's feerate, which are the same. The replacement_feerate is
- // considered too low even though entry4 has a low ancestor feerate.
- BOOST_CHECK(PaysMoreThanConflicts(set_34_cpfp, CFeeRate(entry4->GetModifiedFee(), entry4->GetTxSize()), unused_txid).has_value());
+ // replacement_feerate and entry4_high's feerate, which are the same. The replacement_feerate is
+ // considered too low even though entry4_high has a low ancestor feerate.
+ BOOST_CHECK(PaysMoreThanConflicts(set_34_cpfp, CFeeRate(entry4_high->GetModifiedFee(), entry4_high->GetTxSize()), unused_txid).has_value());
// Tests for EntriesAndTxidsDisjoint
BOOST_CHECK(EntriesAndTxidsDisjoint(empty_set, {tx1->GetHash()}, unused_txid) == std::nullopt);
BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx3->GetHash()}, unused_txid) == std::nullopt);
- // EntriesAndTxidsDisjoint uses txids, not wtxids.
- BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx2->GetWitnessHash()}, unused_txid) == std::nullopt);
- BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx2->GetHash()}, unused_txid).has_value());
+ BOOST_CHECK(EntriesAndTxidsDisjoint({entry2_normal}, {tx2->GetHash()}, unused_txid).has_value());
BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx1->GetHash()}, unused_txid).has_value());
BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx2->GetHash()}, unused_txid).has_value());
// EntriesAndTxidsDisjoint does not calculate descendants of iters_conflicting; it uses whatever
- // the caller passed in. As such, no error is returned even though entry2 is a descendant of tx1.
- BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx1->GetHash()}, unused_txid) == std::nullopt);
+ // the caller passed in. As such, no error is returned even though entry2_normal is a descendant of tx1.
+ BOOST_CHECK(EntriesAndTxidsDisjoint({entry2_normal}, {tx1->GetHash()}, unused_txid) == std::nullopt);
// Tests for PaysForRBF
const CFeeRate incremental_relay_feerate{DEFAULT_INCREMENTAL_RELAY_FEE};
@@ -165,8 +246,8 @@ BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
BOOST_CHECK(PaysForRBF(low_fee, high_fee + 99999999, 99999999, incremental_relay_feerate, unused_txid) == std::nullopt);
// Tests for GetEntriesForConflicts
- CTxMemPool::setEntries all_parents{entry1, entry3, entry5, entry7, entry8};
- CTxMemPool::setEntries all_children{entry2, entry4, entry6};
+ CTxMemPool::setEntries all_parents{entry1_normal, entry3_low, entry5_low, entry7_high, entry8_high};
+ CTxMemPool::setEntries all_children{entry2_normal, entry4_high, entry6_low_prioritised};
const std::vector<CTransactionRef> parent_inputs({m_coinbase_txns[0], m_coinbase_txns[1], m_coinbase_txns[2],
m_coinbase_txns[3], m_coinbase_txns[4]});
const auto conflicts_with_parents = make_tx(parent_inputs, {50 * CENT});
@@ -217,15 +298,328 @@ BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
BOOST_CHECK(HasNoNewUnconfirmed(/*tx=*/ *spends_unconfirmed.get(),
/*pool=*/ pool,
/*iters_conflicting=*/ all_entries) == std::nullopt);
- BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, {entry2}) == std::nullopt);
+ BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, {entry2_normal}) == std::nullopt);
BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, empty_set).has_value());
const auto spends_new_unconfirmed = make_tx({tx1, tx8}, {36 * CENT});
- BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, {entry2}).has_value());
+ BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, {entry2_normal}).has_value());
BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, all_entries).has_value());
const auto spends_conflicting_confirmed = make_tx({m_coinbase_txns[0], m_coinbase_txns[1]}, {45 * CENT});
- BOOST_CHECK(HasNoNewUnconfirmed(*spends_conflicting_confirmed.get(), pool, {entry1, entry3}) == std::nullopt);
+ BOOST_CHECK(HasNoNewUnconfirmed(*spends_conflicting_confirmed.get(), pool, {entry1_normal, entry3_low}) == std::nullopt);
+
+ // Tests for CheckConflictTopology
+
+ // Tx4 has 23 descendants
+ BOOST_CHECK_EQUAL(pool.CheckConflictTopology(set_34_cpfp).value(), strprintf("%s has 23 descendants, max 1 allowed", entry4_high->GetSharedTx()->GetHash().ToString()));
+
+ // No descendants yet
+ BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained}) == std::nullopt);
+
+ // Add 1 descendant, still ok
+ add_descendants(tx9, 1, pool);
+ BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained}) == std::nullopt);
+
+ // N direct conflicts; ok
+ BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained, entry10_unchained, entry11_unchained}) == std::nullopt);
+
+ // Add 1 descendant, still ok, even if it's considered a direct conflict as well
+ const auto child_tx = add_descendants(tx10, 1, pool);
+ const auto entry10_child = pool.GetIter(child_tx->GetHash()).value();
+ BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained, entry10_unchained, entry11_unchained}) == std::nullopt);
+ BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained, entry10_unchained, entry11_unchained, entry10_child}) == std::nullopt);
+
+ // One more, size 3 cluster too much
+ const auto grand_child_tx = add_descendants(child_tx, 1, pool);
+ const auto entry10_grand_child = pool.GetIter(grand_child_tx->GetHash()).value();
+ BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry9_unchained, entry10_unchained, entry11_unchained}).value(), strprintf("%s has 2 descendants, max 1 allowed", entry10_unchained->GetSharedTx()->GetHash().ToString()));
+ // even if direct conflict is descendent itself
+ BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry9_unchained, entry10_grand_child, entry11_unchained}).value(), strprintf("%s has 2 ancestors, max 1 allowed", entry10_grand_child->GetSharedTx()->GetHash().ToString()));
+
+ // Make a single child from two singleton parents
+ const auto two_parent_child_tx = add_descendant_to_parents({tx11, tx12}, pool);
+ const auto entry_two_parent_child = pool.GetIter(two_parent_child_tx->GetHash()).value();
+ BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry11_unchained}).value(), strprintf("%s is not the only parent of child %s", entry11_unchained->GetSharedTx()->GetHash().ToString(), entry_two_parent_child->GetSharedTx()->GetHash().ToString()));
+ BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry12_unchained}).value(), strprintf("%s is not the only parent of child %s", entry12_unchained->GetSharedTx()->GetHash().ToString(), entry_two_parent_child->GetSharedTx()->GetHash().ToString()));
+ BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry_two_parent_child}).value(), strprintf("%s has 2 ancestors, max 1 allowed", entry_two_parent_child->GetSharedTx()->GetHash().ToString()));
+
+ // Single parent with two children, we will conflict with the siblings directly only
+ const auto two_siblings = add_children_to_parent(tx13, pool);
+ const auto entry_sibling_1 = pool.GetIter(two_siblings.first->GetHash()).value();
+ const auto entry_sibling_2 = pool.GetIter(two_siblings.second->GetHash()).value();
+ BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry_sibling_1}).value(), strprintf("%s is not the only child of parent %s", entry_sibling_1->GetSharedTx()->GetHash().ToString(), entry13_unchained->GetSharedTx()->GetHash().ToString()));
+ BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry_sibling_2}).value(), strprintf("%s is not the only child of parent %s", entry_sibling_2->GetSharedTx()->GetHash().ToString(), entry13_unchained->GetSharedTx()->GetHash().ToString()));
+
+}
+
+BOOST_FIXTURE_TEST_CASE(improves_feerate, TestChain100Setup)
+{
+ CTxMemPool& pool = *Assert(m_node.mempool);
+ LOCK2(::cs_main, pool.cs);
+ TestMemPoolEntryHelper entry;
+
+ const CAmount low_fee{CENT/100};
+ const CAmount normal_fee{CENT/10};
+
+ // low feerate parent with normal feerate child
+ const auto tx1 = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(tx1));
+ const auto tx2 = make_tx(/*inputs=*/ {tx1}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx2));
+
+ const auto entry1 = pool.GetIter(tx1->GetHash()).value();
+ const auto tx1_fee = entry1->GetModifiedFee();
+ const auto tx1_size = entry1->GetTxSize();
+ const auto entry2 = pool.GetIter(tx2->GetHash()).value();
+ const auto tx2_fee = entry2->GetModifiedFee();
+ const auto tx2_size = entry2->GetTxSize();
+
+ // Now test ImprovesFeerateDiagram with various levels of "package rbf" feerates
+
+ // It doesn't improve itself
+ const auto res1 = ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee, tx1_size + tx2_size);
+ BOOST_CHECK(res1.has_value());
+ BOOST_CHECK(res1.value().first == DiagramCheckError::FAILURE);
+ BOOST_CHECK(res1.value().second == "insufficient feerate: does not improve feerate diagram");
+
+ // With one more satoshi it does
+ BOOST_CHECK(ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee + 1, tx1_size + tx2_size) == std::nullopt);
+
+ // With prioritisation of in-mempool conflicts, it affects the results of the comparison using the same args as just above
+ pool.PrioritiseTransaction(entry1->GetSharedTx()->GetHash(), /*nFeeDelta=*/1);
+ const auto res2 = ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee + 1, tx1_size + tx2_size);
+ BOOST_CHECK(res2.has_value());
+ BOOST_CHECK(res2.value().first == DiagramCheckError::FAILURE);
+ BOOST_CHECK(res2.value().second == "insufficient feerate: does not improve feerate diagram");
+ pool.PrioritiseTransaction(entry1->GetSharedTx()->GetHash(), /*nFeeDelta=*/-1);
+
+ // With one less vB it does
+ BOOST_CHECK(ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee, tx1_size + tx2_size - 1) == std::nullopt);
+
+ // Adding a grandchild makes the cluster size 3, which is uncalculable
+ const auto tx3 = make_tx(/*inputs=*/ {tx2}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx3));
+ const auto res3 = ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee + 1, tx1_size + tx2_size);
+ BOOST_CHECK(res3.has_value());
+ BOOST_CHECK(res3.value().first == DiagramCheckError::UNCALCULABLE);
+ BOOST_CHECK(res3.value().second == strprintf("%s has 2 descendants, max 1 allowed", tx1->GetHash().GetHex()));
+
+}
+
+BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
+{
+ CTxMemPool& pool = *Assert(m_node.mempool);
+ LOCK2(::cs_main, pool.cs);
+ TestMemPoolEntryHelper entry;
+
+ const CAmount low_fee{CENT/100};
+ const CAmount normal_fee{CENT/10};
+ const CAmount high_fee{CENT};
+
+ // low -> high -> medium fee transactions that would result in two chunks together since they
+ // are all same size
+ const auto low_tx = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx));
+
+ const auto entry_low = pool.GetIter(low_tx->GetHash()).value();
+ const auto low_size = entry_low->GetTxSize();
+
+ // Replacement of size 1
+ {
+ const auto replace_one{pool.CalculateChunksForRBF(/*replacement_fees=*/0, /*replacement_vsize=*/1, {entry_low}, {entry_low})};
+ BOOST_CHECK(replace_one.has_value());
+ std::vector<FeeFrac> expected_old_chunks{{low_fee, low_size}};
+ BOOST_CHECK(replace_one->first == expected_old_chunks);
+ std::vector<FeeFrac> expected_new_chunks{{0, 1}};
+ BOOST_CHECK(replace_one->second == expected_new_chunks);
+ }
+
+ // Non-zero replacement fee/size
+ {
+ const auto replace_one_fee{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low})};
+ BOOST_CHECK(replace_one_fee.has_value());
+ std::vector<FeeFrac> expected_old_diagram{{low_fee, low_size}};
+ BOOST_CHECK(replace_one_fee->first == expected_old_diagram);
+ std::vector<FeeFrac> expected_new_diagram{{high_fee, low_size}};
+ BOOST_CHECK(replace_one_fee->second == expected_new_diagram);
+ }
+
+ // Add a second transaction to the cluster that will make a single chunk, to be evicted in the RBF
+ const auto high_tx = make_tx(/*inputs=*/ {low_tx}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx));
+ const auto entry_high = pool.GetIter(high_tx->GetHash()).value();
+ const auto high_size = entry_high->GetTxSize();
+
+ {
+ const auto replace_single_chunk{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low, entry_high})};
+ BOOST_CHECK(replace_single_chunk.has_value());
+ std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
+ BOOST_CHECK(replace_single_chunk->first == expected_old_chunks);
+ std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size}};
+ BOOST_CHECK(replace_single_chunk->second == expected_new_chunks);
+ }
+
+ // Conflict with the 2nd tx, resulting in new diagram with three entries
+ {
+ const auto replace_cpfp_child{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high}, {entry_high})};
+ BOOST_CHECK(replace_cpfp_child.has_value());
+ std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
+ BOOST_CHECK(replace_cpfp_child->first == expected_old_chunks);
+ std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size}, {low_fee, low_size}};
+ BOOST_CHECK(replace_cpfp_child->second == expected_new_chunks);
+ }
+
+ // third transaction causes the topology check to fail
+ const auto normal_tx = make_tx(/*inputs=*/ {high_tx}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(normal_fee).FromTx(normal_tx));
+ const auto entry_normal = pool.GetIter(normal_tx->GetHash()).value();
+ const auto normal_size = entry_normal->GetTxSize();
+
+ {
+ const auto replace_too_large{pool.CalculateChunksForRBF(/*replacement_fees=*/normal_fee, /*replacement_vsize=*/normal_size, {entry_low}, {entry_low, entry_high, entry_normal})};
+ BOOST_CHECK(!replace_too_large.has_value());
+ BOOST_CHECK_EQUAL(util::ErrorString(replace_too_large).original, strprintf("%s has 2 descendants, max 1 allowed", low_tx->GetHash().GetHex()));
+ }
+
+ // Make a size 2 cluster that is itself two chunks; evict both txns
+ const auto high_tx_2 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {10 * COIN});
+ pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx_2));
+ const auto entry_high_2 = pool.GetIter(high_tx_2->GetHash()).value();
+ const auto high_size_2 = entry_high_2->GetTxSize();
+
+ const auto low_tx_2 = make_tx(/*inputs=*/ {high_tx_2}, /*output_values=*/ {9 * COIN});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx_2));
+ const auto entry_low_2 = pool.GetIter(low_tx_2->GetHash()).value();
+ const auto low_size_2 = entry_low_2->GetTxSize();
+
+ {
+ const auto replace_two_chunks_single_cluster{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high_2}, {entry_high_2, entry_low_2})};
+ BOOST_CHECK(replace_two_chunks_single_cluster.has_value());
+ std::vector<FeeFrac> expected_old_chunks{{high_fee, high_size_2}, {low_fee, low_size_2}};
+ BOOST_CHECK(replace_two_chunks_single_cluster->first == expected_old_chunks);
+ std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size_2}};
+ BOOST_CHECK(replace_two_chunks_single_cluster->second == expected_new_chunks);
+ }
+
+ // You can have more than two direct conflicts if the there are multiple affected clusters, all of size 2 or less
+ const auto conflict_1 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {10 * COIN});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1));
+ const auto conflict_1_entry = pool.GetIter(conflict_1->GetHash()).value();
+
+ const auto conflict_2 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {10 * COIN});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_2));
+ const auto conflict_2_entry = pool.GetIter(conflict_2->GetHash()).value();
+
+ const auto conflict_3 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {10 * COIN});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_3));
+ const auto conflict_3_entry = pool.GetIter(conflict_3->GetHash()).value();
+
+ {
+ const auto replace_multiple_clusters{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry})};
+ BOOST_CHECK(replace_multiple_clusters.has_value());
+ BOOST_CHECK(replace_multiple_clusters->first.size() == 3);
+ BOOST_CHECK(replace_multiple_clusters->second.size() == 1);
+ }
+
+ // Add a child transaction to conflict_1 and make it cluster size 2, two chunks due to same feerate
+ const auto conflict_1_child = make_tx(/*inputs=*/{conflict_1}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1_child));
+ const auto conflict_1_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
+
+ {
+ const auto replace_multiple_clusters_2{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry})};
+
+ BOOST_CHECK(replace_multiple_clusters_2.has_value());
+ BOOST_CHECK(replace_multiple_clusters_2->first.size() == 4);
+ BOOST_CHECK(replace_multiple_clusters_2->second.size() == 1);
+ }
+
+ // Add another descendant to conflict_1, making the cluster size > 2 should fail at this point.
+ const auto conflict_1_grand_child = make_tx(/*inputs=*/{conflict_1_child}, /*output_values=*/ {995 * CENT});
+ pool.addUnchecked(entry.Fee(high_fee).FromTx(conflict_1_grand_child));
+ const auto conflict_1_grand_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
+
+ {
+ const auto replace_cluster_size_3{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry, conflict_1_grand_child_entry})};
+
+ BOOST_CHECK(!replace_cluster_size_3.has_value());
+ BOOST_CHECK_EQUAL(util::ErrorString(replace_cluster_size_3).original, strprintf("%s has 2 descendants, max 1 allowed", conflict_1->GetHash().GetHex()));
+ }
+}
+
+BOOST_AUTO_TEST_CASE(feerate_chunks_utilities)
+{
+ // Sanity check the correctness of the feerate chunks comparison.
+
+ // A strictly better case.
+ std::vector<FeeFrac> old_chunks{{{950, 300}, {100, 100}}};
+ std::vector<FeeFrac> new_chunks{{{1000, 300}, {50, 100}}};
+
+ BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
+
+ // Incomparable diagrams
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{1000, 300}, {0, 100}};
+
+ BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
+ BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
+
+ // Strictly better but smaller size.
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{1100, 300}};
+
+ BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
+
+ // New diagram is strictly better due to the first chunk, even though
+ // second chunk contributes no fees
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{1100, 100}, {0, 100}};
+
+ BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
+
+ // Feerate of first new chunk is better with, but second chunk is worse
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{750, 100}, {249, 250}, {151, 650}};
+
+ BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
+ BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
+
+ // If we make the second chunk slightly better, the new diagram now wins.
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{750, 100}, {250, 250}, {150, 150}};
+
+ BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
+
+ // Identical diagrams, cannot be strictly better
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{950, 300}, {100, 100}};
+
+ BOOST_CHECK(std::is_eq(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_eq(CompareChunks(new_chunks, old_chunks)));
+
+ // Same aggregate fee, but different total size (trigger single tail fee check step)
+ old_chunks = {{950, 300}, {100, 99}};
+ new_chunks = {{950, 300}, {100, 100}};
+
+ // No change in evaluation when tail check needed.
+ BOOST_CHECK(std::is_gt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_lt(CompareChunks(new_chunks, old_chunks)));
+
+ // Trigger multiple tail fee check steps
+ old_chunks = {{950, 300}, {100, 99}};
+ new_chunks = {{950, 300}, {100, 100}, {0, 1}, {0, 1}};
+
+ BOOST_CHECK(std::is_gt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_lt(CompareChunks(new_chunks, old_chunks)));
+
+ // Multiple tail fee check steps, unordered result
+ new_chunks = {{950, 300}, {100, 100}, {0, 1}, {0, 1}, {1, 1}};
+ BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
+ BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp
index 0d2460c606..acacb6257d 100644
--- a/src/test/rpc_tests.cpp
+++ b/src/test/rpc_tests.cpp
@@ -291,6 +291,7 @@ BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values)
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("1e-8")), COIN/100000000);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.1e-7")), COIN/100000000);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.01e-6")), COIN/100000000);
+ BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.00000000000000000000000000000000000001e+30")), 1);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.0000000000000000000000000000000000000000000000000000000000000000000000000001e+68")), COIN/100000000);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("10000000000000000000000000000000000000000000000000000000000000000e-64")), COIN);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000e64")), COIN);
@@ -581,4 +582,72 @@ BOOST_AUTO_TEST_CASE(help_example)
BOOST_CHECK_NE(HelpExampleRpcNamed("foo", {{"arg", true}}), HelpExampleRpcNamed("foo", {{"arg", "true"}}));
}
+static void CheckRpc(const std::vector<RPCArg>& params, const UniValue& args, RPCHelpMan::RPCMethodImpl test_impl)
+{
+ auto null_result{RPCResult{RPCResult::Type::NONE, "", "None"}};
+ const RPCHelpMan rpc{"dummy", "dummy description", params, null_result, RPCExamples{""}, test_impl};
+ JSONRPCRequest req;
+ req.params = args;
+
+ rpc.HandleRequest(req);
+}
+
+BOOST_AUTO_TEST_CASE(rpc_arg_helper)
+{
+ constexpr bool DEFAULT_BOOL = true;
+ constexpr auto DEFAULT_STRING = "default";
+ constexpr uint64_t DEFAULT_UINT64_T = 3;
+
+ //! Parameters with which the RPCHelpMan is instantiated
+ const std::vector<RPCArg> params{
+ // Required arg
+ {"req_int", RPCArg::Type::NUM, RPCArg::Optional::NO, ""},
+ {"req_str", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
+ // Default arg
+ {"def_uint64_t", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_UINT64_T}, ""},
+ {"def_string", RPCArg::Type::STR, RPCArg::Default{DEFAULT_STRING}, ""},
+ {"def_bool", RPCArg::Type::BOOL, RPCArg::Default{DEFAULT_BOOL}, ""},
+ // Optional arg without default
+ {"opt_double", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, ""},
+ {"opt_string", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}
+ };
+
+ //! Check that `self.Arg` returns the same value as the `request.params` accessors
+ RPCHelpMan::RPCMethodImpl check_positional = [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
+ BOOST_CHECK_EQUAL(self.Arg<int>(0), request.params[0].getInt<int>());
+ BOOST_CHECK_EQUAL(self.Arg<std::string>(1), request.params[1].get_str());
+ BOOST_CHECK_EQUAL(self.Arg<uint64_t>(2), request.params[2].isNull() ? DEFAULT_UINT64_T : request.params[2].getInt<uint64_t>());
+ BOOST_CHECK_EQUAL(self.Arg<std::string>(3), request.params[3].isNull() ? DEFAULT_STRING : request.params[3].get_str());
+ BOOST_CHECK_EQUAL(self.Arg<bool>(4), request.params[4].isNull() ? DEFAULT_BOOL : request.params[4].get_bool());
+ if (!request.params[5].isNull()) {
+ BOOST_CHECK_EQUAL(self.MaybeArg<double>(5).value(), request.params[5].get_real());
+ } else {
+ BOOST_CHECK(!self.MaybeArg<double>(5));
+ }
+ if (!request.params[6].isNull()) {
+ BOOST_CHECK(self.MaybeArg<std::string>(6));
+ BOOST_CHECK_EQUAL(*self.MaybeArg<std::string>(6), request.params[6].get_str());
+ } else {
+ BOOST_CHECK(!self.MaybeArg<std::string>(6));
+ }
+ return UniValue{};
+ };
+ CheckRpc(params, UniValue{JSON(R"([5, "hello", null, null, null, null, null])")}, check_positional);
+ CheckRpc(params, UniValue{JSON(R"([5, "hello", 4, "test", true, 1.23, "world"])")}, check_positional);
+
+ //! Check that `self.Arg` returns the same value when using index and key
+ RPCHelpMan::RPCMethodImpl check_named = [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
+ BOOST_CHECK_EQUAL(self.Arg<int>(0), self.Arg<int>("req_int"));
+ BOOST_CHECK_EQUAL(self.Arg<std::string>(1), self.Arg<std::string>("req_str"));
+ BOOST_CHECK_EQUAL(self.Arg<uint64_t>(2), self.Arg<uint64_t>("def_uint64_t"));
+ BOOST_CHECK_EQUAL(self.Arg<std::string>(3), self.Arg<std::string>("def_string"));
+ BOOST_CHECK_EQUAL(self.Arg<bool>(4), self.Arg<bool>("def_bool"));
+ BOOST_CHECK(self.MaybeArg<double>(5) == self.MaybeArg<double>("opt_double"));
+ BOOST_CHECK(self.MaybeArg<std::string>(6) == self.MaybeArg<std::string>("opt_string"));
+ return UniValue{};
+ };
+ CheckRpc(params, UniValue{JSON(R"([5, "hello", null, null, null, null, null])")}, check_named);
+ CheckRpc(params, UniValue{JSON(R"([5, "hello", 4, "test", true, 1.23, "world"])")}, check_named);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp
index 451bc99d44..68f82b760c 100644
--- a/src/test/sanity_tests.cpp
+++ b/src/test/sanity_tests.cpp
@@ -4,7 +4,6 @@
#include <key.h>
#include <test/util/setup_common.h>
-#include <util/time.h>
#include <boost/test/unit_test.hpp>
@@ -13,7 +12,6 @@ BOOST_FIXTURE_TEST_SUITE(sanity_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(basic_sanity)
{
BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "secp256k1 sanity test");
- BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test");
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp
index 3fb3378598..95e725de46 100644
--- a/src/test/scheduler_tests.cpp
+++ b/src/test/scheduler_tests.cpp
@@ -129,8 +129,8 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered)
CScheduler scheduler;
// each queue should be well ordered with respect to itself but not other queues
- SingleThreadedSchedulerClient queue1(scheduler);
- SingleThreadedSchedulerClient queue2(scheduler);
+ SerialTaskRunner queue1(scheduler);
+ SerialTaskRunner queue2(scheduler);
// create more threads than queues
// if the queues only permit execution of one task at once then
@@ -142,7 +142,7 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered)
threads.emplace_back([&] { scheduler.serviceQueue(); });
}
- // these are not atomic, if SinglethreadedSchedulerClient prevents
+ // these are not atomic, if SerialTaskRunner prevents
// parallel execution at the queue level no synchronization should be required here
int counter1 = 0;
int counter2 = 0;
@@ -150,12 +150,12 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered)
// just simply count up on each queue - if execution is properly ordered then
// the callbacks should run in exactly the order in which they were enqueued
for (int i = 0; i < 100; ++i) {
- queue1.AddToProcessQueue([i, &counter1]() {
+ queue1.insert([i, &counter1]() {
bool expectation = i == counter1++;
assert(expectation);
});
- queue2.AddToProcessQueue([i, &counter2]() {
+ queue2.insert([i, &counter2]() {
bool expectation = i == counter2++;
assert(expectation);
});
diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp
index 1f674408b2..e4142e203c 100644
--- a/src/test/script_tests.cpp
+++ b/src/test/script_tests.cpp
@@ -23,10 +23,6 @@
#include <util/fs.h>
#include <util/strencodings.h>
-#if defined(HAVE_CONSENSUS_LIB)
-#include <script/bitcoinconsensus.h>
-#endif
-
#include <cstdint>
#include <fstream>
#include <string>
@@ -139,21 +135,6 @@ void DoTest(const CScript& scriptPubKey, const CScript& scriptSig, const CScript
if (combined_flags & SCRIPT_VERIFY_WITNESS && ~combined_flags & SCRIPT_VERIFY_P2SH) continue;
BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, &scriptWitness, combined_flags, MutableTransactionSignatureChecker(&tx, 0, txCredit.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err) == expect, message + strprintf(" (with flags %x)", combined_flags));
}
-
-#if defined(HAVE_CONSENSUS_LIB)
- DataStream stream;
- stream << TX_WITH_WITNESS(tx2);
- uint32_t libconsensus_flags{flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL};
- if (libconsensus_flags == flags) {
- int expectedSuccessCode = expect ? 1 : 0;
- if (flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS) {
- BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), txCredit.vout[0].nValue, UCharCast(stream.data()), stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message);
- } else {
- BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), 0, UCharCast(stream.data()), stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message);
- BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), UCharCast(stream.data()), stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message);
- }
- }
-#endif
}
void static NegateSignatureS(std::vector<unsigned char>& vchSig) {
@@ -1273,6 +1254,30 @@ BOOST_AUTO_TEST_CASE(script_combineSigs)
BOOST_CHECK(combined.scriptSig == partial3c);
}
+/**
+ * Reproduction of an exception incorrectly raised when parsing a public key inside a TapMiniscript.
+ */
+BOOST_AUTO_TEST_CASE(sign_invalid_miniscript)
+{
+ FillableSigningProvider keystore;
+ SignatureData sig_data;
+ CMutableTransaction prev, curr;
+
+ // Create a Taproot output which contains a leaf in which a non-32 bytes push is used where a public key is expected
+ // by the Miniscript parser. This offending Script was found by the RPC fuzzer.
+ const auto invalid_pubkey{ParseHex("173d36c8c9c9c9ffffffffffff0200000000021e1e37373721361818181818181e1e1e1e19000000000000000000b19292929292926b006c9b9b9292")};
+ TaprootBuilder builder;
+ builder.Add(0, {invalid_pubkey}, 0xc0);
+ XOnlyPubKey nums{ParseHex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")};
+ builder.Finalize(nums);
+ prev.vout.emplace_back(0, GetScriptForDestination(builder.GetOutput()));
+ curr.vin.emplace_back(COutPoint{prev.GetHash(), 0});
+ sig_data.tr_spenddata = builder.GetSpendData();
+
+ // SignSignature can fail but it shouldn't raise an exception (nor crash).
+ BOOST_CHECK(!SignSignature(keystore, CTransaction(prev), curr, 0, SIGHASH_ALL, sig_data));
+}
+
BOOST_AUTO_TEST_CASE(script_standard_push)
{
ScriptError err;
@@ -1494,179 +1499,6 @@ static CScriptWitness ScriptWitnessFromJSON(const UniValue& univalue)
return scriptwitness;
}
-#if defined(HAVE_CONSENSUS_LIB)
-
-/* Test simple (successful) usage of bitcoinconsensus_verify_script */
-BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_returns_true)
-{
- unsigned int libconsensus_flags = 0;
- int nIn = 0;
-
- CScript scriptPubKey;
- CScript scriptSig;
- CScriptWitness wit;
-
- scriptPubKey << OP_1;
- CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
- CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
-
- DataStream stream;
- stream << TX_WITH_WITNESS(spendTx);
-
- bitcoinconsensus_error err;
- int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), UCharCast(stream.data()), stream.size(), nIn, libconsensus_flags, &err);
- BOOST_CHECK_EQUAL(result, 1);
- BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_OK);
-}
-
-/* Test bitcoinconsensus_verify_script returns invalid tx index err*/
-BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_index_err)
-{
- unsigned int libconsensus_flags = 0;
- int nIn = 3;
-
- CScript scriptPubKey;
- CScript scriptSig;
- CScriptWitness wit;
-
- scriptPubKey << OP_EQUAL;
- CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
- CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
-
- DataStream stream;
- stream << TX_WITH_WITNESS(spendTx);
-
- bitcoinconsensus_error err;
- int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), UCharCast(stream.data()), stream.size(), nIn, libconsensus_flags, &err);
- BOOST_CHECK_EQUAL(result, 0);
- BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_TX_INDEX);
-}
-
-/* Test bitcoinconsensus_verify_script returns tx size mismatch err*/
-BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_size)
-{
- unsigned int libconsensus_flags = 0;
- int nIn = 0;
-
- CScript scriptPubKey;
- CScript scriptSig;
- CScriptWitness wit;
-
- scriptPubKey << OP_EQUAL;
- CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
- CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
-
- DataStream stream;
- stream << TX_WITH_WITNESS(spendTx);
-
- bitcoinconsensus_error err;
- int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), UCharCast(stream.data()), stream.size() * 2, nIn, libconsensus_flags, &err);
- BOOST_CHECK_EQUAL(result, 0);
- BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_TX_SIZE_MISMATCH);
-}
-
-/* Test bitcoinconsensus_verify_script returns invalid tx serialization error */
-BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_serialization)
-{
- unsigned int libconsensus_flags = 0;
- int nIn = 0;
-
- CScript scriptPubKey;
- CScript scriptSig;
- CScriptWitness wit;
-
- scriptPubKey << OP_EQUAL;
- CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
- CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
-
- DataStream stream;
- stream << 0xffffffff;
-
- bitcoinconsensus_error err;
- int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), UCharCast(stream.data()), stream.size(), nIn, libconsensus_flags, &err);
- BOOST_CHECK_EQUAL(result, 0);
- BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_TX_DESERIALIZE);
-}
-
-/* Test bitcoinconsensus_verify_script returns amount required error */
-BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_amount_required_err)
-{
- unsigned int libconsensus_flags = bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS;
- int nIn = 0;
-
- CScript scriptPubKey;
- CScript scriptSig;
- CScriptWitness wit;
-
- scriptPubKey << OP_EQUAL;
- CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
- CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
-
- DataStream stream;
- stream << TX_WITH_WITNESS(spendTx);
-
- bitcoinconsensus_error err;
- int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), UCharCast(stream.data()), stream.size(), nIn, libconsensus_flags, &err);
- BOOST_CHECK_EQUAL(result, 0);
- BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_AMOUNT_REQUIRED);
-}
-
-/* Test bitcoinconsensus_verify_script returns invalid flags err */
-BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_invalid_flags)
-{
- unsigned int libconsensus_flags = 1 << 3;
- int nIn = 0;
-
- CScript scriptPubKey;
- CScript scriptSig;
- CScriptWitness wit;
-
- scriptPubKey << OP_EQUAL;
- CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
- CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
-
- DataStream stream;
- stream << TX_WITH_WITNESS(spendTx);
-
- bitcoinconsensus_error err;
- int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), UCharCast(stream.data()), stream.size(), nIn, libconsensus_flags, &err);
- BOOST_CHECK_EQUAL(result, 0);
- BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_INVALID_FLAGS);
-}
-
-/* Test bitcoinconsensus_verify_script returns spent outputs required err */
-BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_spent_outputs_required_err)
-{
- unsigned int libconsensus_flags{bitcoinconsensus_SCRIPT_FLAGS_VERIFY_TAPROOT};
- const int nIn{0};
-
- CScript scriptPubKey;
- CScript scriptSig;
- CScriptWitness wit;
-
- scriptPubKey << OP_EQUAL;
- CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)};
- CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)};
-
- DataStream stream;
- stream << TX_WITH_WITNESS(spendTx);
-
- bitcoinconsensus_error err;
- int result{bitcoinconsensus_verify_script_with_spent_outputs(scriptPubKey.data(), scriptPubKey.size(), creditTx.vout[0].nValue, UCharCast(stream.data()), stream.size(), nullptr, 0, nIn, libconsensus_flags, &err)};
- BOOST_CHECK_EQUAL(result, 0);
- BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_SPENT_OUTPUTS_REQUIRED);
-
- result = bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), creditTx.vout[0].nValue, UCharCast(stream.data()), stream.size(), nIn, libconsensus_flags, &err);
- BOOST_CHECK_EQUAL(result, 0);
- BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_SPENT_OUTPUTS_REQUIRED);
-
- result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), UCharCast(stream.data()), stream.size(), nIn, libconsensus_flags, &err);
- BOOST_CHECK_EQUAL(result, 0);
- BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_SPENT_OUTPUTS_REQUIRED);
-}
-
-#endif // defined(HAVE_CONSENSUS_LIB)
-
static std::vector<unsigned int> AllConsensusFlags()
{
std::vector<unsigned int> ret;
@@ -1714,28 +1546,12 @@ static void AssetTest(const UniValue& test)
txdata.Init(tx, std::vector<CTxOut>(prevouts));
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, txdata);
-#if defined(HAVE_CONSENSUS_LIB)
- DataStream stream;
- stream << TX_WITH_WITNESS(tx);
- std::vector<UTXO> utxos;
- utxos.resize(prevouts.size());
- for (size_t i = 0; i < prevouts.size(); i++) {
- utxos[i].scriptPubKey = prevouts[i].scriptPubKey.data();
- utxos[i].scriptPubKeySize = prevouts[i].scriptPubKey.size();
- utxos[i].value = prevouts[i].nValue;
- }
-#endif
-
for (const auto flags : ALL_CONSENSUS_FLAGS) {
// "final": true tests are valid for all flags. Others are only valid with flags that are
// a subset of test_flags.
if (fin || ((flags & test_flags) == flags)) {
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
BOOST_CHECK(ret);
-#if defined(HAVE_CONSENSUS_LIB)
- int lib_ret = bitcoinconsensus_verify_script_with_spent_outputs(prevouts[idx].scriptPubKey.data(), prevouts[idx].scriptPubKey.size(), prevouts[idx].nValue, UCharCast(stream.data()), stream.size(), utxos.data(), utxos.size(), idx, flags, nullptr);
- BOOST_CHECK(lib_ret == 1);
-#endif
}
}
}
@@ -1748,27 +1564,11 @@ static void AssetTest(const UniValue& test)
txdata.Init(tx, std::vector<CTxOut>(prevouts));
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, txdata);
-#if defined(HAVE_CONSENSUS_LIB)
- DataStream stream;
- stream << TX_WITH_WITNESS(tx);
- std::vector<UTXO> utxos;
- utxos.resize(prevouts.size());
- for (size_t i = 0; i < prevouts.size(); i++) {
- utxos[i].scriptPubKey = prevouts[i].scriptPubKey.data();
- utxos[i].scriptPubKeySize = prevouts[i].scriptPubKey.size();
- utxos[i].value = prevouts[i].nValue;
- }
-#endif
-
for (const auto flags : ALL_CONSENSUS_FLAGS) {
// If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
if ((flags & test_flags) == test_flags) {
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
BOOST_CHECK(!ret);
-#if defined(HAVE_CONSENSUS_LIB)
- int lib_ret = bitcoinconsensus_verify_script_with_spent_outputs(prevouts[idx].scriptPubKey.data(), prevouts[idx].scriptPubKey.size(), prevouts[idx].nValue, UCharCast(stream.data()), stream.size(), utxos.data(), utxos.size(), idx, flags, nullptr);
- BOOST_CHECK(lib_ret == 0);
-#endif
}
}
}
diff --git a/src/test/serfloat_tests.cpp b/src/test/serfloat_tests.cpp
index b36bdc02ca..304541074f 100644
--- a/src/test/serfloat_tests.cpp
+++ b/src/test/serfloat_tests.cpp
@@ -37,6 +37,7 @@ uint64_t TestDouble(double f) {
} // namespace
BOOST_AUTO_TEST_CASE(double_serfloat_tests) {
+ // Test specific values against their expected encoding.
BOOST_CHECK_EQUAL(TestDouble(0.0), 0U);
BOOST_CHECK_EQUAL(TestDouble(-0.0), 0x8000000000000000);
BOOST_CHECK_EQUAL(TestDouble(std::numeric_limits<double>::infinity()), 0x7ff0000000000000U);
@@ -46,55 +47,76 @@ BOOST_AUTO_TEST_CASE(double_serfloat_tests) {
BOOST_CHECK_EQUAL(TestDouble(2.0), 0x4000000000000000ULL);
BOOST_CHECK_EQUAL(TestDouble(4.0), 0x4010000000000000ULL);
BOOST_CHECK_EQUAL(TestDouble(785.066650390625), 0x4088888880000000ULL);
+ BOOST_CHECK_EQUAL(TestDouble(3.7243058682384174), 0x400dcb60e0031440);
+ BOOST_CHECK_EQUAL(TestDouble(91.64070592566159), 0x4056e901536d447a);
+ BOOST_CHECK_EQUAL(TestDouble(-98.63087668642575), 0xc058a860489c007a);
+ BOOST_CHECK_EQUAL(TestDouble(4.908737756962054), 0x4013a28c268b2b70);
+ BOOST_CHECK_EQUAL(TestDouble(77.9247330021754), 0x40537b2ed3547804);
+ BOOST_CHECK_EQUAL(TestDouble(40.24732825357566), 0x40441fa873c43dfc);
+ BOOST_CHECK_EQUAL(TestDouble(71.39395607929222), 0x4051d936938f27b6);
+ BOOST_CHECK_EQUAL(TestDouble(58.80100710817612), 0x404d668766a2bd70);
+ BOOST_CHECK_EQUAL(TestDouble(-30.10665786964975), 0xc03e1b4dee1e01b8);
+ BOOST_CHECK_EQUAL(TestDouble(60.15231509068704), 0x404e137f0f969814);
+ BOOST_CHECK_EQUAL(TestDouble(-48.15848711335961), 0xc04814494e445bc6);
+ BOOST_CHECK_EQUAL(TestDouble(26.68450101125353), 0x403aaf3b755169b0);
+ BOOST_CHECK_EQUAL(TestDouble(-65.72071986604303), 0xc0506e2046378ede);
+ BOOST_CHECK_EQUAL(TestDouble(17.95575825512381), 0x4031f4ac92b0a388);
+ BOOST_CHECK_EQUAL(TestDouble(-35.27171863226279), 0xc041a2c7ad17a42a);
+ BOOST_CHECK_EQUAL(TestDouble(-8.58810329425124), 0xc0212d1bdffef538);
+ BOOST_CHECK_EQUAL(TestDouble(88.51393044338977), 0x405620e43c83b1c8);
+ BOOST_CHECK_EQUAL(TestDouble(48.07224932612732), 0x4048093f77466ffc);
+ BOOST_CHECK_EQUAL(TestDouble(9.867348871395659e+117), 0x586f4daeb2459b9f);
+ BOOST_CHECK_EQUAL(TestDouble(-1.5166424385129721e+206), 0xeabe3bbc484bd458);
+ BOOST_CHECK_EQUAL(TestDouble(-8.585156555624594e-275), 0x8707c76eee012429);
+ BOOST_CHECK_EQUAL(TestDouble(2.2794371091628822e+113), 0x5777b2184458f4ee);
+ BOOST_CHECK_EQUAL(TestDouble(-1.1290476594131867e+163), 0xe1c91893d3488bb0);
+ BOOST_CHECK_EQUAL(TestDouble(9.143848423979275e-246), 0x0d0ff76e5f2620a3);
+ BOOST_CHECK_EQUAL(TestDouble(-2.8366718125941117e+81), 0xd0d7ec7e754b394a);
+ BOOST_CHECK_EQUAL(TestDouble(-1.2754409481684012e+229), 0xef80d32f8ec55342);
+ BOOST_CHECK_EQUAL(TestDouble(6.000577060053642e-186), 0x197a1be7c8209b6a);
+ BOOST_CHECK_EQUAL(TestDouble(2.0839423284378986e-302), 0x014c94f8689cb0a5);
+ BOOST_CHECK_EQUAL(TestDouble(-1.422140051483753e+259), 0xf5bd99271d04bb35);
+ BOOST_CHECK_EQUAL(TestDouble(-1.0593973991188853e+46), 0xc97db0cdb72d1046);
+ BOOST_CHECK_EQUAL(TestDouble(2.62945125875249e+190), 0x67779b36366c993b);
+ BOOST_CHECK_EQUAL(TestDouble(-2.920377657275094e+115), 0xd7e7b7b45908e23b);
+ BOOST_CHECK_EQUAL(TestDouble(9.790289014855851e-118), 0x27a3c031cc428bcc);
+ BOOST_CHECK_EQUAL(TestDouble(-4.629317182034961e-114), 0xa866ccf0b753705a);
+ BOOST_CHECK_EQUAL(TestDouble(-1.7674605603846528e+279), 0xf9e8ed383ffc3e25);
+ BOOST_CHECK_EQUAL(TestDouble(2.5308171727712605e+120), 0x58ef5cd55f0ec997);
+ BOOST_CHECK_EQUAL(TestDouble(-1.05034156412799e+54), 0xcb25eea1b9350fa0);
- // Roundtrip test on IEC559-compatible systems
- if (std::numeric_limits<double>::is_iec559) {
- BOOST_CHECK_EQUAL(sizeof(double), 8U);
- BOOST_CHECK_EQUAL(sizeof(uint64_t), 8U);
- // Test extreme values
- TestDouble(std::numeric_limits<double>::min());
- TestDouble(-std::numeric_limits<double>::min());
- TestDouble(std::numeric_limits<double>::max());
- TestDouble(-std::numeric_limits<double>::max());
- TestDouble(std::numeric_limits<double>::lowest());
- TestDouble(-std::numeric_limits<double>::lowest());
- TestDouble(std::numeric_limits<double>::quiet_NaN());
- TestDouble(-std::numeric_limits<double>::quiet_NaN());
- TestDouble(std::numeric_limits<double>::signaling_NaN());
- TestDouble(-std::numeric_limits<double>::signaling_NaN());
- TestDouble(std::numeric_limits<double>::denorm_min());
- TestDouble(-std::numeric_limits<double>::denorm_min());
- // Test exact encoding: on currently supported platforms, EncodeDouble
- // should produce exactly the same as the in-memory representation for non-NaN.
- for (int j = 0; j < 1000; ++j) {
- // Iterate over 9 specific bits exhaustively; the others are chosen randomly.
- // These specific bits are the sign bit, and the 2 top and bottom bits of
- // exponent and mantissa in the IEEE754 binary64 format.
- for (int x = 0; x < 512; ++x) {
- uint64_t v = InsecureRandBits(64);
- v &= ~(uint64_t{1} << 0);
- if (x & 1) v |= (uint64_t{1} << 0);
- v &= ~(uint64_t{1} << 1);
- if (x & 2) v |= (uint64_t{1} << 1);
- v &= ~(uint64_t{1} << 50);
- if (x & 4) v |= (uint64_t{1} << 50);
- v &= ~(uint64_t{1} << 51);
- if (x & 8) v |= (uint64_t{1} << 51);
- v &= ~(uint64_t{1} << 52);
- if (x & 16) v |= (uint64_t{1} << 52);
- v &= ~(uint64_t{1} << 53);
- if (x & 32) v |= (uint64_t{1} << 53);
- v &= ~(uint64_t{1} << 61);
- if (x & 64) v |= (uint64_t{1} << 61);
- v &= ~(uint64_t{1} << 62);
- if (x & 128) v |= (uint64_t{1} << 62);
- v &= ~(uint64_t{1} << 63);
- if (x & 256) v |= (uint64_t{1} << 63);
- double f;
- memcpy(&f, &v, 8);
- uint64_t v2 = TestDouble(f);
- if (!std::isnan(f)) BOOST_CHECK_EQUAL(v, v2);
+ // Test extreme values
+ BOOST_CHECK_EQUAL(TestDouble(std::numeric_limits<double>::min()), 0x10000000000000);
+ BOOST_CHECK_EQUAL(TestDouble(-std::numeric_limits<double>::min()), 0x8010000000000000);
+ BOOST_CHECK_EQUAL(TestDouble(std::numeric_limits<double>::max()), 0x7fefffffffffffff);
+ BOOST_CHECK_EQUAL(TestDouble(-std::numeric_limits<double>::max()), 0xffefffffffffffff);
+ BOOST_CHECK_EQUAL(TestDouble(std::numeric_limits<double>::lowest()), 0xffefffffffffffff);
+ BOOST_CHECK_EQUAL(TestDouble(-std::numeric_limits<double>::lowest()), 0x7fefffffffffffff);
+ BOOST_CHECK_EQUAL(TestDouble(std::numeric_limits<double>::denorm_min()), 0x1);
+ BOOST_CHECK_EQUAL(TestDouble(-std::numeric_limits<double>::denorm_min()), 0x8000000000000001);
+ // Note that all NaNs are encoded the same way.
+ BOOST_CHECK_EQUAL(TestDouble(std::numeric_limits<double>::quiet_NaN()), 0x7ff8000000000000);
+ BOOST_CHECK_EQUAL(TestDouble(-std::numeric_limits<double>::quiet_NaN()), 0x7ff8000000000000);
+ BOOST_CHECK_EQUAL(TestDouble(std::numeric_limits<double>::signaling_NaN()), 0x7ff8000000000000);
+ BOOST_CHECK_EQUAL(TestDouble(-std::numeric_limits<double>::signaling_NaN()), 0x7ff8000000000000);
+
+ // Construct doubles to test from the encoding.
+ static_assert(sizeof(double) == 8);
+ static_assert(sizeof(uint64_t) == 8);
+ for (int j = 0; j < 1000; ++j) {
+ // Iterate over 9 specific bits exhaustively; the others are chosen randomly.
+ // These specific bits are the sign bit, and the 2 top and bottom bits of
+ // exponent and mantissa in the IEEE754 binary64 format.
+ for (int x = 0; x < 512; ++x) {
+ uint64_t v = InsecureRandBits(64);
+ int x_pos = 0;
+ for (int v_pos : {0, 1, 50, 51, 52, 53, 61, 62, 63}) {
+ v &= ~(uint64_t{1} << v_pos);
+ if ((x >> (x_pos++)) & 1) v |= (uint64_t{1} << v_pos);
}
+ double f;
+ memcpy(&f, &v, 8);
+ TestDouble(f);
}
}
}
diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp
index fa8ceb8dd6..41190b3579 100644
--- a/src/test/settings_tests.cpp
+++ b/src/test/settings_tests.cpp
@@ -99,7 +99,9 @@ BOOST_AUTO_TEST_CASE(ReadWrite)
// Check invalid json not allowed
WriteText(path, R"(invalid json)");
BOOST_CHECK(!common::ReadSettings(path, values, errors));
- std::vector<std::string> fail_parse = {strprintf("Unable to parse settings file %s", fs::PathToString(path))};
+ std::vector<std::string> fail_parse = {strprintf("Settings file %s does not contain valid JSON. This is probably caused by disk corruption or a crash, "
+ "and can be fixed by removing the file, which will reset settings to default values.",
+ fs::PathToString(path))};
BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), fail_parse.begin(), fail_parse.end());
}
diff --git a/src/test/span_tests.cpp b/src/test/span_tests.cpp
index f6cac10b09..aae61990f7 100644
--- a/src/test/span_tests.cpp
+++ b/src/test/span_tests.cpp
@@ -53,7 +53,7 @@ BOOST_AUTO_TEST_SUITE(span_tests)
// aren't compatible with Spans at compile time.
//
// Previously there was a bug where writing a SFINAE check for vector<bool> was
-// not possible, because in libstdc++ vector<bool> has a data() memeber
+// not possible, because in libstdc++ vector<bool> has a data() member
// returning void*, and the Span template guide ignored the data() return value,
// so the template substitution would succeed, but the constructor would fail,
// resulting in a fatal compile error, rather than a SFINAE error that could be
diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp
index 7d1ac5a19a..e666e11758 100644
--- a/src/test/streams_tests.cpp
+++ b/src/test/streams_tests.cpp
@@ -29,7 +29,14 @@ BOOST_AUTO_TEST_CASE(xor_file)
BOOST_CHECK_EXCEPTION(xor_file.ignore(1), std::ios_base::failure, HasReason{"AutoFile::ignore: file handle is nullpt"});
}
{
- AutoFile xor_file{raw_file("wbx"), xor_pat};
+#ifdef __MINGW64__
+ // Our usage of mingw-w64 and the msvcrt runtime does not support
+ // the x modifier for the _wfopen().
+ const char* mode = "wb";
+#else
+ const char* mode = "wbx";
+#endif
+ AutoFile xor_file{raw_file(mode), xor_pat};
xor_file << test1 << test2;
}
{
@@ -136,8 +143,8 @@ BOOST_AUTO_TEST_CASE(streams_vector_reader)
BOOST_CHECK_EQUAL(reader.size(), 5U);
BOOST_CHECK(!reader.empty());
- // Read a single byte as a signed char.
- signed char b;
+ // Read a single byte as a int8_t.
+ int8_t b;
reader >> b;
BOOST_CHECK_EQUAL(b, -1);
BOOST_CHECK_EQUAL(reader.size(), 4U);
diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp
index 6a96b60db0..baa759e42c 100644
--- a/src/test/system_tests.cpp
+++ b/src/test/system_tests.cpp
@@ -2,12 +2,14 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
+
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <test/util/setup_common.h>
#include <common/run_command.h>
#include <univalue.h>
#ifdef ENABLE_EXTERNAL_SIGNER
-#include <boost/process.hpp>
+#include <util/subprocess.h>
#endif // ENABLE_EXTERNAL_SIGNER
#include <boost/test/unit_test.hpp>
@@ -25,48 +27,25 @@ BOOST_AUTO_TEST_CASE(dummy)
BOOST_AUTO_TEST_CASE(run_command)
{
-#ifdef WIN32
- // https://www.winehq.org/pipermail/wine-devel/2008-September/069387.html
- auto hntdll = GetModuleHandleA("ntdll.dll");
- assert(hntdll);
- const bool wine_runtime = GetProcAddress(hntdll, "wine_get_version");
-#endif
-
{
const UniValue result = RunCommandParseJSON("");
BOOST_CHECK(result.isNull());
}
{
-#ifdef WIN32
- const UniValue result = RunCommandParseJSON("cmd.exe /c echo {\"success\": true}");
-#else
- const UniValue result = RunCommandParseJSON("echo \"{\"success\": true}\"");
-#endif
+ const UniValue result = RunCommandParseJSON("echo {\"success\": true}");
BOOST_CHECK(result.isObject());
const UniValue& success = result.find_value("success");
BOOST_CHECK(!success.isNull());
BOOST_CHECK_EQUAL(success.get_bool(), true);
}
{
- // An invalid command is handled by Boost
-#ifdef WIN32
- const int expected_error{wine_runtime ? 6 : 2};
-#else
- const int expected_error{2};
-#endif
- BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), boost::process::process_error, [&](const boost::process::process_error& e) {
- BOOST_CHECK(std::string(e.what()).find("RunCommandParseJSON error:") == std::string::npos);
- BOOST_CHECK_EQUAL(e.code().value(), expected_error);
- return true;
- });
+ // An invalid command is handled by cpp-subprocess
+ const std::string expected{"execve failed: "};
+ BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), subprocess::CalledProcessError, HasReason(expected));
}
{
// Return non-zero exit code, no output to stderr
-#ifdef WIN32
- const std::string command{"cmd.exe /c exit 1"};
-#else
const std::string command{"false"};
-#endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) {
const std::string what{e.what()};
BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned 1: \n", command)) != std::string::npos);
@@ -75,13 +54,8 @@ BOOST_AUTO_TEST_CASE(run_command)
}
{
// Return non-zero exit code, with error message for stderr
-#ifdef WIN32
- const std::string command{"cmd.exe /c dir nosuchfile"};
- const std::string expected{wine_runtime ? "File not found." : "File Not Found"};
-#else
const std::string command{"ls nosuchfile"};
const std::string expected{"No such file or directory"};
-#endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) {
const std::string what(e.what());
BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned", command)) != std::string::npos);
@@ -91,15 +65,10 @@ BOOST_AUTO_TEST_CASE(run_command)
}
{
// Unable to parse JSON
-#ifdef WIN32
- const std::string command{"cmd.exe /c echo {"};
-#else
const std::string command{"echo {"};
-#endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, HasReason("Unable to parse JSON: {"));
}
- // Test std::in, except for Windows
-#ifndef WIN32
+ // Test std::in
{
const UniValue result = RunCommandParseJSON("cat", "{\"success\": true}");
BOOST_CHECK(result.isObject());
@@ -107,7 +76,6 @@ BOOST_AUTO_TEST_CASE(run_command)
BOOST_CHECK(!success.isNull());
BOOST_CHECK_EQUAL(success.get_bool(), true);
}
-#endif
}
#endif // ENABLE_EXTERNAL_SIGNER
diff --git a/src/test/timedata_tests.cpp b/src/test/timedata_tests.cpp
deleted file mode 100644
index 2814dbf4c0..0000000000
--- a/src/test/timedata_tests.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (c) 2011-2021 The Bitcoin Core developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-//
-
-#include <netaddress.h>
-#include <noui.h>
-#include <test/util/logging.h>
-#include <test/util/setup_common.h>
-#include <timedata.h>
-#include <util/string.h>
-#include <util/translation.h>
-#include <warnings.h>
-
-#include <string>
-
-#include <boost/test/unit_test.hpp>
-
-BOOST_FIXTURE_TEST_SUITE(timedata_tests, BasicTestingSetup)
-
-BOOST_AUTO_TEST_CASE(util_MedianFilter)
-{
- CMedianFilter<int> filter(5, 15);
-
- BOOST_CHECK_EQUAL(filter.median(), 15);
-
- filter.input(20); // [15 20]
- BOOST_CHECK_EQUAL(filter.median(), 17);
-
- filter.input(30); // [15 20 30]
- BOOST_CHECK_EQUAL(filter.median(), 20);
-
- filter.input(3); // [3 15 20 30]
- BOOST_CHECK_EQUAL(filter.median(), 17);
-
- filter.input(7); // [3 7 15 20 30]
- BOOST_CHECK_EQUAL(filter.median(), 15);
-
- filter.input(18); // [3 7 18 20 30]
- BOOST_CHECK_EQUAL(filter.median(), 18);
-
- filter.input(0); // [0 3 7 18 30]
- BOOST_CHECK_EQUAL(filter.median(), 7);
-}
-
-static void MultiAddTimeData(int n, int64_t offset)
-{
- static int cnt = 0;
- for (int i = 0; i < n; ++i) {
- CNetAddr addr;
- addr.SetInternal(ToString(++cnt));
- AddTimeData(addr, offset);
- }
-}
-
-
-BOOST_AUTO_TEST_CASE(addtimedata)
-{
- BOOST_CHECK_EQUAL(GetTimeOffset(), 0);
-
- //Part 1: Add large offsets to test a warning message that our clock may be wrong.
- MultiAddTimeData(3, DEFAULT_MAX_TIME_ADJUSTMENT + 1);
- // Filter size is 1 + 3 = 4: It is always initialized with a single element (offset 0)
-
- {
- ASSERT_DEBUG_LOG("Please check that your computer's date and time are correct!");
- MultiAddTimeData(1, DEFAULT_MAX_TIME_ADJUSTMENT + 1); //filter size 5
- }
-
- BOOST_CHECK(GetWarnings(true).original.find("clock is wrong") != std::string::npos);
-
- // nTimeOffset is not changed if the median of offsets exceeds DEFAULT_MAX_TIME_ADJUSTMENT
- BOOST_CHECK_EQUAL(GetTimeOffset(), 0);
-
- // Part 2: Test positive and negative medians by adding more offsets
- MultiAddTimeData(4, 100); // filter size 9
- BOOST_CHECK_EQUAL(GetTimeOffset(), 100);
- MultiAddTimeData(10, -100); //filter size 19
- BOOST_CHECK_EQUAL(GetTimeOffset(), -100);
-
- // Part 3: Test behaviour when filter has reached maximum number of offsets
- const int MAX_SAMPLES = 200;
- int nfill = (MAX_SAMPLES - 3 - 19) / 2; //89
- MultiAddTimeData(nfill, 100);
- MultiAddTimeData(nfill, -100); //filter size MAX_SAMPLES - 3
- BOOST_CHECK_EQUAL(GetTimeOffset(), -100);
-
- MultiAddTimeData(2, 100);
- //filter size MAX_SAMPLES -1, median is the initial 0 offset
- //since we added same number of positive/negative offsets
-
- BOOST_CHECK_EQUAL(GetTimeOffset(), 0);
-
- // After the number of offsets has reached MAX_SAMPLES -1 (=199), nTimeOffset will never change
- // because it is only updated when the number of elements in the filter becomes odd. It was decided
- // not to fix this because it prevents possible attacks. See the comment in AddTimeData() or issue #4521
- // for a more detailed explanation.
- MultiAddTimeData(2, 100); // filter median is 100 now, but nTimeOffset will not change
- // We want this test to end with nTimeOffset==0, otherwise subsequent tests of the suite will fail.
- BOOST_CHECK_EQUAL(GetTimeOffset(), 0);
-
- TestOnlyResetTimeData();
-}
-
-BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/timeoffsets_tests.cpp b/src/test/timeoffsets_tests.cpp
new file mode 100644
index 0000000000..008f1a9db9
--- /dev/null
+++ b/src/test/timeoffsets_tests.cpp
@@ -0,0 +1,69 @@
+// Copyright (c) 2024-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+//
+
+#include <node/timeoffsets.h>
+#include <test/util/setup_common.h>
+
+#include <boost/test/unit_test.hpp>
+
+#include <chrono>
+#include <vector>
+
+using namespace std::chrono_literals;
+
+static void AddMulti(TimeOffsets& offsets, const std::vector<std::chrono::seconds>& to_add)
+{
+ for (auto offset : to_add) {
+ offsets.Add(offset);
+ }
+}
+
+BOOST_FIXTURE_TEST_SUITE(timeoffsets_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(timeoffsets)
+{
+ TimeOffsets offsets{};
+ BOOST_CHECK(offsets.Median() == 0s);
+
+ AddMulti(offsets, {{0s, -1s, -2s, -3s}});
+ // median should be zero for < 5 offsets
+ BOOST_CHECK(offsets.Median() == 0s);
+
+ offsets.Add(-4s);
+ // we now have 5 offsets: [-4, -3, -2, -1, 0]
+ BOOST_CHECK(offsets.Median() == -2s);
+
+ AddMulti(offsets, {4, 5s});
+ // we now have 9 offsets: [-4, -3, -2, -1, 0, 5, 5, 5, 5]
+ BOOST_CHECK(offsets.Median() == 0s);
+
+ AddMulti(offsets, {41, 10s});
+ // the TimeOffsets is now at capacity with 50 offsets, oldest offsets is discarded for any additional offset
+ BOOST_CHECK(offsets.Median() == 10s);
+
+ AddMulti(offsets, {25, 15s});
+ // we now have 25 offsets of 10s followed by 25 offsets of 15s
+ BOOST_CHECK(offsets.Median() == 15s);
+}
+
+static bool IsWarningRaised(const std::vector<std::chrono::seconds>& check_offsets)
+{
+ TimeOffsets offsets{};
+ AddMulti(offsets, check_offsets);
+ return offsets.WarnIfOutOfSync();
+}
+
+
+BOOST_AUTO_TEST_CASE(timeoffsets_warning)
+{
+ BOOST_CHECK(IsWarningRaised({{-60min, -40min, -30min, 0min, 10min}}));
+ BOOST_CHECK(IsWarningRaised({5, 11min}));
+
+ BOOST_CHECK(!IsWarningRaised({4, 60min}));
+ BOOST_CHECK(!IsWarningRaised({100, 3min}));
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
index d1cb2531aa..e6cf64611e 100644
--- a/src/test/transaction_tests.cpp
+++ b/src/test/transaction_tests.cpp
@@ -786,7 +786,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
t.nVersion = 0;
CheckIsNotStandard(t, "version");
- t.nVersion = 3;
+ t.nVersion = TX_MAX_STANDARD_VERSION + 1;
CheckIsNotStandard(t, "version");
// Allowed nVersion
diff --git a/src/test/txindex_tests.cpp b/src/test/txindex_tests.cpp
index e2432a4718..5a32b02ad9 100644
--- a/src/test/txindex_tests.cpp
+++ b/src/test/txindex_tests.cpp
@@ -71,7 +71,7 @@ BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup)
// SyncWithValidationInterfaceQueue() call below is also needed to ensure
// TSAN always sees the test thread waiting for the notification thread, and
// avoid potential false positive reports.
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
// shutdown sequence (c.f. Shutdown() in init.cpp)
txindex.Stop();
diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp
index f6456526bb..55e0c5f285 100644
--- a/src/test/txpackage_tests.cpp
+++ b/src/test/txpackage_tests.cpp
@@ -8,9 +8,12 @@
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <script/script.h>
+#include <serialize.h>
+#include <streams.h>
#include <test/util/random.h>
#include <test/util/script.h>
#include <test/util/setup_common.h>
+#include <util/strencodings.h>
#include <test/util/txmempool.h>
#include <validation.h>
@@ -40,6 +43,93 @@ inline CTransactionRef create_placeholder_tx(size_t num_inputs, size_t num_outpu
return MakeTransactionRef(mtx);
}
+// Create a Wtxid from a hex string
+inline Wtxid WtxidFromString(std::string_view str)
+{
+ return Wtxid::FromUint256(uint256S(str.data()));
+}
+
+BOOST_FIXTURE_TEST_CASE(package_hash_tests, TestChain100Setup)
+{
+ // Random real segwit transaction
+ DataStream stream_1{
+ ParseHex("02000000000101964b8aa63509579ca6086e6012eeaa4c2f4dd1e283da29b67c8eea38b3c6fd220000000000fdffffff0294c618000000000017a9145afbbb42f4e83312666d0697f9e66259912ecde38768fa2c0000000000160014897388a0889390fd0e153a22bb2cf9d8f019faf50247304402200547406380719f84d68cf4e96cc3e4a1688309ef475b150be2b471c70ea562aa02206d255f5acc40fd95981874d77201d2eb07883657ce1c796513f32b6079545cdf0121023ae77335cefcb5ab4c1dc1fb0d2acfece184e593727d7d5906c78e564c7c11d125cf0c00"),
+ };
+ CTransaction tx_1(deserialize, TX_WITH_WITNESS, stream_1);
+ CTransactionRef ptx_1{MakeTransactionRef(tx_1)};
+
+ // Random real nonsegwit transaction
+ DataStream stream_2{
+ ParseHex("01000000010b26e9b7735eb6aabdf358bab62f9816a21ba9ebdb719d5299e88607d722c190000000008b4830450220070aca44506c5cef3a16ed519d7c3c39f8aab192c4e1c90d065f37b8a4af6141022100a8e160b856c2d43d27d8fba71e5aef6405b8643ac4cb7cb3c462aced7f14711a0141046d11fee51b0e60666d5049a9101a72741df480b96ee26488a4d3466b95c9a40ac5eeef87e10a5cd336c19a84565f80fa6c547957b7700ff4dfbdefe76036c339ffffffff021bff3d11000000001976a91404943fdd508053c75000106d3bc6e2754dbcff1988ac2f15de00000000001976a914a266436d2965547608b9e15d9032a7b9d64fa43188ac00000000"),
+ };
+ CTransaction tx_2(deserialize, TX_WITH_WITNESS, stream_2);
+ CTransactionRef ptx_2{MakeTransactionRef(tx_2)};
+
+ // Random real segwit transaction
+ DataStream stream_3{
+ ParseHex("0200000000010177862801f77c2c068a70372b4c435ef8dd621291c36a64eb4dd491f02218f5324600000000fdffffff014a0100000000000022512035ea312034cfac01e956a269f3bf147f569c2fbb00180677421262da042290d803402be713325ff285e66b0380f53f2fae0d0fb4e16f378a440fed51ce835061437566729d4883bc917632f3cff474d6384bc8b989961a1d730d4a87ed38ad28bd337b20f1d658c6c138b1c312e072b4446f50f01ae0da03a42e6274f8788aae53416a7fac0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800357b2270223a226272632d3230222c226f70223a226d696e74222c227469636b223a224342414c222c22616d74223a2236393639227d6821c1f1d658c6c138b1c312e072b4446f50f01ae0da03a42e6274f8788aae53416a7f00000000"),
+ };
+ CTransaction tx_3(deserialize, TX_WITH_WITNESS, stream_3);
+ CTransactionRef ptx_3{MakeTransactionRef(tx_3)};
+
+ // It's easy to see that wtxids are sorted in lexicographical order:
+ Wtxid wtxid_1{WtxidFromString("0x85cd1a31eb38f74ed5742ec9cb546712ab5aaf747de28a9168b53e846cbda17f")};
+ Wtxid wtxid_2{WtxidFromString("0xb4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b")};
+ Wtxid wtxid_3{WtxidFromString("0xe065bac15f62bb4e761d761db928ddee65a47296b2b776785abb912cdec474e3")};
+ BOOST_CHECK_EQUAL(tx_1.GetWitnessHash(), wtxid_1);
+ BOOST_CHECK_EQUAL(tx_2.GetWitnessHash(), wtxid_2);
+ BOOST_CHECK_EQUAL(tx_3.GetWitnessHash(), wtxid_3);
+
+ BOOST_CHECK(wtxid_1.GetHex() < wtxid_2.GetHex());
+ BOOST_CHECK(wtxid_2.GetHex() < wtxid_3.GetHex());
+
+ // The txids are not (we want to test that sorting and hashing use wtxid, not txid):
+ Txid txid_1{TxidFromString("0xbd0f71c1d5e50589063e134fad22053cdae5ab2320db5bf5e540198b0b5a4e69")};
+ Txid txid_2{TxidFromString("0xb4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b")};
+ Txid txid_3{TxidFromString("0xee707be5201160e32c4fc715bec227d1aeea5940fb4295605e7373edce3b1a93")};
+ BOOST_CHECK_EQUAL(tx_1.GetHash(), txid_1);
+ BOOST_CHECK_EQUAL(tx_2.GetHash(), txid_2);
+ BOOST_CHECK_EQUAL(tx_3.GetHash(), txid_3);
+
+ BOOST_CHECK(txid_2.GetHex() < txid_1.GetHex());
+
+ BOOST_CHECK(txid_1.ToUint256() != wtxid_1.ToUint256());
+ BOOST_CHECK(txid_2.ToUint256() == wtxid_2.ToUint256());
+ BOOST_CHECK(txid_3.ToUint256() != wtxid_3.ToUint256());
+
+ // We are testing that both functions compare using GetHex() and not uint256.
+ // (in this pair of wtxids, hex string order != uint256 order)
+ BOOST_CHECK(wtxid_2 < wtxid_1);
+ // (in this pair of wtxids, hex string order == uint256 order)
+ BOOST_CHECK(wtxid_2 < wtxid_3);
+
+ // All permutations of the package containing ptx_1, ptx_2, ptx_3 have the same package hash
+ std::vector<CTransactionRef> package_123{ptx_1, ptx_2, ptx_3};
+ std::vector<CTransactionRef> package_132{ptx_1, ptx_3, ptx_2};
+ std::vector<CTransactionRef> package_231{ptx_2, ptx_3, ptx_1};
+ std::vector<CTransactionRef> package_213{ptx_2, ptx_1, ptx_3};
+ std::vector<CTransactionRef> package_312{ptx_3, ptx_1, ptx_2};
+ std::vector<CTransactionRef> package_321{ptx_3, ptx_2, ptx_1};
+
+ uint256 calculated_hash_123 = (HashWriter() << wtxid_1 << wtxid_2 << wtxid_3).GetSHA256();
+
+ uint256 hash_if_by_txid = (HashWriter() << wtxid_2 << wtxid_1 << wtxid_3).GetSHA256();
+ BOOST_CHECK(hash_if_by_txid != calculated_hash_123);
+
+ uint256 hash_if_use_txid = (HashWriter() << txid_2 << txid_1 << txid_3).GetSHA256();
+ BOOST_CHECK(hash_if_use_txid != calculated_hash_123);
+
+ uint256 hash_if_use_int_order = (HashWriter() << wtxid_2 << wtxid_1 << wtxid_3).GetSHA256();
+ BOOST_CHECK(hash_if_use_int_order != calculated_hash_123);
+
+ BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_123));
+ BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_132));
+ BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_231));
+ BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_213));
+ BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_312));
+ BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_321));
+}
+
BOOST_FIXTURE_TEST_CASE(package_sanitization_tests, TestChain100Setup)
{
// Packages can't have more than 25 transactions.
@@ -132,7 +222,7 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
/*output_amount=*/CAmount(48 * COIN), /*submit=*/false);
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
Package package_parent_child{tx_parent, tx_child};
- const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_parent_child, /*test_accept=*/true);
+ const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_parent_child, /*test_accept=*/true, /*client_maxfeerate=*/{});
if (auto err_parent_child{CheckPackageMempoolAcceptResult(package_parent_child, result_parent_child, /*expect_valid=*/true, nullptr)}) {
BOOST_ERROR(err_parent_child.value());
} else {
@@ -151,7 +241,7 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
CTransactionRef giant_ptx = create_placeholder_tx(999, 999);
BOOST_CHECK(GetVirtualTransactionSize(*giant_ptx) > DEFAULT_ANCESTOR_SIZE_LIMIT_KVB * 1000);
Package package_single_giant{giant_ptx};
- auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_single_giant, /*test_accept=*/true);
+ auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_single_giant, /*test_accept=*/true, /*client_maxfeerate=*/{});
if (auto err_single_large{CheckPackageMempoolAcceptResult(package_single_giant, result_single_large, /*expect_valid=*/false, nullptr)}) {
BOOST_ERROR(err_single_large.value());
} else {
@@ -190,6 +280,9 @@ BOOST_FIXTURE_TEST_CASE(noncontextual_package_tests, TestChain100Setup)
BOOST_CHECK_EQUAL(state.GetRejectReason(), "package-not-sorted");
BOOST_CHECK(IsChildWithParents({tx_parent, tx_child}));
BOOST_CHECK(IsChildWithParentsTree({tx_parent, tx_child}));
+ BOOST_CHECK(GetPackageHash({tx_parent}) != GetPackageHash({tx_child}));
+ BOOST_CHECK(GetPackageHash({tx_child, tx_child}) != GetPackageHash({tx_child}));
+ BOOST_CHECK(GetPackageHash({tx_child, tx_parent}) != GetPackageHash({tx_child, tx_child}));
}
// 24 Parents and 1 Child
@@ -275,7 +368,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
package_unrelated.emplace_back(MakeTransactionRef(mtx));
}
auto result_unrelated_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_unrelated, /*test_accept=*/false);
+ package_unrelated, /*test_accept=*/false, /*client_maxfeerate=*/{});
// We don't expect m_tx_results for each transaction when basic sanity checks haven't passed.
BOOST_CHECK(result_unrelated_submit.m_state.IsInvalid());
BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
@@ -315,7 +408,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
// 3 Generations is not allowed.
{
auto result_3gen_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_3gen, /*test_accept=*/false);
+ package_3gen, /*test_accept=*/false, /*client_maxfeerate=*/{});
BOOST_CHECK(result_3gen_submit.m_state.IsInvalid());
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
@@ -332,7 +425,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
CTransactionRef tx_parent_invalid = MakeTransactionRef(mtx_parent_invalid);
Package package_invalid_parent{tx_parent_invalid, tx_child};
auto result_quit_early = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_invalid_parent, /*test_accept=*/ false);
+ package_invalid_parent, /*test_accept=*/ false, /*client_maxfeerate=*/{});
if (auto err_parent_invalid{CheckPackageMempoolAcceptResult(package_invalid_parent, result_quit_early, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_parent_invalid.value());
} else {
@@ -353,7 +446,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
package_missing_parent.push_back(MakeTransactionRef(mtx_child));
{
const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_missing_parent, /*test_accept=*/false);
+ package_missing_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
BOOST_CHECK(result_missing_parent.m_state.IsInvalid());
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "package-not-child-with-unconfirmed-parents");
@@ -363,7 +456,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
// Submit package with parent + child.
{
const auto submit_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_parent_child, /*test_accept=*/false);
+ package_parent_child, /*test_accept=*/false, /*client_maxfeerate=*/{});
expected_pool_size += 2;
BOOST_CHECK_MESSAGE(submit_parent_child.m_state.IsValid(),
"Package validation unexpectedly failed: " << submit_parent_child.m_state.GetRejectReason());
@@ -385,7 +478,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
// Already-in-mempool transactions should be detected and de-duplicated.
{
const auto submit_deduped = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_parent_child, /*test_accept=*/false);
+ package_parent_child, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_deduped{CheckPackageMempoolAcceptResult(package_parent_child, submit_deduped, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_deduped.value());
} else {
@@ -450,13 +543,15 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
BOOST_CHECK_EQUAL(ptx_child1->GetHash(), ptx_child2->GetHash());
// child1 and child2 have different wtxids
BOOST_CHECK(ptx_child1->GetWitnessHash() != ptx_child2->GetWitnessHash());
+ // Check that they have different package hashes
+ BOOST_CHECK(GetPackageHash({ptx_parent, ptx_child1}) != GetPackageHash({ptx_parent, ptx_child2}));
// Try submitting Package1{parent, child1} and Package2{parent, child2} where the children are
// same-txid-different-witness.
{
Package package_parent_child1{ptx_parent, ptx_child1};
const auto submit_witness1 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_parent_child1, /*test_accept=*/false);
+ package_parent_child1, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_witness1{CheckPackageMempoolAcceptResult(package_parent_child1, submit_witness1, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_witness1.value());
}
@@ -464,7 +559,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
// Child2 would have been validated individually.
Package package_parent_child2{ptx_parent, ptx_child2};
const auto submit_witness2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_parent_child2, /*test_accept=*/false);
+ package_parent_child2, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_witness2{CheckPackageMempoolAcceptResult(package_parent_child2, submit_witness2, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_witness2.value());
} else {
@@ -478,7 +573,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
// Deduplication should work when wtxid != txid. Submit package with the already-in-mempool
// transactions again, which should not fail.
const auto submit_segwit_dedup = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_parent_child1, /*test_accept=*/false);
+ package_parent_child1, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_segwit_dedup{CheckPackageMempoolAcceptResult(package_parent_child1, submit_segwit_dedup, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_segwit_dedup.value());
} else {
@@ -503,12 +598,13 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
/*output_destination=*/grandchild_locking_script,
/*output_amount=*/CAmount(47 * COIN), /*submit=*/false);
CTransactionRef ptx_grandchild = MakeTransactionRef(mtx_grandchild);
-
+ // Check that they have different package hashes
+ BOOST_CHECK(GetPackageHash({ptx_child1, ptx_grandchild}) != GetPackageHash({ptx_child2, ptx_grandchild}));
// We already submitted child1 above.
{
Package package_child2_grandchild{ptx_child2, ptx_grandchild};
const auto submit_spend_ignored = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_child2_grandchild, /*test_accept=*/false);
+ package_child2_grandchild, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_spend_ignored{CheckPackageMempoolAcceptResult(package_child2_grandchild, submit_spend_ignored, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_spend_ignored.value());
} else {
@@ -583,7 +679,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
CTransactionRef ptx_parent3 = MakeTransactionRef(mtx_parent3);
package_mixed.push_back(ptx_parent3);
BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*ptx_parent3)) > low_fee_amt);
- BOOST_CHECK(m_node.mempool->m_min_relay_feerate.GetFee(GetVirtualTransactionSize(*ptx_parent3)) <= low_fee_amt);
+ BOOST_CHECK(m_node.mempool->m_opts.min_relay_feerate.GetFee(GetVirtualTransactionSize(*ptx_parent3)) <= low_fee_amt);
// child spends parent1, parent2, and parent3
CKey mixed_grandchild_key = GenerateRandomKey();
@@ -606,7 +702,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
// parent3 should be accepted
// child should be accepted
{
- const auto mixed_result = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_mixed, false);
+ const auto mixed_result = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_mixed, false, /*client_maxfeerate=*/{});
if (auto err_mixed{CheckPackageMempoolAcceptResult(package_mixed, mixed_result, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_mixed.value());
} else {
@@ -670,7 +766,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
{
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_cpfp, /*test_accept=*/ false);
+ package_cpfp, /*test_accept=*/ false, /*client_maxfeerate=*/{});
if (auto err_cpfp_deprio{CheckPackageMempoolAcceptResult(package_cpfp, submit_cpfp_deprio, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_cpfp_deprio.value());
} else {
@@ -692,7 +788,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
{
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const auto submit_cpfp = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_cpfp, /*test_accept=*/ false);
+ package_cpfp, /*test_accept=*/ false, /*client_maxfeerate=*/{});
if (auto err_cpfp{CheckPackageMempoolAcceptResult(package_cpfp, submit_cpfp, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_cpfp.value());
} else {
@@ -729,7 +825,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
CTransactionRef tx_parent_cheap = MakeTransactionRef(mtx_parent_cheap);
package_still_too_low.push_back(tx_parent_cheap);
BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) > parent_fee);
- BOOST_CHECK(m_node.mempool->m_min_relay_feerate.GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) <= parent_fee);
+ BOOST_CHECK(m_node.mempool->m_opts.min_relay_feerate.GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) <= parent_fee);
auto mtx_child_cheap = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_cheap, /*input_vout=*/0,
/*input_height=*/101, /*input_signing_key=*/child_key,
@@ -744,7 +840,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
// Cheap package should fail for being too low fee.
{
const auto submit_package_too_low = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_still_too_low, /*test_accept=*/false);
+ package_still_too_low, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_package_too_low{CheckPackageMempoolAcceptResult(package_still_too_low, submit_package_too_low, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_package_too_low.value());
} else {
@@ -770,7 +866,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
// Now that the child's fees have "increased" by 1 BTC, the cheap package should succeed.
{
const auto submit_prioritised_package = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_still_too_low, /*test_accept=*/false);
+ package_still_too_low, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_prioritised{CheckPackageMempoolAcceptResult(package_still_too_low, submit_prioritised_package, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_prioritised.value());
} else {
@@ -818,7 +914,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
{
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const auto submit_rich_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_rich_parent, /*test_accept=*/false);
+ package_rich_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_rich_parent{CheckPackageMempoolAcceptResult(package_rich_parent, submit_rich_parent, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_rich_parent.value());
} else {
diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp
index ecf0889711..95583b53bf 100644
--- a/src/test/txvalidation_tests.cpp
+++ b/src/test/txvalidation_tests.cpp
@@ -4,11 +4,14 @@
#include <consensus/validation.h>
#include <key_io.h>
+#include <policy/v3_policy.h>
#include <policy/packages.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
+#include <random.h>
#include <script/script.h>
#include <test/util/setup_common.h>
+#include <test/util/txmempool.h>
#include <validation.h>
#include <boost/test/unit_test.hpp>
@@ -48,4 +51,340 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
BOOST_CHECK_EQUAL(result.m_state.GetRejectReason(), "coinbase");
BOOST_CHECK(result.m_state.GetResult() == TxValidationResult::TX_CONSENSUS);
}
+
+// Generate a number of random, nonexistent outpoints.
+static inline std::vector<COutPoint> random_outpoints(size_t num_outpoints) {
+ std::vector<COutPoint> outpoints;
+ for (size_t i{0}; i < num_outpoints; ++i) {
+ outpoints.emplace_back(Txid::FromUint256(GetRandHash()), 0);
+ }
+ return outpoints;
+}
+
+static inline std::vector<CPubKey> random_keys(size_t num_keys) {
+ std::vector<CPubKey> keys;
+ keys.reserve(num_keys);
+ for (size_t i{0}; i < num_keys; ++i) {
+ CKey key;
+ key.MakeNewKey(true);
+ keys.emplace_back(key.GetPubKey());
+ }
+ return keys;
+}
+
+// Creates a placeholder tx (not valid) with 25 outputs. Specify the nVersion and the inputs.
+static inline CTransactionRef make_tx(const std::vector<COutPoint>& inputs, int32_t version)
+{
+ CMutableTransaction mtx = CMutableTransaction{};
+ mtx.nVersion = version;
+ mtx.vin.resize(inputs.size());
+ mtx.vout.resize(25);
+ for (size_t i{0}; i < inputs.size(); ++i) {
+ mtx.vin[i].prevout = inputs[i];
+ }
+ for (auto i{0}; i < 25; ++i) {
+ mtx.vout[i].scriptPubKey = CScript() << OP_TRUE;
+ mtx.vout[i].nValue = 10000;
+ }
+ return MakeTransactionRef(mtx);
+}
+
+BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
+{
+ // Test V3 policy helper functions
+ CTxMemPool& pool = *Assert(m_node.mempool);
+ LOCK2(cs_main, pool.cs);
+ TestMemPoolEntryHelper entry;
+ std::set<Txid> empty_conflicts_set;
+ CTxMemPool::setEntries empty_ancestors;
+
+ auto mempool_tx_v3 = make_tx(random_outpoints(1), /*version=*/3);
+ pool.addUnchecked(entry.FromTx(mempool_tx_v3));
+ auto mempool_tx_v2 = make_tx(random_outpoints(1), /*version=*/2);
+ pool.addUnchecked(entry.FromTx(mempool_tx_v2));
+ // Default values.
+ CTxMemPool::Limits m_limits{};
+
+ // Cannot spend from an unconfirmed v3 transaction unless this tx is also v3.
+ {
+ // mempool_tx_v3
+ // ^
+ // tx_v2_from_v3
+ auto tx_v2_from_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/2);
+ auto ancestors_v2_from_v3{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v3), m_limits)};
+ const auto expected_error_str{strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
+ tx_v2_from_v3->GetHash().ToString(), tx_v2_from_v3->GetWitnessHash().ToString(),
+ mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
+ auto result_v2_from_v3{SingleV3Checks(tx_v2_from_v3, *ancestors_v2_from_v3, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v3))};
+ BOOST_CHECK_EQUAL(result_v2_from_v3->first, expected_error_str);
+ BOOST_CHECK_EQUAL(result_v2_from_v3->second, nullptr);
+
+ Package package_v3_v2{mempool_tx_v3, tx_v2_from_v3};
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v2_from_v3, GetVirtualTransactionSize(*tx_v2_from_v3), package_v3_v2, empty_ancestors), expected_error_str);
+ CTxMemPool::setEntries entries_mempool_v3{pool.GetIter(mempool_tx_v3->GetHash().ToUint256()).value()};
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v2_from_v3, GetVirtualTransactionSize(*tx_v2_from_v3), {tx_v2_from_v3}, entries_mempool_v3), expected_error_str);
+
+ // mempool_tx_v3 mempool_tx_v2
+ // ^ ^
+ // tx_v2_from_v2_and_v3
+ auto tx_v2_from_v2_and_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}, COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/2);
+ auto ancestors_v2_from_both{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v2_and_v3), m_limits)};
+ const auto expected_error_str_2{strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
+ tx_v2_from_v2_and_v3->GetHash().ToString(), tx_v2_from_v2_and_v3->GetWitnessHash().ToString(),
+ mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
+ auto result_v2_from_both{SingleV3Checks(tx_v2_from_v2_and_v3, *ancestors_v2_from_both, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v2_and_v3))};
+ BOOST_CHECK_EQUAL(result_v2_from_both->first, expected_error_str_2);
+ BOOST_CHECK_EQUAL(result_v2_from_both->second, nullptr);
+
+ Package package_v3_v2_v2{mempool_tx_v3, mempool_tx_v2, tx_v2_from_v2_and_v3};
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v2_from_v2_and_v3, GetVirtualTransactionSize(*tx_v2_from_v2_and_v3), package_v3_v2_v2, empty_ancestors), expected_error_str_2);
+ }
+
+ // V3 cannot spend from an unconfirmed non-v3 transaction.
+ {
+ // mempool_tx_v2
+ // ^
+ // tx_v3_from_v2
+ auto tx_v3_from_v2 = make_tx({COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/3);
+ auto ancestors_v3_from_v2{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v2), m_limits)};
+ const auto expected_error_str{strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
+ tx_v3_from_v2->GetHash().ToString(), tx_v3_from_v2->GetWitnessHash().ToString(),
+ mempool_tx_v2->GetHash().ToString(), mempool_tx_v2->GetWitnessHash().ToString())};
+ auto result_v3_from_v2{SingleV3Checks(tx_v3_from_v2, *ancestors_v3_from_v2, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v2))};
+ BOOST_CHECK_EQUAL(result_v3_from_v2->first, expected_error_str);
+ BOOST_CHECK_EQUAL(result_v3_from_v2->second, nullptr);
+
+ Package package_v2_v3{mempool_tx_v2, tx_v3_from_v2};
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_from_v2, GetVirtualTransactionSize(*tx_v3_from_v2), package_v2_v3, empty_ancestors), expected_error_str);
+ CTxMemPool::setEntries entries_mempool_v2{pool.GetIter(mempool_tx_v2->GetHash().ToUint256()).value()};
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_from_v2, GetVirtualTransactionSize(*tx_v3_from_v2), {tx_v3_from_v2}, entries_mempool_v2), expected_error_str);
+
+ // mempool_tx_v3 mempool_tx_v2
+ // ^ ^
+ // tx_v3_from_v2_and_v3
+ auto tx_v3_from_v2_and_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}, COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/3);
+ auto ancestors_v3_from_both{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v2_and_v3), m_limits)};
+ const auto expected_error_str_2{strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
+ tx_v3_from_v2_and_v3->GetHash().ToString(), tx_v3_from_v2_and_v3->GetWitnessHash().ToString(),
+ mempool_tx_v2->GetHash().ToString(), mempool_tx_v2->GetWitnessHash().ToString())};
+ auto result_v3_from_both{SingleV3Checks(tx_v3_from_v2_and_v3, *ancestors_v3_from_both, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v2_and_v3))};
+ BOOST_CHECK_EQUAL(result_v3_from_both->first, expected_error_str_2);
+ BOOST_CHECK_EQUAL(result_v3_from_both->second, nullptr);
+
+ // tx_v3_from_v2_and_v3 also violates V3_ANCESTOR_LIMIT.
+ const auto expected_error_str_3{strprintf("tx %s (wtxid=%s) would have too many ancestors",
+ tx_v3_from_v2_and_v3->GetHash().ToString(), tx_v3_from_v2_and_v3->GetWitnessHash().ToString())};
+ Package package_v3_v2_v3{mempool_tx_v3, mempool_tx_v2, tx_v3_from_v2_and_v3};
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_from_v2_and_v3, GetVirtualTransactionSize(*tx_v3_from_v2_and_v3), package_v3_v2_v3, empty_ancestors), expected_error_str_3);
+ }
+ // V3 from V3 is ok, and non-V3 from non-V3 is ok.
+ {
+ // mempool_tx_v3
+ // ^
+ // tx_v3_from_v3
+ auto tx_v3_from_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/3);
+ auto ancestors_v3{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v3), m_limits)};
+ BOOST_CHECK(SingleV3Checks(tx_v3_from_v3, *ancestors_v3, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v3))
+ == std::nullopt);
+
+ Package package_v3_v3{mempool_tx_v3, tx_v3_from_v3};
+ BOOST_CHECK(PackageV3Checks(tx_v3_from_v3, GetVirtualTransactionSize(*tx_v3_from_v3), package_v3_v3, empty_ancestors) == std::nullopt);
+
+ // mempool_tx_v2
+ // ^
+ // tx_v2_from_v2
+ auto tx_v2_from_v2 = make_tx({COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/2);
+ auto ancestors_v2{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v2), m_limits)};
+ BOOST_CHECK(SingleV3Checks(tx_v2_from_v2, *ancestors_v2, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v2))
+ == std::nullopt);
+
+ Package package_v2_v2{mempool_tx_v2, tx_v2_from_v2};
+ BOOST_CHECK(PackageV3Checks(tx_v2_from_v2, GetVirtualTransactionSize(*tx_v2_from_v2), package_v2_v2, empty_ancestors) == std::nullopt);
+ }
+
+ // Tx spending v3 cannot have too many mempool ancestors
+ // Configuration where the tx has multiple direct parents.
+ {
+ Package package_multi_parents;
+ std::vector<COutPoint> mempool_outpoints;
+ mempool_outpoints.emplace_back(mempool_tx_v3->GetHash(), 0);
+ package_multi_parents.emplace_back(mempool_tx_v3);
+ for (size_t i{0}; i < 2; ++i) {
+ auto mempool_tx = make_tx(random_outpoints(i + 1), /*version=*/3);
+ pool.addUnchecked(entry.FromTx(mempool_tx));
+ mempool_outpoints.emplace_back(mempool_tx->GetHash(), 0);
+ package_multi_parents.emplace_back(mempool_tx);
+ }
+ auto tx_v3_multi_parent = make_tx(mempool_outpoints, /*version=*/3);
+ package_multi_parents.emplace_back(tx_v3_multi_parent);
+ auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_multi_parent), m_limits)};
+ BOOST_CHECK_EQUAL(ancestors->size(), 3);
+ const auto expected_error_str{strprintf("tx %s (wtxid=%s) would have too many ancestors",
+ tx_v3_multi_parent->GetHash().ToString(), tx_v3_multi_parent->GetWitnessHash().ToString())};
+ auto result{SingleV3Checks(tx_v3_multi_parent, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_multi_parent))};
+ BOOST_CHECK_EQUAL(result->first, expected_error_str);
+ BOOST_CHECK_EQUAL(result->second, nullptr);
+
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_multi_parent, GetVirtualTransactionSize(*tx_v3_multi_parent), package_multi_parents, empty_ancestors),
+ expected_error_str);
+ }
+
+ // Configuration where the tx is in a multi-generation chain.
+ {
+ Package package_multi_gen;
+ CTransactionRef middle_tx;
+ auto last_outpoint{random_outpoints(1)[0]};
+ for (size_t i{0}; i < 2; ++i) {
+ auto mempool_tx = make_tx({last_outpoint}, /*version=*/3);
+ pool.addUnchecked(entry.FromTx(mempool_tx));
+ last_outpoint = COutPoint{mempool_tx->GetHash(), 0};
+ package_multi_gen.emplace_back(mempool_tx);
+ if (i == 1) middle_tx = mempool_tx;
+ }
+ auto tx_v3_multi_gen = make_tx({last_outpoint}, /*version=*/3);
+ package_multi_gen.emplace_back(tx_v3_multi_gen);
+ auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_multi_gen), m_limits)};
+ const auto expected_error_str{strprintf("tx %s (wtxid=%s) would have too many ancestors",
+ tx_v3_multi_gen->GetHash().ToString(), tx_v3_multi_gen->GetWitnessHash().ToString())};
+ auto result{SingleV3Checks(tx_v3_multi_gen, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_multi_gen))};
+ BOOST_CHECK_EQUAL(result->first, expected_error_str);
+ BOOST_CHECK_EQUAL(result->second, nullptr);
+
+ // Middle tx is what triggers a failure for the grandchild:
+ BOOST_CHECK_EQUAL(*PackageV3Checks(middle_tx, GetVirtualTransactionSize(*middle_tx), package_multi_gen, empty_ancestors), expected_error_str);
+ BOOST_CHECK(PackageV3Checks(tx_v3_multi_gen, GetVirtualTransactionSize(*tx_v3_multi_gen), package_multi_gen, empty_ancestors) == std::nullopt);
+ }
+
+ // Tx spending v3 cannot be too large in virtual size.
+ auto many_inputs{random_outpoints(100)};
+ many_inputs.emplace_back(mempool_tx_v3->GetHash(), 0);
+ {
+ auto tx_v3_child_big = make_tx(many_inputs, /*version=*/3);
+ const auto vsize{GetVirtualTransactionSize(*tx_v3_child_big)};
+ auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child_big), m_limits)};
+ const auto expected_error_str{strprintf("v3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
+ tx_v3_child_big->GetHash().ToString(), tx_v3_child_big->GetWitnessHash().ToString(), vsize, V3_CHILD_MAX_VSIZE)};
+ auto result{SingleV3Checks(tx_v3_child_big, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child_big))};
+ BOOST_CHECK_EQUAL(result->first, expected_error_str);
+ BOOST_CHECK_EQUAL(result->second, nullptr);
+
+ Package package_child_big{mempool_tx_v3, tx_v3_child_big};
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_child_big, GetVirtualTransactionSize(*tx_v3_child_big), package_child_big, empty_ancestors),
+ expected_error_str);
+ }
+
+ // Tx spending v3 cannot have too many sigops.
+ // This child has 10 P2WSH multisig inputs.
+ auto multisig_outpoints{random_outpoints(10)};
+ multisig_outpoints.emplace_back(mempool_tx_v3->GetHash(), 0);
+ auto keys{random_keys(2)};
+ CScript script_multisig;
+ script_multisig << OP_1;
+ for (const auto& key : keys) {
+ script_multisig << ToByteVector(key);
+ }
+ script_multisig << OP_2 << OP_CHECKMULTISIG;
+ {
+ CMutableTransaction mtx_many_sigops = CMutableTransaction{};
+ mtx_many_sigops.nVersion = 3;
+ for (const auto& outpoint : multisig_outpoints) {
+ mtx_many_sigops.vin.emplace_back(outpoint);
+ mtx_many_sigops.vin.back().scriptWitness.stack.emplace_back(script_multisig.begin(), script_multisig.end());
+ }
+ mtx_many_sigops.vout.resize(1);
+ mtx_many_sigops.vout.back().scriptPubKey = CScript() << OP_TRUE;
+ mtx_many_sigops.vout.back().nValue = 10000;
+ auto tx_many_sigops{MakeTransactionRef(mtx_many_sigops)};
+
+ auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_many_sigops), m_limits)};
+ // legacy uses fAccurate = false, and the maximum number of multisig keys is used
+ const int64_t total_sigops{static_cast<int64_t>(tx_many_sigops->vin.size()) * static_cast<int64_t>(script_multisig.GetSigOpCount(/*fAccurate=*/false))};
+ BOOST_CHECK_EQUAL(total_sigops, tx_many_sigops->vin.size() * MAX_PUBKEYS_PER_MULTISIG);
+ const int64_t bip141_vsize{GetVirtualTransactionSize(*tx_many_sigops)};
+ // Weight limit is not reached...
+ BOOST_CHECK(SingleV3Checks(tx_many_sigops, *ancestors, empty_conflicts_set, bip141_vsize) == std::nullopt);
+ // ...but sigop limit is.
+ const auto expected_error_str{strprintf("v3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
+ tx_many_sigops->GetHash().ToString(), tx_many_sigops->GetWitnessHash().ToString(),
+ total_sigops * DEFAULT_BYTES_PER_SIGOP / WITNESS_SCALE_FACTOR, V3_CHILD_MAX_VSIZE)};
+ auto result{SingleV3Checks(tx_many_sigops, *ancestors, empty_conflicts_set,
+ GetVirtualTransactionSize(*tx_many_sigops, /*nSigOpCost=*/total_sigops, /*bytes_per_sigop=*/ DEFAULT_BYTES_PER_SIGOP))};
+ BOOST_CHECK_EQUAL(result->first, expected_error_str);
+ BOOST_CHECK_EQUAL(result->second, nullptr);
+
+ Package package_child_sigops{mempool_tx_v3, tx_many_sigops};
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_many_sigops, total_sigops * DEFAULT_BYTES_PER_SIGOP / WITNESS_SCALE_FACTOR, package_child_sigops, empty_ancestors),
+ expected_error_str);
+ }
+
+ // Parent + child with v3 in the mempool. Child is allowed as long as it is under V3_CHILD_MAX_VSIZE.
+ auto tx_mempool_v3_child = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/3);
+ {
+ BOOST_CHECK(GetTransactionWeight(*tx_mempool_v3_child) <= V3_CHILD_MAX_VSIZE * WITNESS_SCALE_FACTOR);
+ auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_mempool_v3_child), m_limits)};
+ BOOST_CHECK(SingleV3Checks(tx_mempool_v3_child, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_mempool_v3_child)) == std::nullopt);
+ pool.addUnchecked(entry.FromTx(tx_mempool_v3_child));
+
+ Package package_v3_1p1c{mempool_tx_v3, tx_mempool_v3_child};
+ BOOST_CHECK(PackageV3Checks(tx_mempool_v3_child, GetVirtualTransactionSize(*tx_mempool_v3_child), package_v3_1p1c, empty_ancestors) == std::nullopt);
+ }
+
+ // A v3 transaction cannot have more than 1 descendant. Sibling is returned when exactly 1 exists.
+ {
+ auto tx_v3_child2 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 1}}, /*version=*/3);
+
+ // Configuration where parent already has 1 other child in mempool
+ auto ancestors_1sibling{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child2), m_limits)};
+ const auto expected_error_str{strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
+ mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
+ auto result_with_sibling_eviction{SingleV3Checks(tx_v3_child2, *ancestors_1sibling, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child2))};
+ BOOST_CHECK_EQUAL(result_with_sibling_eviction->first, expected_error_str);
+ // The other mempool child is returned to allow for sibling eviction.
+ BOOST_CHECK_EQUAL(result_with_sibling_eviction->second, tx_mempool_v3_child);
+
+ // If directly replacing the child, make sure there is no double-counting.
+ BOOST_CHECK(SingleV3Checks(tx_v3_child2, *ancestors_1sibling, {tx_mempool_v3_child->GetHash()}, GetVirtualTransactionSize(*tx_v3_child2))
+ == std::nullopt);
+
+ Package package_v3_1p2c{mempool_tx_v3, tx_mempool_v3_child, tx_v3_child2};
+ BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_child2, GetVirtualTransactionSize(*tx_v3_child2), package_v3_1p2c, empty_ancestors),
+ expected_error_str);
+
+ // Configuration where parent already has 2 other children in mempool (no sibling eviction allowed). This may happen as the result of a reorg.
+ pool.addUnchecked(entry.FromTx(tx_v3_child2));
+ auto tx_v3_child3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 24}}, /*version=*/3);
+ auto entry_mempool_parent = pool.GetIter(mempool_tx_v3->GetHash().ToUint256()).value();
+ BOOST_CHECK_EQUAL(entry_mempool_parent->GetCountWithDescendants(), 3);
+ auto ancestors_2siblings{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child3), m_limits)};
+
+ auto result_2children{SingleV3Checks(tx_v3_child3, *ancestors_2siblings, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child3))};
+ BOOST_CHECK_EQUAL(result_2children->first, expected_error_str);
+ // The other mempool child is not returned because sibling eviction is not allowed.
+ BOOST_CHECK_EQUAL(result_2children->second, nullptr);
+ }
+
+ // Sibling eviction: parent already has 1 other child, which also has its own child (no sibling eviction allowed). This may happen as the result of a reorg.
+ {
+ auto tx_mempool_grandparent = make_tx(random_outpoints(1), /*version=*/3);
+ auto tx_mempool_sibling = make_tx({COutPoint{tx_mempool_grandparent->GetHash(), 0}}, /*version=*/3);
+ auto tx_mempool_nibling = make_tx({COutPoint{tx_mempool_sibling->GetHash(), 0}}, /*version=*/3);
+ auto tx_to_submit = make_tx({COutPoint{tx_mempool_grandparent->GetHash(), 1}}, /*version=*/3);
+
+ pool.addUnchecked(entry.FromTx(tx_mempool_grandparent));
+ pool.addUnchecked(entry.FromTx(tx_mempool_sibling));
+ pool.addUnchecked(entry.FromTx(tx_mempool_nibling));
+
+ auto ancestors_3gen{pool.CalculateMemPoolAncestors(entry.FromTx(tx_to_submit), m_limits)};
+ const auto expected_error_str{strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
+ tx_mempool_grandparent->GetHash().ToString(), tx_mempool_grandparent->GetWitnessHash().ToString())};
+ auto result_3gen{SingleV3Checks(tx_to_submit, *ancestors_3gen, empty_conflicts_set, GetVirtualTransactionSize(*tx_to_submit))};
+ BOOST_CHECK_EQUAL(result_3gen->first, expected_error_str);
+ // The other mempool child is not returned because sibling eviction is not allowed.
+ BOOST_CHECK_EQUAL(result_3gen->second, nullptr);
+ }
+
+ // Configuration where tx has multiple generations of descendants is not tested because that is
+ // equivalent to the tx with multiple generations of ancestors.
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/util/chainstate.h b/src/test/util/chainstate.h
index e2a88eacdd..ff95e64b7e 100644
--- a/src/test/util/chainstate.h
+++ b/src/test/util/chainstate.h
@@ -91,13 +91,16 @@ CreateAndActivateUTXOSnapshot(
// these blocks instead
CBlockIndex *pindex = orig_tip;
while (pindex && pindex != chain.m_chain.Tip()) {
- pindex->nStatus &= ~BLOCK_HAVE_DATA;
- pindex->nStatus &= ~BLOCK_HAVE_UNDO;
- // We have to set the ASSUMED_VALID flag, because otherwise it
- // would not be possible to have a block index entry without HAVE_DATA
- // and with nTx > 0 (since we aren't setting the pruned flag);
- // see CheckBlockIndex().
- pindex->nStatus |= BLOCK_ASSUMED_VALID;
+ // Remove all data and validity flags by just setting
+ // BLOCK_VALID_TREE. Also reset transaction counts and sequence
+ // ids that are set when blocks are received, to make test setup
+ // more realistic and satisfy consistency checks in
+ // CheckBlockIndex().
+ assert(pindex->IsValid(BlockStatus::BLOCK_VALID_TREE));
+ pindex->nStatus = BlockStatus::BLOCK_VALID_TREE;
+ pindex->nTx = 0;
+ pindex->nChainTx = 0;
+ pindex->nSequenceId = 0;
pindex = pindex->pprev;
}
}
diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp
index 08d1b4c902..ad7a38d3fe 100644
--- a/src/test/util/mining.cpp
+++ b/src/test/util/mining.cpp
@@ -95,12 +95,12 @@ COutPoint MineBlock(const NodeContext& node, std::shared_ptr<CBlock>& block)
const auto old_height = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight());
bool new_block;
BlockValidationStateCatcher bvsc{block->GetHash()};
- RegisterValidationInterface(&bvsc);
+ node.validation_signals->RegisterValidationInterface(&bvsc);
const bool processed{chainman.ProcessNewBlock(block, true, true, &new_block)};
const bool duplicate{!new_block && processed};
assert(!duplicate);
- UnregisterValidationInterface(&bvsc);
- SyncWithValidationInterfaceQueue();
+ node.validation_signals->UnregisterValidationInterface(&bvsc);
+ node.validation_signals->SyncWithValidationInterfaceQueue();
const bool was_valid{bvsc.m_state && bvsc.m_state->IsValid()};
assert(old_height + was_valid == WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight()));
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index 8789e86196..e9566cf50b 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -2,6 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <config/bitcoin-config.h> // IWYU pragma: keep
+
#include <test/util/setup_common.h>
#include <kernel/validation_cache_sizes.h>
@@ -10,7 +12,6 @@
#include <banman.h>
#include <chainparams.h>
#include <common/system.h>
-#include <common/url.h>
#include <consensus/consensus.h>
#include <consensus/params.h>
#include <consensus/validation.h>
@@ -44,11 +45,11 @@
#include <test/util/net.h>
#include <test/util/random.h>
#include <test/util/txmempool.h>
-#include <timedata.h>
#include <txdb.h>
#include <txmempool.h>
#include <util/chaintype.h>
#include <util/check.h>
+#include <util/fs_helpers.h>
#include <util/rbf.h>
#include <util/strencodings.h>
#include <util/string.h>
@@ -77,7 +78,6 @@ using node::RegenerateCommitments;
using node::VerifyLoadedChainstate;
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
-UrlDecodeFn* const URL_DECODE = nullptr;
/** Random context to get unique temp data dirs. Separate from g_insecure_rand_ctx, which can be seeded from a const env var */
static FastRandomContext g_insecure_rand_ctx_temp_path;
@@ -97,9 +97,22 @@ struct NetworkSetup
};
static NetworkSetup g_networksetup_instance;
+/** Register test-only arguments */
+static void SetupUnitTestArgs(ArgsManager& argsman)
+{
+ argsman.AddArg("-testdatadir", strprintf("Custom data directory (default: %s<random_string>)", fs::PathToString(fs::temp_directory_path() / "test_common_" PACKAGE_NAME / "")),
+ ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
+}
+
+/** Test setup failure */
+static void ExitFailure(std::string_view str_err)
+{
+ std::cerr << str_err << std::endl;
+ exit(EXIT_FAILURE);
+}
+
BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vector<const char*>& extra_args)
- : m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME / g_insecure_rand_ctx_temp_path.rand256().ToString()},
- m_args{}
+ : m_args{}
{
m_node.shutdown = &m_interrupt;
m_node.args = &gArgs;
@@ -120,18 +133,49 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto
arguments = Cat(arguments, G_TEST_COMMAND_LINE_ARGUMENTS());
}
util::ThreadRename("test");
- fs::create_directories(m_path_root);
- m_args.ForceSetArg("-datadir", fs::PathToString(m_path_root));
- gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root));
gArgs.ClearPathCache();
{
SetupServerArgs(*m_node.args);
+ SetupUnitTestArgs(*m_node.args);
std::string error;
if (!m_node.args->ParseParameters(arguments.size(), arguments.data(), error)) {
m_node.args->ClearArgs();
throw std::runtime_error{error};
}
}
+
+ if (!m_node.args->IsArgSet("-testdatadir")) {
+ // By default, the data directory has a random name
+ const auto rand_str{g_insecure_rand_ctx_temp_path.rand256().ToString()};
+ m_path_root = fs::temp_directory_path() / "test_common_" PACKAGE_NAME / rand_str;
+ TryCreateDirectories(m_path_root);
+ } else {
+ // Custom data directory
+ m_has_custom_datadir = true;
+ fs::path root_dir{m_node.args->GetPathArg("-testdatadir")};
+ if (root_dir.empty()) ExitFailure("-testdatadir argument is empty, please specify a path");
+
+ root_dir = fs::absolute(root_dir);
+ const std::string test_path{G_TEST_GET_FULL_NAME ? G_TEST_GET_FULL_NAME() : ""};
+ m_path_lock = root_dir / "test_common_" PACKAGE_NAME / fs::PathFromString(test_path);
+ m_path_root = m_path_lock / "datadir";
+
+ // Try to obtain the lock; if unsuccessful don't disturb the existing test.
+ TryCreateDirectories(m_path_lock);
+ if (util::LockDirectory(m_path_lock, ".lock", /*probe_only=*/false) != util::LockResult::Success) {
+ ExitFailure("Cannot obtain a lock on test data lock directory " + fs::PathToString(m_path_lock) + '\n' + "The test executable is probably already running.");
+ }
+
+ // Always start with a fresh data directory; this doesn't delete the .lock file located one level above.
+ fs::remove_all(m_path_root);
+ if (!TryCreateDirectories(m_path_root)) ExitFailure("Cannot create test data directory");
+
+ // Print the test directory name if custom.
+ std::cout << "Test directory (will not be deleted): " << m_path_root << std::endl;
+ }
+ m_args.ForceSetArg("-datadir", fs::PathToString(m_path_root));
+ gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root));
+
SelectParams(chainType);
SeedInsecureRand();
if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN);
@@ -139,6 +183,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto
AppInitParameterInteraction(*m_node.args);
LogInstance().StartLogging();
m_node.kernel = std::make_unique<kernel::Context>();
+ m_node.ecc_context = std::make_unique<ECC_Context>();
SetupEnvironment();
ValidationCacheSizes validation_cache_sizes{};
@@ -156,10 +201,17 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto
BasicTestingSetup::~BasicTestingSetup()
{
+ m_node.ecc_context.reset();
m_node.kernel.reset();
SetMockTime(0s); // Reset mocktime for following tests
LogInstance().DisconnectTestLogger();
- fs::remove_all(m_path_root);
+ if (m_has_custom_datadir) {
+ // Only remove the lock file, preserve the data directory.
+ UnlockDirectory(m_path_lock, ".lock");
+ fs::remove(m_path_lock / ".lock");
+ } else {
+ fs::remove_all(m_path_root);
+ }
gArgs.ClearArgs();
}
@@ -172,7 +224,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, const std::vecto
// from blocking due to queue overrun.
m_node.scheduler = std::make_unique<CScheduler>();
m_node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { m_node.scheduler->serviceQueue(); });
- GetMainSignals().RegisterBackgroundSignalScheduler(*m_node.scheduler);
+ m_node.validation_signals = std::make_unique<ValidationSignals>(std::make_unique<SerialTaskRunner>(*m_node.scheduler));
m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(*m_node.args), DEFAULT_ACCEPT_STALE_FEE_ESTIMATES);
m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node));
@@ -184,9 +236,9 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, const std::vecto
const ChainstateManager::Options chainman_opts{
.chainparams = chainparams,
.datadir = m_args.GetDataDirNet(),
- .adjusted_time_callback = GetAdjustedTime,
.check_block_index = true,
.notifications = *m_node.notifications,
+ .signals = m_node.validation_signals.get(),
.worker_threads_num = 2,
};
const BlockManager::Options blockman_opts{
@@ -204,8 +256,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, const std::vecto
ChainTestingSetup::~ChainTestingSetup()
{
if (m_node.scheduler) m_node.scheduler->stop();
- GetMainSignals().FlushBackgroundCallbacks();
- GetMainSignals().UnregisterBackgroundSignalScheduler();
+ m_node.validation_signals->FlushBackgroundCallbacks();
m_node.connman.reset();
m_node.banman.reset();
m_node.addrman.reset();
@@ -214,6 +265,7 @@ ChainTestingSetup::~ChainTestingSetup()
m_node.mempool.reset();
m_node.fee_estimator.reset();
m_node.chainman.reset();
+ m_node.validation_signals.reset();
m_node.scheduler.reset();
}
@@ -500,9 +552,9 @@ void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate)
assert(m_node.mempool->size() == 0);
// The target feerate cannot be too low...
// ...otherwise the transaction's feerate will need to be negative.
- assert(target_feerate > m_node.mempool->m_incremental_relay_feerate);
+ assert(target_feerate > m_node.mempool->m_opts.incremental_relay_feerate);
// ...otherwise this is not meaningful. The feerate policy uses the maximum of both feerates.
- assert(target_feerate > m_node.mempool->m_min_relay_feerate);
+ assert(target_feerate > m_node.mempool->m_opts.min_relay_feerate);
// Manually create an invalid transaction. Manually set the fee in the CTxMemPoolEntry to
// achieve the exact target feerate.
@@ -513,7 +565,7 @@ void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate)
LockPoints lp;
// The new mempool min feerate is equal to the removed package's feerate + incremental feerate.
const auto tx_fee = target_feerate.GetFee(GetVirtualTransactionSize(*tx)) -
- m_node.mempool->m_incremental_relay_feerate.GetFee(GetVirtualTransactionSize(*tx));
+ m_node.mempool->m_opts.incremental_relay_feerate.GetFee(GetVirtualTransactionSize(*tx));
m_node.mempool->addUnchecked(CTxMemPoolEntry(tx, /*fee=*/tx_fee,
/*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0,
/*spends_coinbase=*/true, /*sigops_cost=*/1, lp));
diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h
index 9ff4c372a5..dbd66e3585 100644
--- a/src/test/util/setup_common.h
+++ b/src/test/util/setup_common.h
@@ -6,6 +6,7 @@
#define BITCOIN_TEST_UTIL_SETUP_COMMON_H
#include <common/args.h> // IWYU pragma: export
+#include <kernel/context.h>
#include <key.h>
#include <node/caches.h>
#include <node/context.h> // IWYU pragma: export
@@ -15,6 +16,7 @@
#include <util/chaintype.h> // IWYU pragma: export
#include <util/check.h>
#include <util/fs.h>
+#include <util/signalinterrupt.h>
#include <util/string.h>
#include <util/vector.h>
@@ -32,6 +34,9 @@ extern const std::function<void(const std::string&)> G_TEST_LOG_FUN;
/** Retrieve the command line arguments. */
extern const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS;
+/** Retrieve the unit test name. */
+extern const std::function<std::string()> G_TEST_GET_FULL_NAME;
+
// Enable BOOST_CHECK_EQUAL for enum class types
namespace std {
template <typename T>
@@ -53,7 +58,9 @@ struct BasicTestingSetup {
explicit BasicTestingSetup(const ChainType chainType = ChainType::MAIN, const std::vector<const char*>& extra_args = {});
~BasicTestingSetup();
- const fs::path m_path_root;
+ fs::path m_path_root;
+ fs::path m_path_lock;
+ bool m_has_custom_datadir{false};
ArgsManager m_args;
};
diff --git a/src/test/util/txmempool.cpp b/src/test/util/txmempool.cpp
index 379c3c9329..870cdd0b32 100644
--- a/src/test/util/txmempool.cpp
+++ b/src/test/util/txmempool.cpp
@@ -7,6 +7,7 @@
#include <chainparams.h>
#include <node/context.h>
#include <node/mempool_args.h>
+#include <policy/v3_policy.h>
#include <txmempool.h>
#include <util/check.h>
#include <util/time.h>
@@ -21,6 +22,7 @@ CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node)
// Default to always checking mempool regardless of
// chainparams.DefaultConsistencyChecks for tests
.check_ratio = 1,
+ .signals = node.validation_signals.get(),
};
const auto result{ApplyArgsManOptions(*node.args, ::Params(), mempool_opts)};
Assert(result);
@@ -66,12 +68,6 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,
return strprintf("tx %s unexpectedly failed: %s", wtxid.ToString(), atmp_result.m_state.ToString());
}
- //m_replaced_transactions should exist iff the result was VALID
- if (atmp_result.m_replaced_transactions.has_value() != valid) {
- return strprintf("tx %s result should %shave m_replaced_transactions",
- wtxid.ToString(), valid ? "" : "not ");
- }
-
// m_vsize and m_base_fees should exist iff the result was VALID or MEMPOOL_ENTRY
const bool mempool_entry{atmp_result.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY};
if (atmp_result.m_base_fees.has_value() != (valid || mempool_entry)) {
@@ -116,3 +112,28 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,
}
return std::nullopt;
}
+
+void CheckMempoolV3Invariants(const CTxMemPool& tx_pool)
+{
+ LOCK(tx_pool.cs);
+ for (const auto& tx_info : tx_pool.infoAll()) {
+ const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));
+ if (tx_info.tx->nVersion == 3) {
+ // Check that special v3 ancestor/descendant limits and rules are always respected
+ Assert(entry.GetCountWithDescendants() <= V3_DESCENDANT_LIMIT);
+ Assert(entry.GetCountWithAncestors() <= V3_ANCESTOR_LIMIT);
+ // If this transaction has at least 1 ancestor, it's a "child" and has restricted weight.
+ if (entry.GetCountWithAncestors() > 1) {
+ Assert(entry.GetTxSize() <= V3_CHILD_MAX_VSIZE);
+ // All v3 transactions must only have v3 unconfirmed parents.
+ const auto& parents = entry.GetMemPoolParentsConst();
+ Assert(parents.begin()->get().GetSharedTx()->nVersion == 3);
+ }
+ } else if (entry.GetCountWithAncestors() > 1) {
+ // All non-v3 transactions must only have non-v3 unconfirmed parents.
+ for (const auto& parent : entry.GetMemPoolParentsConst()) {
+ Assert(parent.get().GetSharedTx()->nVersion != 3);
+ }
+ }
+ }
+}
diff --git a/src/test/util/txmempool.h b/src/test/util/txmempool.h
index a866d1ce74..b3022af7df 100644
--- a/src/test/util/txmempool.h
+++ b/src/test/util/txmempool.h
@@ -46,4 +46,14 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,
const PackageMempoolAcceptResult& result,
bool expect_valid,
const CTxMemPool* mempool);
+
+/** For every transaction in tx_pool, check v3 invariants:
+ * - a v3 tx's ancestor count must be within V3_ANCESTOR_LIMIT
+ * - a v3 tx's descendant count must be within V3_DESCENDANT_LIMIT
+ * - if a v3 tx has ancestors, its sigop-adjusted vsize must be within V3_CHILD_MAX_VSIZE
+ * - any non-v3 tx must only have non-v3 parents
+ * - any v3 tx must only have v3 parents
+ * */
+void CheckMempoolV3Invariants(const CTxMemPool& tx_pool);
+
#endif // BITCOIN_TEST_UTIL_TXMEMPOOL_H
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 47808a2a58..9a2add748e 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -290,13 +290,18 @@ BOOST_AUTO_TEST_CASE(util_TrimString)
BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime)
{
+ BOOST_CHECK_EQUAL(FormatISO8601DateTime(971890963199), "32767-12-31T23:59:59Z");
+ BOOST_CHECK_EQUAL(FormatISO8601DateTime(971890876800), "32767-12-31T00:00:00Z");
BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z");
BOOST_CHECK_EQUAL(FormatISO8601DateTime(0), "1970-01-01T00:00:00Z");
}
BOOST_AUTO_TEST_CASE(util_FormatISO8601Date)
{
+ BOOST_CHECK_EQUAL(FormatISO8601Date(971890963199), "32767-12-31");
+ BOOST_CHECK_EQUAL(FormatISO8601Date(971890876800), "32767-12-31");
BOOST_CHECK_EQUAL(FormatISO8601Date(1317425777), "2011-09-30");
+ BOOST_CHECK_EQUAL(FormatISO8601Date(0), "1970-01-01");
}
BOOST_AUTO_TEST_CASE(util_FormatMoney)
diff --git a/src/test/util_threadnames_tests.cpp b/src/test/util_threadnames_tests.cpp
index 45d3a58fd3..df5b1a4461 100644
--- a/src/test/util_threadnames_tests.cpp
+++ b/src/test/util_threadnames_tests.cpp
@@ -11,9 +11,7 @@
#include <thread>
#include <vector>
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <boost/test/unit_test.hpp>
diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp
index 35e5c6a037..69f4e305ab 100644
--- a/src/test/validation_block_tests.cpp
+++ b/src/test/validation_block_tests.cpp
@@ -127,6 +127,7 @@ std::shared_ptr<const CBlock> MinerTestingSetup::BadBlock(const uint256& prev_ha
return ret;
}
+// NOLINTNEXTLINE(misc-no-recursion)
void MinerTestingSetup::BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks)
{
if (height <= 0 || blocks.size() >= max_size) return;
@@ -158,7 +159,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
bool ignored;
// Connect the genesis block and drain any outstanding events
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, true, &ignored));
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
// subscribe to events (this subscriber will validate event ordering)
const CBlockIndex* initial_tip = nullptr;
@@ -167,7 +168,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
initial_tip = m_node.chainman->ActiveChain().Tip();
}
auto sub = std::make_shared<TestSubscriber>(initial_tip->GetBlockHash());
- RegisterSharedValidationInterface(sub);
+ m_node.validation_signals->RegisterSharedValidationInterface(sub);
// create a bunch of threads that repeatedly process a block generated above at random
// this will create parallelism and randomness inside validation - the ValidationInterface
@@ -196,9 +197,9 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
for (auto& t : threads) {
t.join();
}
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
- UnregisterSharedValidationInterface(sub);
+ m_node.validation_signals->UnregisterSharedValidationInterface(sub);
LOCK(cs_main);
BOOST_CHECK_EQUAL(sub->m_expected_tip, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp
index fe2d2ba592..1c02066047 100644
--- a/src/test/validation_chainstate_tests.cpp
+++ b/src/test/validation_chainstate_tests.cpp
@@ -12,6 +12,7 @@
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <uint256.h>
+#include <util/check.h>
#include <validation.h>
#include <vector>
@@ -102,14 +103,14 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 2);
- Chainstate& background_cs{*[&] {
+ Chainstate& background_cs{*Assert([&]() -> Chainstate* {
for (Chainstate* cs : chainman.GetAll()) {
if (cs != &chainman.ActiveChainstate()) {
return cs;
}
}
- assert(false);
- }()};
+ return nullptr;
+ }())};
// Append the first block to the background chain.
BlockValidationState state;
diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
index 368ba8bee4..4bf66a55eb 100644
--- a/src/test/validation_chainstatemanager_tests.cpp
+++ b/src/test/validation_chainstatemanager_tests.cpp
@@ -15,7 +15,6 @@
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <test/util/validation.h>
-#include <timedata.h>
#include <uint256.h>
#include <validation.h>
#include <validationinterface.h>
@@ -103,7 +102,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager, TestChain100Setup)
BOOST_CHECK_EQUAL(active_tip2, c2.m_chain.Tip());
// Let scheduler events finish running to avoid accessing memory that is going to be unloaded
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
}
//! Test rebalancing the caches associated with each chainstate.
@@ -277,9 +276,6 @@ struct SnapshotTestSetup : TestChain100Setup {
BOOST_CHECK_EQUAL(
*node::ReadSnapshotBaseBlockhash(found),
*chainman.SnapshotBlockhash());
-
- // Ensure that the genesis block was not marked assumed-valid.
- BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid());
}
const auto& au_data = ::Params().AssumeutxoForHeight(snapshot_height);
@@ -375,7 +371,7 @@ struct SnapshotTestSetup : TestChain100Setup {
cs->ForceFlushStateToDisk();
}
// Process all callbacks referring to the old manager before wiping it.
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
LOCK(::cs_main);
chainman.ResetChainstates();
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0);
@@ -383,8 +379,8 @@ struct SnapshotTestSetup : TestChain100Setup {
const ChainstateManager::Options chainman_opts{
.chainparams = ::Params(),
.datadir = chainman.m_options.datadir,
- .adjusted_time_callback = GetAdjustedTime,
.notifications = *m_node.notifications,
+ .signals = m_node.validation_signals.get(),
};
const BlockManager::Options blockman_opts{
.chainparams = chainman_opts.chainparams,
@@ -411,7 +407,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
//! - First, verify that setBlockIndexCandidates is as expected when using a single,
//! fully-validating chainstate.
//!
-//! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate
+//! - Then mark a region of the chain as missing data and introduce a second chainstate
//! that will tolerate assumed-valid blocks. Run LoadBlockIndex() and ensure that the first
//! chainstate only contains fully validated blocks and the other chainstate contains all blocks,
//! except those marked assume-valid, because those entries don't HAVE_DATA.
@@ -422,7 +418,6 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
Chainstate& cs1 = chainman.ActiveChainstate();
int num_indexes{0};
- int num_assumed_valid{0};
// Blocks in range [assumed_valid_start_idx, last_assumed_valid_idx) will be
// marked as assumed-valid and not having data.
const int expected_assumed_valid{20};
@@ -457,35 +452,30 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
reload_all_block_indexes();
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 1);
- // Mark some region of the chain assumed-valid, and remove the HAVE_DATA flag.
+ // Reset some region of the chain's nStatus, removing the HAVE_DATA flag.
for (int i = 0; i <= cs1.m_chain.Height(); ++i) {
LOCK(::cs_main);
auto index = cs1.m_chain[i];
- // Blocks with heights in range [91, 110] are marked ASSUMED_VALID
+ // Blocks with heights in range [91, 110] are marked as missing data.
if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) {
- index->nStatus = BlockStatus::BLOCK_VALID_TREE | BlockStatus::BLOCK_ASSUMED_VALID;
+ index->nStatus = BlockStatus::BLOCK_VALID_TREE;
+ index->nTx = 0;
+ index->nChainTx = 0;
}
++num_indexes;
- if (index->IsAssumedValid()) ++num_assumed_valid;
// Note the last fully-validated block as the expected validated tip.
if (i == (assumed_valid_start_idx - 1)) {
validated_tip = index;
- BOOST_CHECK(!index->IsAssumedValid());
}
// Note the last assumed valid block as the snapshot base
if (i == last_assumed_valid_idx - 1) {
assumed_base = index;
- BOOST_CHECK(index->IsAssumedValid());
- } else if (i == last_assumed_valid_idx) {
- BOOST_CHECK(!index->IsAssumedValid());
}
}
- BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
-
// Note: cs2's tip is not set when ActivateExistingSnapshot is called.
Chainstate& cs2 = WITH_LOCK(::cs_main,
return chainman.ActivateExistingSnapshot(*assumed_base->phashBlock));
diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp
index 14440571eb..93a884be6d 100644
--- a/src/test/validation_tests.cpp
+++ b/src/test/validation_tests.cpp
@@ -4,12 +4,17 @@
#include <chainparams.h>
#include <consensus/amount.h>
+#include <consensus/merkle.h>
+#include <core_io.h>
+#include <hash.h>
#include <net.h>
#include <signet.h>
#include <uint256.h>
#include <util/chaintype.h>
#include <validation.h>
+#include <string>
+
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
@@ -145,4 +150,214 @@ BOOST_AUTO_TEST_CASE(test_assumeutxo)
BOOST_CHECK_EQUAL(out110_2.nChainTx, 111U);
}
+BOOST_AUTO_TEST_CASE(block_malleation)
+{
+ // Test utilities that calls `IsBlockMutated` and then clears the validity
+ // cache flags on `CBlock`.
+ auto is_mutated = [](CBlock& block, bool check_witness_root) {
+ bool mutated{IsBlockMutated(block, check_witness_root)};
+ block.fChecked = false;
+ block.m_checked_witness_commitment = false;
+ block.m_checked_merkle_root = false;
+ return mutated;
+ };
+ auto is_not_mutated = [&is_mutated](CBlock& block, bool check_witness_root) {
+ return !is_mutated(block, check_witness_root);
+ };
+
+ // Test utilities to create coinbase transactions and insert witness
+ // commitments.
+ //
+ // Note: this will not include the witness stack by default to avoid
+ // triggering the "no witnesses allowed for blocks that don't commit to
+ // witnesses" rule when testing other malleation vectors.
+ auto create_coinbase_tx = [](bool include_witness = false) {
+ CMutableTransaction coinbase;
+ coinbase.vin.resize(1);
+ if (include_witness) {
+ coinbase.vin[0].scriptWitness.stack.resize(1);
+ coinbase.vin[0].scriptWitness.stack[0] = std::vector<unsigned char>(32, 0x00);
+ }
+
+ coinbase.vout.resize(1);
+ coinbase.vout[0].scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT);
+ coinbase.vout[0].scriptPubKey[0] = OP_RETURN;
+ coinbase.vout[0].scriptPubKey[1] = 0x24;
+ coinbase.vout[0].scriptPubKey[2] = 0xaa;
+ coinbase.vout[0].scriptPubKey[3] = 0x21;
+ coinbase.vout[0].scriptPubKey[4] = 0xa9;
+ coinbase.vout[0].scriptPubKey[5] = 0xed;
+
+ auto tx = MakeTransactionRef(coinbase);
+ assert(tx->IsCoinBase());
+ return tx;
+ };
+ auto insert_witness_commitment = [](CBlock& block, uint256 commitment) {
+ assert(!block.vtx.empty() && block.vtx[0]->IsCoinBase() && !block.vtx[0]->vout.empty());
+
+ CMutableTransaction mtx{*block.vtx[0]};
+ CHash256().Write(commitment).Write(std::vector<unsigned char>(32, 0x00)).Finalize(commitment);
+ memcpy(&mtx.vout[0].scriptPubKey[6], commitment.begin(), 32);
+ block.vtx[0] = MakeTransactionRef(mtx);
+ };
+
+ {
+ CBlock block;
+
+ // Empty block is expected to have merkle root of 0x0.
+ BOOST_CHECK(block.vtx.empty());
+ block.hashMerkleRoot = uint256{1};
+ BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false));
+ block.hashMerkleRoot = uint256{};
+ BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false));
+
+ // Block with a single coinbase tx is mutated if the merkle root is not
+ // equal to the coinbase tx's hash.
+ block.vtx.push_back(create_coinbase_tx());
+ BOOST_CHECK(block.vtx[0]->GetHash() != block.hashMerkleRoot);
+ BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false));
+ block.hashMerkleRoot = block.vtx[0]->GetHash();
+ BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false));
+
+ // Block with two transactions is mutated if the merkle root does not
+ // match the double sha256 of the concatenation of the two transaction
+ // hashes.
+ block.vtx.push_back(MakeTransactionRef(CMutableTransaction{}));
+ BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false));
+ HashWriter hasher;
+ hasher.write(block.vtx[0]->GetHash());
+ hasher.write(block.vtx[1]->GetHash());
+ block.hashMerkleRoot = hasher.GetHash();
+ BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false));
+
+ // Block with two transactions is mutated if any node is duplicate.
+ {
+ block.vtx[1] = block.vtx[0];
+ HashWriter hasher;
+ hasher.write(block.vtx[0]->GetHash());
+ hasher.write(block.vtx[1]->GetHash());
+ block.hashMerkleRoot = hasher.GetHash();
+ BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false));
+ }
+
+ // Blocks with 64-byte coinbase transactions are not considered mutated
+ block.vtx.clear();
+ {
+ CMutableTransaction mtx;
+ mtx.vin.resize(1);
+ mtx.vout.resize(1);
+ mtx.vout[0].scriptPubKey.resize(4);
+ block.vtx.push_back(MakeTransactionRef(mtx));
+ block.hashMerkleRoot = block.vtx.back()->GetHash();
+ assert(block.vtx.back()->IsCoinBase());
+ assert(GetSerializeSize(TX_NO_WITNESS(block.vtx.back())) == 64);
+ }
+ BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false));
+ }
+
+ {
+ // Test merkle root malleation
+
+ // Pseudo code to mine transactions tx{1,2,3}:
+ //
+ // ```
+ // loop {
+ // tx1 = random_tx()
+ // tx2 = random_tx()
+ // tx3 = deserialize_tx(txid(tx1) || txid(tx2));
+ // if serialized_size_without_witness(tx3) == 64 {
+ // print(hex(tx3))
+ // break
+ // }
+ // }
+ // ```
+ //
+ // The `random_tx` function used to mine the txs below simply created
+ // empty transactions with a random version field.
+ CMutableTransaction tx1;
+ BOOST_CHECK(DecodeHexTx(tx1, "ff204bd0000000000000", /*try_no_witness=*/true, /*try_witness=*/false));
+ CMutableTransaction tx2;
+ BOOST_CHECK(DecodeHexTx(tx2, "8ae53c92000000000000", /*try_no_witness=*/true, /*try_witness=*/false));
+ CMutableTransaction tx3;
+ BOOST_CHECK(DecodeHexTx(tx3, "cdaf22d00002c6a7f848f8ae4d30054e61dcf3303d6fe01d282163341f06feecc10032b3160fcab87bdfe3ecfb769206ef2d991b92f8a268e423a6ef4d485f06", /*try_no_witness=*/true, /*try_witness=*/false));
+ {
+ // Verify that double_sha256(txid1||txid2) == txid3
+ HashWriter hasher;
+ hasher.write(tx1.GetHash());
+ hasher.write(tx2.GetHash());
+ assert(hasher.GetHash() == tx3.GetHash());
+ // Verify that tx3 is 64 bytes in size (without witness).
+ assert(GetSerializeSize(TX_NO_WITNESS(tx3)) == 64);
+ }
+
+ CBlock block;
+ block.vtx.push_back(MakeTransactionRef(tx1));
+ block.vtx.push_back(MakeTransactionRef(tx2));
+ uint256 merkle_root = block.hashMerkleRoot = BlockMerkleRoot(block);
+ BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false));
+
+ // Mutate the block by replacing the two transactions with one 64-byte
+ // transaction that serializes into the concatenation of the txids of
+ // the transactions in the unmutated block.
+ block.vtx.clear();
+ block.vtx.push_back(MakeTransactionRef(tx3));
+ BOOST_CHECK(!block.vtx.back()->IsCoinBase());
+ BOOST_CHECK(BlockMerkleRoot(block) == merkle_root);
+ BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false));
+ }
+
+ {
+ CBlock block;
+ block.vtx.push_back(create_coinbase_tx(/*include_witness=*/true));
+ {
+ CMutableTransaction mtx;
+ mtx.vin.resize(1);
+ mtx.vin[0].scriptWitness.stack.resize(1);
+ mtx.vin[0].scriptWitness.stack[0] = {0};
+ block.vtx.push_back(MakeTransactionRef(mtx));
+ }
+ block.hashMerkleRoot = BlockMerkleRoot(block);
+ // Block with witnesses is considered mutated if the witness commitment
+ // is not validated.
+ BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false));
+ // Block with invalid witness commitment is considered mutated.
+ BOOST_CHECK(is_mutated(block, /*check_witness_root=*/true));
+
+ // Block with valid commitment is not mutated
+ {
+ auto commitment{BlockWitnessMerkleRoot(block)};
+ insert_witness_commitment(block, commitment);
+ block.hashMerkleRoot = BlockMerkleRoot(block);
+ }
+ BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/true));
+
+ // Malleating witnesses should be caught by `IsBlockMutated`.
+ {
+ CMutableTransaction mtx{*block.vtx[1]};
+ assert(!mtx.vin[0].scriptWitness.stack[0].empty());
+ ++mtx.vin[0].scriptWitness.stack[0][0];
+ block.vtx[1] = MakeTransactionRef(mtx);
+ }
+ // Without also updating the witness commitment, the merkle root should
+ // not change when changing one of the witnesses.
+ BOOST_CHECK(block.hashMerkleRoot == BlockMerkleRoot(block));
+ BOOST_CHECK(is_mutated(block, /*check_witness_root=*/true));
+ {
+ auto commitment{BlockWitnessMerkleRoot(block)};
+ insert_witness_commitment(block, commitment);
+ block.hashMerkleRoot = BlockMerkleRoot(block);
+ }
+ BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/true));
+
+ // Test malleating the coinbase witness reserved value
+ {
+ CMutableTransaction mtx{*block.vtx[0]};
+ mtx.vin[0].scriptWitness.stack.resize(0);
+ block.vtx[0] = MakeTransactionRef(mtx);
+ block.hashMerkleRoot = BlockMerkleRoot(block);
+ }
+ BOOST_CHECK(is_mutated(block, /*check_witness_root=*/true));
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/validationinterface_tests.cpp b/src/test/validationinterface_tests.cpp
index 5979441057..a46cfc3029 100644
--- a/src/test/validationinterface_tests.cpp
+++ b/src/test/validationinterface_tests.cpp
@@ -28,7 +28,7 @@ BOOST_AUTO_TEST_CASE(unregister_validation_interface_race)
const CBlock block_dummy;
BlockValidationState state_dummy;
while (generate) {
- GetMainSignals().BlockChecked(block_dummy, state_dummy);
+ m_node.validation_signals->BlockChecked(block_dummy, state_dummy);
}
}};
@@ -37,8 +37,8 @@ BOOST_AUTO_TEST_CASE(unregister_validation_interface_race)
// keep going for about 1 sec, which is 250k iterations
for (int i = 0; i < 250000; i++) {
auto sub = std::make_shared<TestSubscriberNoop>();
- RegisterSharedValidationInterface(sub);
- UnregisterSharedValidationInterface(sub);
+ m_node.validation_signals->RegisterSharedValidationInterface(sub);
+ m_node.validation_signals->UnregisterSharedValidationInterface(sub);
}
// tell the other thread we are done
generate = false;
@@ -52,8 +52,8 @@ BOOST_AUTO_TEST_CASE(unregister_validation_interface_race)
class TestInterface : public CValidationInterface
{
public:
- TestInterface(std::function<void()> on_call = nullptr, std::function<void()> on_destroy = nullptr)
- : m_on_call(std::move(on_call)), m_on_destroy(std::move(on_destroy))
+ TestInterface(ValidationSignals& signals, std::function<void()> on_call = nullptr, std::function<void()> on_destroy = nullptr)
+ : m_on_call(std::move(on_call)), m_on_destroy(std::move(on_destroy)), m_signals{signals}
{
}
virtual ~TestInterface()
@@ -64,14 +64,15 @@ public:
{
if (m_on_call) m_on_call();
}
- static void Call()
+ void Call()
{
CBlock block;
BlockValidationState state;
- GetMainSignals().BlockChecked(block, state);
+ m_signals.BlockChecked(block, state);
}
std::function<void()> m_on_call;
std::function<void()> m_on_destroy;
+ ValidationSignals& m_signals;
};
// Regression test to ensure UnregisterAllValidationInterfaces calls don't
@@ -80,17 +81,23 @@ public:
BOOST_AUTO_TEST_CASE(unregister_all_during_call)
{
bool destroyed = false;
- RegisterSharedValidationInterface(std::make_shared<TestInterface>(
+ auto shared{std::make_shared<TestInterface>(
+ *m_node.validation_signals,
[&] {
// First call should decrements reference count 2 -> 1
- UnregisterAllValidationInterfaces();
+ m_node.validation_signals->UnregisterAllValidationInterfaces();
BOOST_CHECK(!destroyed);
// Second call should not decrement reference count 1 -> 0
- UnregisterAllValidationInterfaces();
+ m_node.validation_signals->UnregisterAllValidationInterfaces();
BOOST_CHECK(!destroyed);
},
- [&] { destroyed = true; }));
- TestInterface::Call();
+ [&] { destroyed = true; })};
+ m_node.validation_signals->RegisterSharedValidationInterface(shared);
+ BOOST_CHECK(shared.use_count() == 2);
+ shared->Call();
+ BOOST_CHECK(shared.use_count() == 1);
+ BOOST_CHECK(!destroyed);
+ shared.reset();
BOOST_CHECK(destroyed);
}