aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAva Chow <github@achow101.com>2024-08-07 13:05:04 -0400
committerAva Chow <github@achow101.com>2024-08-07 13:05:04 -0400
commitda083d4bbdb37737f5080fada97bd15f5a8bfb2d (patch)
tree8db2d721386282d13fdcd6fd976df5d086ee64b8
parent676abd1af754964858a60fddffb9a12c0a6c2749 (diff)
parent6bfa26048dbafb91e9ca63ea8d3960271e798098 (diff)
Merge bitcoin/bitcoin#29775: Testnet4 including PoW difficulty adjustment fix
6bfa26048dbafb91e9ca63ea8d3960271e798098 testnet: Add timewarp attack prevention for Testnet4 (Fabian Jahr) 0100907ca168c53e8fe044bdda396f308825162c testnet: Add Testnet4 difficulty adjustment rules fix (Fabian Jahr) 74a04f9e7ad6a16988149cc3438b9ce13c91cdb9 testnet: Introduce Testnet4 (Fabian Jahr) Pull request description: To supplement the [ongoing conceptual discussion about a testnet reset](https://groups.google.com/g/bitcoindev/c/9bL00vRj7OU/m/9yCPo3uUBwAJ) I have drafted a move to v4 including a fix to the difficulty adjustment mechanism, which was part of the motivation that started the discussion. Conceptual considerations: - The conceptual discussion about doing a testnet4 or softforking the fix into testnet3 is outside of the scope of this PR and I would ask reviewers to contribute their opinions on this on the ML instead. However, I am happy to adapt this PR to a softfork change on testnet3 if there is consensus for that instead. - The difficulty adjustment fix suggested here touches the `CalculateNextWorkRequired` function and uses the same logic used in `GetNextWorkRequired` to find the last previous block that was not mined with difficulty 1 under the exceptionf. An alternative fix briefly mentioned on the mailing list by Jameson Lopp would be to "restrict the special testnet minimum difficulty rule so that it can't be triggered on the block right before a difficulty retarget". That would also fix the issue but I find my suggestion here a bit more elegant. ACKs for top commit: jsarenik: tACK 6bfa26048dba achow101: ACK 6bfa26048dbafb91e9ca63ea8d3960271e798098 murchandamus: tACK 6bfa26048dbafb91e9ca63ea8d3960271e798098 Tree-SHA512: 0b8b69a621406a944da5be551b863d065358ba94d85dd3b80d83c412660e230ee93b27316081fbee9b4851cc4ff8585db64c7dfa26cb5148ac835663f2712c3d
-rw-r--r--contrib/completions/bash/bitcoin-cli.bash2
-rwxr-xr-xcontrib/seeds/generate-seeds.py6
-rw-r--r--doc/REST-interface.md2
-rw-r--r--doc/files.md13
-rw-r--r--doc/release-process.md4
-rw-r--r--src/bitcoin-cli.cpp5
-rw-r--r--src/bitcoin-wallet.cpp2
-rw-r--r--src/chainparams.cpp2
-rw-r--r--src/chainparamsbase.cpp7
-rw-r--r--src/chainparamsseeds.h12
-rw-r--r--src/common/args.cpp7
-rw-r--r--src/common/args.h2
-rw-r--r--src/consensus/params.h1
-rw-r--r--src/init.cpp17
-rw-r--r--src/kernel/chainparams.cpp107
-rw-r--r--src/kernel/chainparams.h1
-rw-r--r--src/pow.cpp14
-rw-r--r--src/qt/guiconstants.h1
-rw-r--r--src/qt/guiutil.cpp1
-rw-r--r--src/qt/networkstyle.cpp1
-rw-r--r--src/test/argsman_tests.cpp18
-rw-r--r--src/test/pow_tests.cpp5
-rw-r--r--src/test/versionbits_tests.cpp2
-rw-r--r--src/util/chaintype.cpp4
-rw-r--r--src/util/chaintype.h1
-rw-r--r--src/validation.cpp12
-rwxr-xr-xtest/functional/feature_config_args.py36
-rwxr-xr-xtest/functional/wallet_crosschain.py29
28 files changed, 283 insertions, 31 deletions
diff --git a/contrib/completions/bash/bitcoin-cli.bash b/contrib/completions/bash/bitcoin-cli.bash
index 89e01bc09a..b04fdbcb0e 100644
--- a/contrib/completions/bash/bitcoin-cli.bash
+++ b/contrib/completions/bash/bitcoin-cli.bash
@@ -9,7 +9,7 @@ _bitcoin_rpc() {
local rpcargs=()
for i in ${COMP_LINE}; do
case "$i" in
- -conf=*|-datadir=*|-regtest|-rpc*|-testnet)
+ -conf=*|-datadir=*|-regtest|-rpc*|-testnet|-testnet4)
rpcargs=( "${rpcargs[@]}" "$i" )
;;
esac
diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py
index f67e7b0f4c..72dd5d28cc 100755
--- a/contrib/seeds/generate-seeds.py
+++ b/contrib/seeds/generate-seeds.py
@@ -5,11 +5,12 @@
'''
Script to generate list of seed nodes for kernel/chainparams.cpp.
-This script expects two text files in the directory that is passed as an
+This script expects three text files in the directory that is passed as an
argument:
nodes_main.txt
nodes_test.txt
+ nodes_testnet4.txt
These files must consist of lines in the format
@@ -171,6 +172,9 @@ def main():
g.write('\n')
with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f:
process_nodes(g, f, 'chainparams_seed_test')
+ g.write('\n')
+ with open(os.path.join(indir,'nodes_testnet4.txt'), 'r', encoding="utf8") as f:
+ process_nodes(g, f, 'chainparams_seed_testnet4')
g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n')
if __name__ == '__main__':
diff --git a/doc/REST-interface.md b/doc/REST-interface.md
index 2d7d0e3769..6664bc2a3a 100644
--- a/doc/REST-interface.md
+++ b/doc/REST-interface.md
@@ -4,7 +4,7 @@ Unauthenticated REST Interface
The REST API can be enabled with the `-rest` option.
The interface runs on the same port as the JSON-RPC interface, by default port 8332 for mainnet, port 18332 for testnet,
-port 38332 for signet, and port 18443 for regtest.
+port 48332 for testnet4, port 38332 for signet, and port 18443 for regtest.
REST Interface consistency guarantees
-------------------------------------
diff --git a/doc/files.md b/doc/files.md
index 5c5a61bd56..b738d6055a 100644
--- a/doc/files.md
+++ b/doc/files.md
@@ -34,12 +34,13 @@ Windows | `%LOCALAPPDATA%\Bitcoin\` <sup>[\[1\]](#note1)</sup>
3. All content of the data directory, except for `bitcoin.conf` file, is chain-specific. This means the actual data directory paths for non-mainnet cases differ:
-Chain option | Data directory path
--------------------------------|------------------------------
-`-chain=main` (default) | *path_to_datadir*`/`
-`-chain=test` or `-testnet` | *path_to_datadir*`/testnet3/`
-`-chain=signet` or `-signet` | *path_to_datadir*`/signet/`
-`-chain=regtest` or `-regtest` | *path_to_datadir*`/regtest/`
+Chain option | Data directory path
+---------------------------------|------------------------------
+`-chain=main` (default) | *path_to_datadir*`/`
+`-chain=test` or `-testnet` | *path_to_datadir*`/testnet3/`
+`-chain=testnet4` or `-testnet4` | *path_to_datadir*`/testnet4/`
+`-chain=signet` or `-signet` | *path_to_datadir*`/signet/`
+`-chain=regtest` or `-regtest` | *path_to_datadir*`/regtest/`
## Data directory layout
diff --git a/doc/release-process.md b/doc/release-process.md
index 1e6d49100e..fa2c53eb0c 100644
--- a/doc/release-process.md
+++ b/doc/release-process.md
@@ -311,13 +311,15 @@ Both variables are used as a guideline for how much space the user needs on thei
Note that all values should be taken from a **fully synced** node and have an overhead of 5-10% added on top of its base value.
To calculate `m_assumed_blockchain_size`, take the size in GiB of these directories:
-- For `mainnet` -> the data directory, excluding the `/testnet3`, `/signet`, and `/regtest` directories and any overly large files, e.g. a huge `debug.log`
+- For `mainnet` -> the data directory, excluding the `/testnet3`, `/testnet4`, `/signet`, and `/regtest` directories and any overly large files, e.g. a huge `debug.log`
- For `testnet` -> `/testnet3`
+- For `testnet4` -> `/testnet4`
- For `signet` -> `/signet`
To calculate `m_assumed_chain_state_size`, take the size in GiB of these directories:
- For `mainnet` -> `/chainstate`
- For `testnet` -> `/testnet3/chainstate`
+- For `testnet4` -> `/testnet4/chainstate`
- For `signet` -> `/signet/chainstate`
Notes:
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 44fc273163..934b5fb6dc 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -75,6 +75,7 @@ static void SetupCliArgs(ArgsManager& argsman)
const auto defaultBaseParams = CreateBaseChainParams(ChainType::MAIN);
const auto testnetBaseParams = CreateBaseChainParams(ChainType::TESTNET);
+ const auto testnet4BaseParams = CreateBaseChainParams(ChainType::TESTNET4);
const auto signetBaseParams = CreateBaseChainParams(ChainType::SIGNET);
const auto regtestBaseParams = CreateBaseChainParams(ChainType::REGTEST);
@@ -98,7 +99,7 @@ static void SetupCliArgs(ArgsManager& argsman)
argsman.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
- argsman.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
+ argsman.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, testnet4: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), testnet4BaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
argsman.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-rpcwait", "Wait for RPC server to start", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-rpcwaittimeout=<n>", strprintf("Timeout in seconds to wait for the RPC server to start, or 0 for no timeout. (default: %d)", DEFAULT_WAIT_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
@@ -428,6 +429,8 @@ private:
std::string ChainToString() const
{
switch (gArgs.GetChainType()) {
+ case ChainType::TESTNET4:
+ return " testnet4";
case ChainType::TESTNET:
return " testnet";
case ChainType::SIGNET:
diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp
index b6f5c3f15d..7d030abe97 100644
--- a/src/bitcoin-wallet.cpp
+++ b/src/bitcoin-wallet.cpp
@@ -69,7 +69,7 @@ static std::optional<int> WalletAppInit(ArgsManager& args, int argc, char* argv[
strUsage += "\n"
"bitcoin-wallet is an offline tool for creating and interacting with " PACKAGE_NAME " wallet files.\n"
"By default bitcoin-wallet will act on wallets in the default mainnet wallet directory in the datadir.\n"
- "To change the target wallet, use the -datadir, -wallet and -regtest/-signet/-testnet arguments.\n\n"
+ "To change the target wallet, use the -datadir, -wallet and -regtest/-signet/-testnet/-testnet4 arguments.\n\n"
"Usage:\n"
" bitcoin-wallet [options] <command>\n";
strUsage += "\n" + args.GetHelpMessage();
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 5d4401b719..68319e8e8b 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -115,6 +115,8 @@ std::unique_ptr<const CChainParams> CreateChainParams(const ArgsManager& args, c
return CChainParams::Main();
case ChainType::TESTNET:
return CChainParams::TestNet();
+ case ChainType::TESTNET4:
+ return CChainParams::TestNet4();
case ChainType::SIGNET: {
auto opts = CChainParams::SigNetOptions{};
ReadSigNetArgs(args, opts);
diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp
index 8cbf9e85e0..2b57b4ca55 100644
--- a/src/chainparamsbase.cpp
+++ b/src/chainparamsbase.cpp
@@ -17,7 +17,8 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman)
argsman.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. "
"This is intended for regression testing tools and app development. Equivalent to -chain=regtest.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv). (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
- argsman.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
+ argsman.AddArg("-testnet", "Use the testnet3 chain. Equivalent to -chain=test. Support for testnet3 is deprecated and will be removed with the next release. Consider moving to testnet4 now by using -testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
+ argsman.AddArg("-testnet4", "Use the testnet4 chain. Equivalent to -chain=testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-vbparams=deployment:start:end[:min_activation_height]", "Use given start/end times and min_activation_height for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-signet", "Use the signet chain. Equivalent to -chain=signet. Note that the network is defined by the -signetchallenge parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-signetchallenge", "Blocks must satisfy the given script to be considered valid (only for signet networks; defaults to the global default signet test network challenge)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS);
@@ -33,7 +34,7 @@ const CBaseChainParams& BaseParams()
}
/**
- * Port numbers for incoming Tor connections (8334, 18334, 38334, 18445) have
+ * Port numbers for incoming Tor connections (8334, 18334, 38334, 48334, 18445) have
* been chosen arbitrarily to keep ranges of used ports tight.
*/
std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const ChainType chain)
@@ -43,6 +44,8 @@ std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const ChainType chain)
return std::make_unique<CBaseChainParams>("", 8332, 8334);
case ChainType::TESTNET:
return std::make_unique<CBaseChainParams>("testnet3", 18332, 18334);
+ case ChainType::TESTNET4:
+ return std::make_unique<CBaseChainParams>("testnet4", 48332, 48334);
case ChainType::SIGNET:
return std::make_unique<CBaseChainParams>("signet", 38332, 38334);
case ChainType::REGTEST:
diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h
index 554d0cae5a..2a7e52316a 100644
--- a/src/chainparamsseeds.h
+++ b/src/chainparamsseeds.h
@@ -1786,4 +1786,16 @@ static const uint8_t chainparams_seed_test[] = {
0x04,0x20,0xbd,0x0e,0xc8,0x73,0x43,0xa7,0xc6,0x25,0x15,0xcf,0x3e,0x23,0xa8,0xb0,0xbf,0xe8,0x20,0xa7,0xec,0x2a,0xf6,0x37,0x6c,0x60,0x5e,0x4d,0xed,0xf4,0xb1,0xef,0xf7,0xb2,0x47,0x9d,
0x04,0x20,0xc8,0x88,0xfe,0x71,0x5f,0xa3,0x6c,0x96,0x6a,0xd7,0x9e,0x38,0x84,0x9f,0x44,0xe1,0x6b,0xdc,0x98,0x31,0xad,0x96,0x29,0xe7,0x00,0x83,0x63,0x03,0xae,0x69,0x2e,0x63,0x47,0x9d,
};
+
+static const uint8_t chainparams_seed_testnet4[] = {
+ 0x01,0x04,0x39,0x80,0xb0,0xa3,0xbc,0xcd,
+ 0x01,0x04,0x33,0x9e,0xf8,0x08,0xbc,0xcd,
+ 0x01,0x04,0x5f,0xd9,0x49,0xa2,0xbc,0xcd,
+ 0x01,0x04,0x12,0xbd,0x9c,0x66,0xbc,0xcd,
+ 0x01,0x04,0x67,0x63,0xab,0xd4,0xbc,0xcd,
+ 0x01,0x04,0x52,0x43,0x66,0x0f,0xbc,0xcd,
+ 0x01,0x04,0x58,0x63,0xf8,0x32,0xbc,0xcd,
+ 0x01,0x04,0x67,0xa5,0xc0,0xd2,0xbc,0xcd,
+ 0x01,0x04,0x12,0xc9,0xcf,0x37,0xbc,0xcd,
+};
#endif // BITCOIN_CHAINPARAMSSEEDS_H
diff --git a/src/common/args.cpp b/src/common/args.cpp
index caff36fdb3..a37a16b62b 100644
--- a/src/common/args.cpp
+++ b/src/common/args.cpp
@@ -159,6 +159,7 @@ std::list<SectionInfo> ArgsManager::GetUnrecognizedSections() const
ChainTypeToString(ChainType::REGTEST),
ChainTypeToString(ChainType::SIGNET),
ChainTypeToString(ChainType::TESTNET),
+ ChainTypeToString(ChainType::TESTNET4),
ChainTypeToString(ChainType::MAIN),
};
@@ -773,10 +774,11 @@ std::variant<ChainType, std::string> ArgsManager::GetChainArg() const
const bool fRegTest = get_net("-regtest");
const bool fSigNet = get_net("-signet");
const bool fTestNet = get_net("-testnet");
+ const bool fTestNet4 = get_net("-testnet4");
const auto chain_arg = GetArg("-chain");
- if ((int)chain_arg.has_value() + (int)fRegTest + (int)fSigNet + (int)fTestNet > 1) {
- throw std::runtime_error("Invalid combination of -regtest, -signet, -testnet and -chain. Can use at most one.");
+ if ((int)chain_arg.has_value() + (int)fRegTest + (int)fSigNet + (int)fTestNet + (int)fTestNet4 > 1) {
+ throw std::runtime_error("Invalid combination of -regtest, -signet, -testnet, -testnet4 and -chain. Can use at most one.");
}
if (chain_arg) {
if (auto parsed = ChainTypeFromString(*chain_arg)) return *parsed;
@@ -786,6 +788,7 @@ std::variant<ChainType, std::string> ArgsManager::GetChainArg() const
if (fRegTest) return ChainType::REGTEST;
if (fSigNet) return ChainType::SIGNET;
if (fTestNet) return ChainType::TESTNET;
+ if (fTestNet4) return ChainType::TESTNET4;
return ChainType::MAIN;
}
diff --git a/src/common/args.h b/src/common/args.h
index 78a61313b9..323a86d8dc 100644
--- a/src/common/args.h
+++ b/src/common/args.h
@@ -423,7 +423,7 @@ private:
fs::path GetDataDir(bool net_specific) const;
/**
- * Return -regtest/-signet/-testnet/-chain= setting as a ChainType enum if a
+ * Return -regtest/-signet/-testnet/-testnet4/-chain= setting as a ChainType enum if a
* recognized chain type was set, or as a string if an unrecognized chain
* name was set. Raise an exception if an invalid combination of flags was
* provided.
diff --git a/src/consensus/params.h b/src/consensus/params.h
index 25f53eb620..d970e41637 100644
--- a/src/consensus/params.h
+++ b/src/consensus/params.h
@@ -108,6 +108,7 @@ struct Params {
/** Proof of work parameters */
uint256 powLimit;
bool fPowAllowMinDifficultyBlocks;
+ bool enforce_BIP94;
bool fPowNoRetargeting;
int64_t nPowTargetSpacing;
int64_t nPowTargetTimespan;
diff --git a/src/init.cpp b/src/init.cpp
index ca809db466..faaf3353d0 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -450,10 +450,12 @@ void SetupServerArgs(ArgsManager& argsman)
const auto defaultBaseParams = CreateBaseChainParams(ChainType::MAIN);
const auto testnetBaseParams = CreateBaseChainParams(ChainType::TESTNET);
+ const auto testnet4BaseParams = CreateBaseChainParams(ChainType::TESTNET4);
const auto signetBaseParams = CreateBaseChainParams(ChainType::SIGNET);
const auto regtestBaseParams = CreateBaseChainParams(ChainType::REGTEST);
const auto defaultChainParams = CreateChainParams(argsman, ChainType::MAIN);
const auto testnetChainParams = CreateChainParams(argsman, ChainType::TESTNET);
+ const auto testnet4ChainParams = CreateChainParams(argsman, ChainType::TESTNET4);
const auto signetChainParams = CreateChainParams(argsman, ChainType::SIGNET);
const auto regtestChainParams = CreateChainParams(argsman, ChainType::REGTEST);
@@ -467,7 +469,7 @@ void SetupServerArgs(ArgsManager& argsman)
#if HAVE_SYSTEM
argsman.AddArg("-alertnotify=<cmd>", "Execute command when an alert is raised (%s in cmd is replaced by message)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif
- argsman.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ argsman.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet3: %s, testnet4: %s, signet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnet4ChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat files (default: <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-blocksxor",
strprintf("Whether an XOR-key applies to blocksdir *.dat files. "
@@ -493,7 +495,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE_MB), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY_HOURS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
- argsman.AddArg("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex(), signetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
+ argsman.AddArg("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet3: %s, testnet4: %s, signet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnet4ChainParams->GetConsensus().nMinimumChainWork.GetHex(), signetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
argsman.AddArg("-par=<n>", strprintf("Set the number of script verification threads (0 = auto, up to %d, <0 = leave that many cores free, default: %d)",
MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@@ -522,7 +524,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-addnode=<ip>", strprintf("Add a node to connect to and attempt to keep the connection open (see the addnode RPC help for more info). This option can be specified multiple times to add multiple nodes; connections are limited to %u at a time and are counted separately from the -maxconnections limit.", MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-asmap=<file>", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- argsman.AddArg("-bind=<addr>[:<port>][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), signetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-bind=<addr>[:<port>][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet3: 127.0.0.1:%u=onion, testnet4: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), testnet4BaseParams->OnionServiceTargetPort(), signetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-cjdnsreachable", "If set, then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network, see doc/cjdns.md) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -549,7 +551,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
- argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet: %u, signet: %u, regtest: %u). Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u). Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), testnet4ChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
#ifdef HAVE_SOCKADDR_UN
argsman.AddArg("-proxy=<ip:port|path>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled). May be a local file path prefixed with 'unix:' if the proxy supports it.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION);
#else
@@ -666,7 +668,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpccookieperms=<readable-by>", strprintf("Set permissions on the RPC auth cookie file so that it is readable by [owner|group|all] (default: owner [via umask 0077])"), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
- argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC);
+ argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), testnet4BaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC);
argsman.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
argsman.AddArg("-rpcthreads=<n>", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
argsman.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
@@ -913,6 +915,11 @@ bool AppInitParameterInteraction(const ArgsManager& args)
return InitError(errors);
}
+ // Testnet3 deprecation warning
+ if (chain == ChainType::TESTNET) {
+ LogInfo("Warning: Support for testnet3 is deprecated and will be removed in an upcoming release. Consider switching to testnet4.\n");
+ }
+
// Warn if unrecognized section name are present in the config file.
bilingual_str warnings;
for (const auto& section : args.GetUnrecognizedSections()) {
diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp
index 0a5b7f9f0d..bbddc3dfec 100644
--- a/src/kernel/chainparams.cpp
+++ b/src/kernel/chainparams.cpp
@@ -100,6 +100,7 @@ public:
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
consensus.nPowTargetSpacing = 10 * 60;
consensus.fPowAllowMinDifficultyBlocks = false;
+ consensus.enforce_BIP94 = false;
consensus.fPowNoRetargeting = false;
consensus.nRuleChangeActivationThreshold = 1815; // 90% of 2016
consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
@@ -219,6 +220,7 @@ public:
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
consensus.nPowTargetSpacing = 10 * 60;
consensus.fPowAllowMinDifficultyBlocks = true;
+ consensus.enforce_BIP94 = false;
consensus.fPowNoRetargeting = false;
consensus.nRuleChangeActivationThreshold = 1512; // 75% for testchains
consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
@@ -297,6 +299,104 @@ public:
};
/**
+ * Testnet (v4): public test network which is reset from time to time.
+ */
+class CTestNet4Params : public CChainParams {
+public:
+ CTestNet4Params() {
+ m_chain_type = ChainType::TESTNET4;
+ consensus.signet_blocks = false;
+ consensus.signet_challenge.clear();
+ consensus.nSubsidyHalvingInterval = 210000;
+ consensus.BIP34Height = 1;
+ consensus.BIP34Hash = uint256{};
+ consensus.BIP65Height = 1;
+ consensus.BIP66Height = 1;
+ consensus.CSVHeight = 1;
+ consensus.SegwitHeight = 1;
+ consensus.MinBIP9WarningHeight = 0;
+ consensus.powLimit = uint256S("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
+ consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
+ consensus.nPowTargetSpacing = 10 * 60;
+ consensus.fPowAllowMinDifficultyBlocks = true;
+ consensus.enforce_BIP94 = true;
+ consensus.fPowNoRetargeting = false;
+ consensus.nRuleChangeActivationThreshold = 1512; // 75% for testchains
+ consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
+ consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28;
+ consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
+ consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
+ consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].min_activation_height = 0; // No activation delay
+
+ // Deployment of Taproot (BIPs 340-342)
+ consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2;
+ consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
+ consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
+ consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay
+
+ consensus.nMinimumChainWork = uint256{};
+ consensus.defaultAssumeValid = uint256{};
+
+ pchMessageStart[0] = 0x1c;
+ pchMessageStart[1] = 0x16;
+ pchMessageStart[2] = 0x3f;
+ pchMessageStart[3] = 0x28;
+ nDefaultPort = 48333;
+ nPruneAfterHeight = 1000;
+ m_assumed_blockchain_size = 0;
+ m_assumed_chain_state_size = 0;
+
+ const char* testnet4_genesis_msg = "03/May/2024 000000000000000000001ebd58c244970b3aa9d783bb001011fbe8ea8e98e00e";
+ const CScript testnet4_genesis_script = CScript() << ParseHex("000000000000000000000000000000000000000000000000000000000000000000") << OP_CHECKSIG;
+ genesis = CreateGenesisBlock(testnet4_genesis_msg,
+ testnet4_genesis_script,
+ 1714777860,
+ 393743547,
+ 0x1d00ffff,
+ 1,
+ 50 * COIN);
+ consensus.hashGenesisBlock = genesis.GetHash();
+ assert(consensus.hashGenesisBlock == uint256S("0x00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043"));
+ assert(genesis.hashMerkleRoot == uint256S("0x7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e"));
+
+ vFixedSeeds.clear();
+ vSeeds.clear();
+ // nodes with support for servicebits filtering should be at the top
+ vSeeds.emplace_back("seed.testnet4.bitcoin.sprovoost.nl."); // Sjors Provoost
+ vSeeds.emplace_back("seed.testnet4.wiz.biz."); // Jason Maurice
+
+ base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111);
+ base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196);
+ base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1,239);
+ base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF};
+ base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
+
+ bech32_hrp = "tb";
+
+ vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_testnet4), std::end(chainparams_seed_testnet4));
+
+ fDefaultConsistencyChecks = false;
+ m_is_mockable_chain = false;
+
+ checkpointData = {
+ {
+ {},
+ }
+ };
+
+ m_assumeutxo_data = {
+ {}
+ };
+
+ chainTxData = ChainTxData{
+ .nTime = 0,
+ .tx_count = 0,
+ .dTxRate = 0,
+ };
+ }
+};
+
+/**
* Signet: test network with an additional consensus parameter (see BIP325).
*/
class SigNetParams : public CChainParams {
@@ -356,6 +456,7 @@ public:
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
consensus.nPowTargetSpacing = 10 * 60;
consensus.fPowAllowMinDifficultyBlocks = false;
+ consensus.enforce_BIP94 = false;
consensus.fPowNoRetargeting = false;
consensus.nRuleChangeActivationThreshold = 1815; // 90% of 2016
consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
@@ -434,6 +535,7 @@ public:
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
consensus.nPowTargetSpacing = 10 * 60;
consensus.fPowAllowMinDifficultyBlocks = true;
+ consensus.enforce_BIP94 = false;
consensus.fPowNoRetargeting = true;
consensus.nRuleChangeActivationThreshold = 108; // 75% for testchains
consensus.nMinerConfirmationWindow = 144; // Faster than normal for regtest (144 instead of 2016)
@@ -563,6 +665,11 @@ std::unique_ptr<const CChainParams> CChainParams::TestNet()
return std::make_unique<const CTestNetParams>();
}
+std::unique_ptr<const CChainParams> CChainParams::TestNet4()
+{
+ return std::make_unique<const CTestNet4Params>();
+}
+
std::vector<int> CChainParams::GetAvailableSnapshotHeights() const
{
std::vector<int> heights;
diff --git a/src/kernel/chainparams.h b/src/kernel/chainparams.h
index e367de2f93..c4584600fd 100644
--- a/src/kernel/chainparams.h
+++ b/src/kernel/chainparams.h
@@ -161,6 +161,7 @@ public:
static std::unique_ptr<const CChainParams> SigNet(const SigNetOptions& options);
static std::unique_ptr<const CChainParams> Main();
static std::unique_ptr<const CChainParams> TestNet();
+ static std::unique_ptr<const CChainParams> TestNet4();
protected:
CChainParams() = default;
diff --git a/src/pow.cpp b/src/pow.cpp
index 1e8d53de8b..50de8946be 100644
--- a/src/pow.cpp
+++ b/src/pow.cpp
@@ -61,7 +61,19 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF
// Retarget
const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
arith_uint256 bnNew;
- bnNew.SetCompact(pindexLast->nBits);
+
+ // Special difficulty rule for Testnet4
+ if (params.enforce_BIP94) {
+ // Here we use the first block of the difficulty period. This way
+ // the real difficulty is always preserved in the first block as
+ // it is not allowed to use the min-difficulty exception.
+ int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);
+ const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);
+ bnNew.SetCompact(pindexFirst->nBits);
+ } else {
+ bnNew.SetCompact(pindexLast->nBits);
+ }
+
bnNew *= nActualTimespan;
bnNew /= params.nPowTargetTimespan;
diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h
index 0386689baf..30ffa302a4 100644
--- a/src/qt/guiconstants.h
+++ b/src/qt/guiconstants.h
@@ -50,6 +50,7 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80;
#define QAPP_ORG_DOMAIN "bitcoin.org"
#define QAPP_APP_NAME_DEFAULT "Bitcoin-Qt"
#define QAPP_APP_NAME_TESTNET "Bitcoin-Qt-testnet"
+#define QAPP_APP_NAME_TESTNET4 "Bitcoin-Qt-testnet4"
#define QAPP_APP_NAME_SIGNET "Bitcoin-Qt-signet"
#define QAPP_APP_NAME_REGTEST "Bitcoin-Qt-regtest"
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 0e3a0d9a9d..baa19d7c24 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -115,6 +115,7 @@ static std::string DummyAddress(const CChainParams &params)
break;
case ChainType::SIGNET:
case ChainType::TESTNET:
+ case ChainType::TESTNET4:
addr = "tb1p35yvjel7srp783ztf8v6jdra7dhfzk5jaun8xz2qp6ws7z80n4tqa6qnlg";
break;
case ChainType::REGTEST:
diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp
index b6314f5533..d3f7c02d05 100644
--- a/src/qt/networkstyle.cpp
+++ b/src/qt/networkstyle.cpp
@@ -19,6 +19,7 @@ static const struct {
} network_styles[] = {
{ChainType::MAIN, QAPP_APP_NAME_DEFAULT, 0, 0},
{ChainType::TESTNET, QAPP_APP_NAME_TESTNET, 70, 30},
+ {ChainType::TESTNET4, QAPP_APP_NAME_TESTNET4, 70, 30},
{ChainType::SIGNET, QAPP_APP_NAME_SIGNET, 35, 15},
{ChainType::REGTEST, QAPP_APP_NAME_REGTEST, 160, 30},
};
diff --git a/src/test/argsman_tests.cpp b/src/test/argsman_tests.cpp
index 5f0318e8c4..297595a9cf 100644
--- a/src/test/argsman_tests.cpp
+++ b/src/test/argsman_tests.cpp
@@ -644,10 +644,12 @@ BOOST_AUTO_TEST_CASE(util_GetChainTypeString)
{
TestArgsManager test_args;
const auto testnet = std::make_pair("-testnet", ArgsManager::ALLOW_ANY);
+ const auto testnet4 = std::make_pair("-testnet4", ArgsManager::ALLOW_ANY);
const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_ANY);
- test_args.SetupArgs({testnet, regtest});
+ test_args.SetupArgs({testnet, testnet4, regtest});
const char* argv_testnet[] = {"cmd", "-testnet"};
+ const char* argv_testnet4[] = {"cmd", "-testnet4"};
const char* argv_regtest[] = {"cmd", "-regtest"};
const char* argv_test_no_reg[] = {"cmd", "-testnet", "-noregtest"};
const char* argv_both[] = {"cmd", "-testnet", "-regtest"};
@@ -663,6 +665,12 @@ BOOST_AUTO_TEST_CASE(util_GetChainTypeString)
BOOST_CHECK(test_args.ParseParameters(2, argv_testnet, error));
BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "test");
+ BOOST_CHECK(test_args.ParseParameters(0, argv_testnet4, error));
+ BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "main");
+
+ BOOST_CHECK(test_args.ParseParameters(2, argv_testnet4, error));
+ BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "testnet4");
+
BOOST_CHECK(test_args.ParseParameters(2, argv_regtest, error));
BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "regtest");
@@ -758,8 +766,8 @@ struct ArgsMergeTestingSetup : public BasicTestingSetup {
ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] {
for (bool soft_set : {false, true}) {
for (bool force_set : {false, true}) {
- for (const std::string& section : {ChainTypeToString(ChainType::MAIN), ChainTypeToString(ChainType::TESTNET), ChainTypeToString(ChainType::SIGNET)}) {
- for (const std::string& network : {ChainTypeToString(ChainType::MAIN), ChainTypeToString(ChainType::TESTNET), ChainTypeToString(ChainType::SIGNET)}) {
+ for (const std::string& section : {ChainTypeToString(ChainType::MAIN), ChainTypeToString(ChainType::TESTNET), ChainTypeToString(ChainType::TESTNET4), ChainTypeToString(ChainType::SIGNET)}) {
+ for (const std::string& network : {ChainTypeToString(ChainType::MAIN), ChainTypeToString(ChainType::TESTNET), ChainTypeToString(ChainType::TESTNET4), ChainTypeToString(ChainType::SIGNET)}) {
for (bool net_specific : {false, true}) {
fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific);
}
@@ -913,7 +921,7 @@ BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup)
// Results file is formatted like:
//
// <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output>
- BOOST_CHECK_EQUAL(out_sha_hex, "d1e436c1cd510d0ec44d5205d4b4e3bee6387d316e0075c58206cb16603f3d82");
+ BOOST_CHECK_EQUAL(out_sha_hex, "f1ee5ab094cc43d16a6086fa7f2c10389e0f99902616b31bbf29189972ad1473");
}
// Similar test as above, but for ArgsManager::GetChainTypeString function.
@@ -1016,7 +1024,7 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup)
// Results file is formatted like:
//
// <input> || <output>
- BOOST_CHECK_EQUAL(out_sha_hex, "f263493e300023b6509963887444c41386f44b63bc30047eb8402e8c1144854c");
+ BOOST_CHECK_EQUAL(out_sha_hex, "9e60306e1363528bbc19a47f22bcede88e5d6815212f18ec8e6cdc4638dddab4");
}
BOOST_AUTO_TEST_CASE(util_ReadWriteSettings)
diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp
index 9aadebbe63..4af66af283 100644
--- a/src/test/pow_tests.cpp
+++ b/src/test/pow_tests.cpp
@@ -198,6 +198,11 @@ BOOST_AUTO_TEST_CASE(ChainParams_TESTNET_sanity)
sanity_check_chainparams(*m_node.args, ChainType::TESTNET);
}
+BOOST_AUTO_TEST_CASE(ChainParams_TESTNET4_sanity)
+{
+ sanity_check_chainparams(*m_node.args, ChainType::TESTNET4);
+}
+
BOOST_AUTO_TEST_CASE(ChainParams_SIGNET_sanity)
{
sanity_check_chainparams(*m_node.args, ChainType::SIGNET);
diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp
index f462895edb..896840b0f3 100644
--- a/src/test/versionbits_tests.cpp
+++ b/src/test/versionbits_tests.cpp
@@ -419,7 +419,7 @@ BOOST_AUTO_TEST_CASE(versionbits_computeblockversion)
// check that any deployment on any chain can conceivably reach both
// ACTIVE and FAILED states in roughly the way we expect
- for (const auto& chain_type: {ChainType::MAIN, ChainType::TESTNET, ChainType::SIGNET, ChainType::REGTEST}) {
+ for (const auto& chain_type: {ChainType::MAIN, ChainType::TESTNET, ChainType::TESTNET4, ChainType::SIGNET, ChainType::REGTEST}) {
const auto chainParams = CreateChainParams(*m_node.args, chain_type);
uint32_t chain_all_vbits{0};
for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++i) {
diff --git a/src/util/chaintype.cpp b/src/util/chaintype.cpp
index 8a199e352a..272466e7af 100644
--- a/src/util/chaintype.cpp
+++ b/src/util/chaintype.cpp
@@ -15,6 +15,8 @@ std::string ChainTypeToString(ChainType chain)
return "main";
case ChainType::TESTNET:
return "test";
+ case ChainType::TESTNET4:
+ return "testnet4";
case ChainType::SIGNET:
return "signet";
case ChainType::REGTEST:
@@ -29,6 +31,8 @@ std::optional<ChainType> ChainTypeFromString(std::string_view chain)
return ChainType::MAIN;
} else if (chain == "test") {
return ChainType::TESTNET;
+ } else if (chain == "testnet4") {
+ return ChainType::TESTNET4;
} else if (chain == "signet") {
return ChainType::SIGNET;
} else if (chain == "regtest") {
diff --git a/src/util/chaintype.h b/src/util/chaintype.h
index c73985df57..2fe734b64a 100644
--- a/src/util/chaintype.h
+++ b/src/util/chaintype.h
@@ -13,6 +13,7 @@ enum class ChainType {
TESTNET,
SIGNET,
REGTEST,
+ TESTNET4,
};
std::string ChainTypeToString(ChainType chain);
diff --git a/src/validation.cpp b/src/validation.cpp
index d8c1c27aae..f7250778fc 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -4182,6 +4182,18 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast())
return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "time-too-old", "block's timestamp is too early");
+ // Testnet4 only: Check timestamp against prev for difficulty-adjustment
+ // blocks to prevent timewarp attacks (see https://github.com/bitcoin/bitcoin/pull/15482).
+ if (consensusParams.enforce_BIP94) {
+ // Check timestamp for the first block of each difficulty adjustment
+ // interval, except the genesis block.
+ if (nHeight % consensusParams.DifficultyAdjustmentInterval() == 0) {
+ if (block.GetBlockTime() < pindexPrev->GetBlockTime() - 60 * 60 * 2) {
+ return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "time-timewarp-attack", "block's timestamp is too early on diff adjustment block");
+ }
+ }
+ }
+
// Check timestamp
if (block.Time() > NodeClock::now() + std::chrono::seconds{MAX_FUTURE_BLOCK_TIME}) {
return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", "block timestamp too far in the future");
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index dc812d224f..bb20e2baa8 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -371,11 +371,44 @@ class ConfArgsTest(BitcoinTestFramework):
def test_acceptstalefeeestimates_arg_support(self):
self.log.info("Test -acceptstalefeeestimates option support")
conf_file = self.nodes[0].datadir_path / "bitcoin.conf"
- for chain, chain_name in {("main", ""), ("test", "testnet3"), ("signet", "signet")}:
+ for chain, chain_name in {("main", ""), ("test", "testnet3"), ("signet", "signet"), ("testnet4", "testnet4")}:
util.write_config(conf_file, n=0, chain=chain_name, extra_config='acceptstalefeeestimates=1\n')
self.nodes[0].assert_start_raises_init_error(expected_msg=f'Error: acceptstalefeeestimates is not supported on {chain} chain.')
util.write_config(conf_file, n=0, chain="regtest") # Reset to regtest
+ def test_testnet3_deprecation_msg(self):
+ self.log.info("Test testnet3 deprecation warning")
+ t3_warning_log = "Warning: Support for testnet3 is deprecated and will be removed in an upcoming release. Consider switching to testnet4."
+
+ def warning_msg(node, approx_size):
+ return f'Warning: Disk space for "{node.datadir_path / node.chain / "blocks" }" may not accommodate the block files. Approximately {approx_size} GB of data will be stored in this directory.'
+
+ # Testnet3 node will log the warning
+ self.nodes[0].chain = 'testnet3'
+ self.nodes[0].replace_in_config([('regtest=', 'testnet='), ('[regtest]', '[test]')])
+ with self.nodes[0].assert_debug_log([t3_warning_log]):
+ self.start_node(0)
+ # Some CI environments will have limited space and some others won't
+ # so we need to handle both cases as a valid result.
+ self.nodes[0].stderr.seek(0)
+ err = self.nodes[0].stdout.read()
+ self.nodes[0].stderr.seek(0)
+ self.nodes[0].stderr.truncate()
+ if err != b'' and err != warning_msg(self.nodes[0], 42):
+ raise AssertionError("Unexpected stderr after shutdown of Testnet3 node")
+ self.stop_node(0)
+
+ # Testnet4 node will not log the warning
+ self.nodes[0].chain = 'testnet4'
+ self.nodes[0].replace_in_config([('testnet=', 'testnet4='), ('[test]', '[testnet4]')])
+ with self.nodes[0].assert_debug_log([], unexpected_msgs=[t3_warning_log]):
+ self.start_node(0)
+ self.stop_node(0)
+
+ # Reset to regtest
+ self.nodes[0].chain = 'regtest'
+ self.nodes[0].replace_in_config([('testnet4=', 'regtest='), ('[testnet4]', '[regtest]')])
+
def run_test(self):
self.test_log_buffer()
self.test_args_log()
@@ -389,6 +422,7 @@ class ConfArgsTest(BitcoinTestFramework):
self.test_ignored_conf()
self.test_ignored_default_conf()
self.test_acceptstalefeeestimates_arg_support()
+ self.test_testnet3_deprecation_msg()
# Remove the -datadir argument so it doesn't override the config file
self.nodes[0].args = [arg for arg in self.nodes[0].args if not arg.startswith("-datadir")]
diff --git a/test/functional/wallet_crosschain.py b/test/functional/wallet_crosschain.py
index 3505d33e51..97db84c3e4 100755
--- a/test/functional/wallet_crosschain.py
+++ b/test/functional/wallet_crosschain.py
@@ -11,7 +11,7 @@ class WalletCrossChain(BitcoinTestFramework):
self.add_wallet_options(parser)
def set_test_params(self):
- self.num_nodes = 2
+ self.num_nodes = 3
self.setup_clean_chain = True
def skip_test_if_missing_module(self):
@@ -24,6 +24,12 @@ class WalletCrossChain(BitcoinTestFramework):
self.nodes[1].chain = 'testnet3'
self.nodes[1].extra_args = ['-maxconnections=0', '-prune=550'] # disable testnet sync
self.nodes[1].replace_in_config([('regtest=', 'testnet='), ('[regtest]', '[test]')])
+
+ # Switch node 2 to testnet4 before starting it.
+ self.nodes[2].chain = 'testnet4'
+ self.nodes[2].extra_args = ['-maxconnections=0', '-prune=550'] # disable testnet4 sync
+ self.nodes[2].replace_in_config([('regtest=', 'testnet4='), ('[regtest]', '[testnet4]')])
+
self.start_nodes()
def run_test(self):
@@ -39,19 +45,40 @@ class WalletCrossChain(BitcoinTestFramework):
self.nodes[1].createwallet(node1_wallet)
self.nodes[1].backupwallet(node1_wallet_backup)
self.nodes[1].unloadwallet(node1_wallet)
+ node2_wallet = self.nodes[2].datadir_path / 'node2_wallet'
+ node2_wallet_backup = self.nodes[0].datadir_path / 'node2_wallet.bak'
+ self.nodes[2].createwallet(node2_wallet)
+ self.nodes[2].backupwallet(node2_wallet_backup)
+ self.nodes[2].unloadwallet(node2_wallet)
self.log.info("Loading/restoring wallets into nodes with a different genesis block")
if self.options.descriptors:
assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[0].loadwallet, node1_wallet)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[0].loadwallet, node2_wallet)
assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[1].loadwallet, node0_wallet)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[2].loadwallet, node0_wallet)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[1].loadwallet, node2_wallet)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[2].loadwallet, node1_wallet)
assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[0].restorewallet, 'w', node1_wallet_backup)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[0].restorewallet, 'w', node2_wallet_backup)
assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[1].restorewallet, 'w', node0_wallet_backup)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[2].restorewallet, 'w', node0_wallet_backup)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[1].restorewallet, 'w', node2_wallet_backup)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[2].restorewallet, 'w', node1_wallet_backup)
else:
assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].loadwallet, node1_wallet)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].loadwallet, node2_wallet)
assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[1].loadwallet, node0_wallet)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[2].loadwallet, node0_wallet)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[1].loadwallet, node2_wallet)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[2].loadwallet, node1_wallet)
assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].restorewallet, 'w', node1_wallet_backup)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].restorewallet, 'w', node2_wallet_backup)
assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[1].restorewallet, 'w', node0_wallet_backup)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[2].restorewallet, 'w', node0_wallet_backup)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[1].restorewallet, 'w', node2_wallet_backup)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[2].restorewallet, 'w', node1_wallet_backup)
if not self.options.descriptors:
self.log.info("Override cross-chain wallet load protection")