Each custom signet is stored in a different datadir, using the network magic (message start) as the suffix.
The default signet is always stored without a suffix for backward compatibility, even if the default challenge is provided explicitly via -signetchallenge.
For those already familiar with #29838, here are the differences between #29838 and this that are visible to the user:
The suffix does not use the first 4 bytes of hash160(challenge), but rather the network magic (message start) as mentioned in #29838 (comment) and #34566 (comment).
If the default challenge is provided to -signetchallenge, #29838 added a suffix to the datadir, whereas this PR does not. This includes backward compatibility when the default signet is used explicitly, not only implicitly, even though the default signet consensus parameters aren't used in the explicit case.[^1] A test was added for this case.
[^1]: could be something for a follow-up PR, or maybe it's intentional to not mix explicit with implicit options?
Unlike #29838, this PR does not update tests that didn't break (rpc_bind.py, interface_bitcoin_cli.py). I have found other tests where datadir_path is used in combination with self.chain (regexp: datadir_path.*chain). I considered it inconsistent to update some tests but not all of them. I've decided to not update all of them to keep the scope of this PR low until feedback. I can go through all tests in a follow-up PR to make sure they don't break when they are ever used with (custom) signet, however unlikely that might be.
I have done some manual testing like this:
<details>
<summary>manual testing</summary>
'signet' directory is used, IBD:
$ bitcoind -signet
'signet' directory is used, no IBD without -addnode (see #29838 (comment)) or existing peers.dat:
If your review is incorrectly listed, please copy-paste <code><!--meta-tag:bot-skip--></code> into the comment that the bot should ignore.
<!--174a7506f384e20aa4161008e828411d-->
Conflicts
Reviewers, this pull request conflicts with the following ones:
#28690 (build: Introduce internal kernel library by sedited)
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
<!--5faf32d7da4f0f540f40219e4f7537a3-->
LLM Linter (⨠experimental)
Possible places where comparison-specific test macros should replace generic comparisons:
[test/functional/feature_signet.py] assert get_signet_commitment(get_segwit_commitment(node)) is None -> use assert_equal(..., None) instead of a bare assert ... is None.
[test/functional/tool_signet_miner.py] assert get_signet_commitment(get_segwit_commitment(node)) is None -> use assert_equal(..., None) instead of a bare assert ... is None (appears multiple times in the added lines).
<sup>2026-06-04 12:26:40</sup>
ekzyis marked this as a draft on Feb 11, 2026
ekzyis
commented at 8:04 PM on February 11, 2026:
contributor
Will keep this in draft until I fix why tool_signet_miner.py is failing in CI (but not locally for some reason)
Update: Ah, it's because the CI runs it with --usecli
DrahtBot added the label CI failed on Feb 11, 2026
ekzyis force-pushed on Feb 11, 2026
ekzyis force-pushed on Feb 12, 2026
ekzyis force-pushed on Feb 12, 2026
ekzyis renamed this: feature: Use separate datadirs for each signet feature: Use different datadirs for different signets on Feb 12, 2026
ekzyis force-pushed on Feb 12, 2026
ekzyis force-pushed on Feb 12, 2026
DrahtBot removed the label CI failed on Feb 12, 2026
ekzyis marked this as ready for review on Feb 12, 2026
ekzyis force-pushed on Feb 12, 2026
sedited
commented at 5:09 PM on March 8, 2026:
contributor
This hasn't gotten any attention so far. @ajtowns do you want to take a look here since you opened the original issue?
ekzyis force-pushed on Apr 6, 2026
ekzyis
commented at 11:12 PM on April 6, 2026:
contributor
rebased on master (b730dc3301f)
in
test/functional/feature_signet.py:125
in
c4765b3c3aoutdated
120 | + self.log.info("Test that the signet data directory with -signetchallenge=51 is 'signet_51'") 121 | + assert_node_datadir(self.nodes[0], "signet_51") 122 | + 123 | + self.log.info("Test that the main signet data directory is 'signet'") 124 | + assert_node_datadir(self.nodes[3], "signet") 125 | +
Missing a test that verifies truncation to 16 chars for a longer challenge. The 522103ad5e... signet challenge would work. Just add an assert_node_datadir(self.nodes[4], "signet_522103ad5e0edad1") check.
The default signet challenge string is now duplicated in chainparamsbase.cpp and kernel/chainparams.cpp (and also in the Python test framework). I'm not sure if this is actually a problem, but could this be defined as a shared constant? I'm still learning the codebase, so I'd appreciate some input for better practices here.
This passes the signet challenge of the node to mine_block. It's required to pass -signetchallenge to the RPC. Without it, RPC authentication fails because the cookie isn't found. It will look in the default signet datadir:
$ build/test/functional/test_runner.py tool_signet_miner.py
Temporary test directory at /tmp/nix-shell-115046-4006075642/test_runner_āæ_š_20260408_191958
Remaining jobs: [tool_signet_miner.py]
1/1 - tool_signet_miner.py failed, Duration: 0 s
stdout:
2026-04-08T17:19:58.648068Z TestFramework (INFO): PRNG seed is: 8822581970357866232
2026-04-08T17:19:58.698730Z TestFramework (INFO): Initializing test directory /tmp/nix-shell-115046-4006075642/test_runner_āæ_š_20260408_191958/tool_signet_miner_0
2026-04-08T17:19:59.085274Z TestFramework (INFO): Signet node with single signature challenge
error: Authorization failed: Failed to read cookie file and no rpcpassword was specified. Configuration file: (/tmp/nix-shell-115046-4006075642/test_runner_āæ_š_20260408_191958/tool_signet_miner_0/node0/bitcoin.conf)
pinheadmz
commented at 4:03 PM on April 8, 2026:
member
concept ACK, I use a lot of different signets for educational projects and we could really use this. Personally I feel like using the magic bytes (message start) makes more sense and is less likely to collide between signet challenges
ekzyis marked this as ready for review on Apr 8, 2026
ekzyis force-pushed on Apr 8, 2026
ekzyis force-pushed on Apr 8, 2026
DrahtBot added the label CI failed on Apr 8, 2026
ekzyis force-pushed on Apr 8, 2026
ekzyis
commented at 8:09 PM on April 8, 2026:
contributor
concept ACK, I use a lot of different signets for educational projects and we could really use this. Personally I feel like using the magic bytes (message start) makes more sense and is less likely to collide between signet challenges
Right! 77cd6c99306 uses magic bytes as the suffix. Additionally to what you mentioned, I also like this for these reasons:
makes directory names shorter (was also mentioned in #29383 (comment))
consistent suffix length, independent of challenge length
Updated commit message and PR description.
DrahtBot removed the label CI failed on Apr 8, 2026
RandyMcMillan
commented at 2:31 PM on April 9, 2026:
contributor
concept ACK
ekzyis force-pushed on Apr 10, 2026
ekzyis
commented at 11:50 AM on April 10, 2026:
contributor
I think "notable new feature" and "visible change to the end-user experience" from developer-notes.md apply here, so I added release notes in f283eaa.
in
src/chainparamsbase.cpp:43
in
f283eaac1doutdated
is challenge_hex guaranteed to be valid hex at this point? Otherwise I think this is empty but that might be ok since I'm fairly certain we'd fail later on if the challenge isnt valid.
I don't see this having any undesired side effect like creating a datadir that can't be used, though, so I also think it's okay.
It just reminds me that I originally intended this functionality to be part of CChainParams instead of CBaseChainParams, but DataDir() is part of the CBaseChainParams interface. I explored conditionally adding -datadir with SoftSetArg like we do for other options, but then we would still create a "signet" folder inside it.
What if the hard-coded default signet challenge was moved from kernel/chainparams.cpp to src/signet.h as a constexpr. Then you can just compare the actual challenge provided by the user string-to-string (although probably need ToLower for case sensitivity)
No, this would be caught when checking the directory name in line 111.
For example:
diff --git a/test/functional/feature_signet.py b/test/functional/feature_signet.py
index 9bc8353785..81f0246b22 100755
--- a/test/functional/feature_signet.py
+++ b/test/functional/feature_signet.py
@@ -117,7 +117,7 @@ class SignetBasicTest(BitcoinTestFramework):
assert rpc_log_path.startswith(str(datadir) + "/")
self.log.info("Test that the signet data directory with custom -signetchallenge uses network magic as suffix")
- assert_node_datadir(self.nodes[0], f"signet_{message_start(self.signets[0].challenge)}")
+ assert_node_datadir(self.nodes[0], f"signet")
assert_node_datadir(self.nodes[4], f"signet_{message_start(self.signets[2].challenge)}")
self.log.info("Test that the main signet data directory is 'signet'")
$ python feature_signet.py
...
Traceback (most recent call last):
File "/home/ekzyis/prog/bitcoin/test/functional/test_framework/test_framework.py", line 143, in main
self.run_test()
~~~~~~~~~~~~~^^
File "/home/ekzyis/prog/bitcoin/build/test/functional/feature_signet.py", line 120, in run_test
assert_node_datadir(self.nodes[0], f"signet")
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/ekzyis/prog/bitcoin/build/test/functional/feature_signet.py", line 112, in assert_node_datadir
assert_equal(path.basename(datadir), expected_dirname)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/ekzyis/prog/bitcoin/test/functional/test_framework/util.py", line 83, in assert_equal
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
AssertionError: not(signet_54d26fbd == signet)
pinheadmz
commented at 5:41 PM on April 17, 2026:
member
code review at f283eaac1dd41fb86c15eac4c734e1bc919134b7 some thoughts:
I think this is too much for one commit. See if you can pull out any refactors into the first commit, then make your change (with any tests that MUST be changed to stay passing), add more tests next, and finally the release note can even be on its own commit.
There are probably other doc files that could use notes about this change along with the release note (files.md for example)
I'm not sure how to address this but the bitcoin.conf file has these network sections like [regtest] and [signet]. But that section would include the signetchallenge so I don't think a user would need more than one signet section in their conf. But a user switching between signets might have hard-coded peers like connect= in the signet section of their conf, and that could be an issue when switching networks. Just wondering if you have any thoughts about that.
Otherwise it's looking good! I built on macos/arm64 and ran the functional tests with and without --usecli. Played with the feature on default and custom signets.
ekzyis force-pushed on Apr 21, 2026
ekzyis marked this as a draft on Apr 21, 2026
ekzyis
commented at 6:34 PM on April 21, 2026:
contributor
I think this is too much for one commit. See if you can pull out any refactors into the first commit, then make your change (with any tests that MUST be changed to stay passing), add more tests next, and finally the release note can even be on its own commit.
I split it into four commits as suggested
There are probably other doc files that could use notes about this change along with the release note (files.md for example)
I updated files.md but I haven't found other documentation about datadirs that need an update
[...] Just wondering if you have any thoughts about that.
I think different peers for each signet is an argument for supporting more than one signet section in the config (in a follow-up PR) as mentioned in #29838 (comment).
Looks like my refactor in 2fd5da15ea broke all kinds of builds. I put this into draft until I fixed them.
DrahtBot added the label CI failed on Apr 21, 2026
DrahtBot
commented at 6:35 PM on April 21, 2026:
contributor
<!--85328a0da195eb286784d51f73fa0af9-->
š§ At least one of the CI tasks failed.
<sub>Task test ancestor commits: https://github.com/bitcoin/bitcoin/actions/runs/24737621566/job/72368627292</sub>
<sub>LLM reason (⨠experimental): CTest failed because test_kernel (the only failing test) returned a non-zero status, causing the CI to exit with code 8.</sub>
<details><summary>Hints</summary>
Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the
affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
</details>
ekzyis force-pushed on Apr 21, 2026
ekzyis force-pushed on Apr 21, 2026
ekzyis force-pushed on Apr 21, 2026
DrahtBot removed the label CI failed on Apr 21, 2026
ekzyis
commented at 9:55 PM on April 21, 2026:
contributor
12d77c9d8a rebased on master (875faa29e1) to resolve a conflict in src/kernel/CMakeLists.txt
5912565c81 I resolved the conflict by not adding a line to src/kernel/CMakeLists.txt
ekzyis marked this as ready for review on Apr 21, 2026
ekzyis force-pushed on Apr 21, 2026
ekzyis force-pushed on Apr 21, 2026
DrahtBot added the label CI failed on Apr 21, 2026
DrahtBot
commented at 11:20 PM on April 21, 2026:
contributor
<!--85328a0da195eb286784d51f73fa0af9-->
š§ At least one of the CI tasks failed.
<sub>Task test ancestor commits: https://github.com/bitcoin/bitcoin/actions/runs/24751140594/job/72414298229</sub>
<sub>LLM reason (⨠experimental): CI failed because the C++ build errored while compiling chainparamsbase.cpp (bitcoin_common / variant operator __OP), causing gmake to exit with code 1.</sub>
<details><summary>Hints</summary>
Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the
affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
</details>
DrahtBot removed the label CI failed on Apr 22, 2026
DrahtBot added the label Needs rebase on May 22, 2026
ekzyis marked this as a draft on May 22, 2026
ekzyis force-pushed on May 22, 2026
DrahtBot added the label CI failed on May 22, 2026
ekzyis force-pushed on May 22, 2026
ekzyis
commented at 9:29 PM on May 22, 2026:
contributor
a59dda147a9 rebased on master (9f7b08c61ca)
Thanks to #35334, I was able to remove the custom SignetMinerTest.setup_nodes() and the manual assignment to node.cli.options in SignetBasicTest.setup_nodes(). Those changes may not have been necessary even before that PR (I might have changed the existing tests more than needed), but they are definitely not needed anymore.
ekzyis marked this as ready for review on May 22, 2026
DrahtBot removed the label Needs rebase on May 22, 2026
DrahtBot removed the label CI failed on May 22, 2026
DrahtBot added the label Needs rebase on May 29, 2026
ekzyis force-pushed on May 29, 2026
ekzyis
commented at 11:52 AM on May 29, 2026:
contributor
ba97013d760 rebased on master (13b7fffc5e0)
This was the only conflict:
<<<<<<< HEAD
#include <script/verify_flags.h>
||||||| parent of f6307691e65 (refactor: Import signet constant and helper from signet.h)
=======
#include <signet.h>
>>>>>>> f6307691e65 (refactor: Import signet constant and helper from signet.h)
DrahtBot added the label CI failed on May 29, 2026
DrahtBot removed the label Needs rebase on May 29, 2026
ekzyis force-pushed on May 29, 2026
DrahtBot removed the label CI failed on May 29, 2026
pinheadmz
commented at 7:57 PM on June 1, 2026:
member
One thing that occured to me about this change is that the although the signet_challenge field has been added to RPCs like getmininginfo and getblockchaininfo the magic bytes and therefore the data directory path isn't obvious to the user. This might be an issue for some (automated) users of the software that expect a data directory to be, simply ~/.bitcoin/{network} (like warnet, probably)
I wonder if datadir should be added to an RPC somewhere?
ajtowns
commented at 1:13 AM on June 2, 2026:
contributor
I wonder if datadir should be added to an RPC somewhere?
I'd contend that a getnodeinfo that reported info like that would be a useful feature.
pinheadmz
commented at 9:42 AM on June 2, 2026:
member
Although, the user won't be able to call any RPC if they don't know where the cookie is...
Maybe the approach should be, requiring -datadir is set if -signetchallenge is present? Forces the user to make their own nickname and path for every signet they use.
ekzyis
commented at 10:40 AM on June 2, 2026:
contributor
Although, the user won't be able to call any RPC if they don't know where the cookie is...
I agree with the problem, this is confusing:
$ bitcoin-cli -signet getblockchaininfo
error: Authorization failed: Failed to read cookie file and no rpcpassword was specified. Configuration file: (/home/ekzyis/.bitcoin/bitcoin.conf)
It is not obvious that -signetchallenge is needed:
$ bitcoin-cli -signet -signetchallenge=51 getblockchaininfo
{
"chain": "signet",
"blocks": 0,
"headers": 0,
"bestblockhash": "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6",
"bits": "1e0377ae",
"target": "00000377ae000000000000000000000000000000000000000000000000000000",
"difficulty": 0.001126515290698186,
"time": 1598918400,
"mediantime": 1598918400,
"verificationprogress": 1,
"initialblockdownload": true,
"chainwork": "000000000000000000000000000000000000000000000000000000000049d414",
"size_on_disk": 293,
"pruned": false,
"signet_challenge": "51",
"warnings": [
"This is a pre-release test build - use at your own risk - do not use for mining or merchant applications"
]
}
This wasn't the case before.
Maybe the approach should be, requiring -datadir is set if -signetchallenge is present? Forces the user to make their own nickname and path for every signet they use.
Not sure I'm following. -datadir only changes the base path (~/.bitcoin by default). -signetchallenge would still be needed to know which signet folder to pick inside datadir (~/.bitcoin/signet or ~/.bitcoin/signet_XXXXXX).
So I think if RPC auth fails and -signet was passed, we could add "did you forget -signetchallenge for custom signets?" to the error message. WDYT?
I wonder if datadir should be added to an RPC somewhere?
I'd contend that a getnodeinfo that reported info like that would be a useful feature.
I think datadir (and network_magic) could be added to the output of getblockchaininfo above.
pinheadmz
commented at 11:06 AM on June 2, 2026:
member
I meant when starting bitcoind with -signetchallenge (that is not default) we should crash and throw an init error telling the user to ALSO set -datadir so they know where the cookie is. It's an entirely different approach than what you've implemented here (sorry). Just up for discussion.
pinheadmz
commented at 11:16 AM on June 2, 2026:
member
I also didn't realize -signetchallenge could be passed to cli and that works, but not super user friendly
DrahtBot added the label Needs rebase on Jun 3, 2026
refactor: Import signet constant and helper from signet.h
We want to avoid duplicate code in the following commit, so we import the
signet default challenge and a function to calculate the message start from the
signet header file.
The source list for the `bitcoin_common` target now includes signet.cpp because
kernel/chainparams.cpp depends on it. The targets `bitcoin_node` and `fuzz`
include `bitcoin_common`, so signet.cpp was removed from there.
90c55d6528
Use different datadirs for different signets
This adds support for syncing multiple signets.
Each custom signet is stored in a different datadir, using the network magic
(message start) as the suffix.
The default signet is always stored without a suffix for backward compatibility,
even if the default challenge is provided explicitly via -signetchallenge.
Co-authored-by: Brandon Odiwuor <brandon.odiwuor@gmail.com>
doc: Update documentation for signet datadir7364f0c7a2
ekzyis force-pushed on Jun 4, 2026
DrahtBot removed the label Needs rebase on Jun 4, 2026
cli: Add -signetchallenge info on RPC auth errorf53d611b17
DrahtBot added the label CI failed on Jun 4, 2026
ekzyis
commented at 12:31 PM on June 4, 2026:
contributor
7364f0c7a29 rebased on master (47da4f9b716)
I meant when starting bitcoind with -signetchallenge (that is not default) we should crash and throw an init error telling the user to ALSO set -datadir so they know where the cookie is. It's an entirely different approach than what you've implemented here (sorry). Just up for discussion.
Mhh, a strict requirement like this would be a simple fix to avoid conflicts between chain data, but I'm not sure it would make running multiple signets easier. Instead of handling different datadirs for different signets in the code, we make the user handle them.
I also didn't realize -signetchallenge could be passed to cli and that works, but not super user friendly
I think f53d611b176 would help. WDYT?
pinheadmz
commented at 12:58 PM on June 4, 2026:
member
I can't review the code for a few more days but I think the communication is right. This will be a niche developer use case anyway.
Another idea I had is maybe adding a command to bitcoin-util that derives the magic bytes, or maybe the entire data dir path, given a signet challenge. That way in applications like warnet we know everything we need to know before starting bitcoind.
DrahtBot removed the label CI failed on Jun 4, 2026
ekzyis
commented at 9:22 PM on June 26, 2026:
contributor
Another idea I had is maybe adding a command to bitcoin-util that derives the magic bytes
In "test: Add signet datadir tests" 3875deab1410672d05325b8e9b561244e190f527
nit: worth adding a test that checks if -signetchallenge=SIGNET_DEFAULT_CHALLENGE.upper() also uses 'signet' datadir
in
doc/release-notes-34566.md:8
in
7364f0c7a2
0 | @@ -0,0 +1,8 @@ 1 | +Network changes 2 | +--------------- 3 | + 4 | +Custom signets now use separate data directories, with a suffix derived from the 5 | +network magic (message start), so multiple signets can be synced. The default 6 | +signet continues to use the unsuffixed directory for backward compatibility, 7 | +including when the default challenge is set explicitly via `-signetchallenge`. 8 | +(#34566)
In "doc: Update documentation for signet datadir" 7364f0c7a29aafaeb73cdecbd86c76e9705e01a1
I believe this release note should warn and explain how to upgrade for users that are running with a non default signet challenge. Something like:
Users currently running a custom signet (a non-default -signetchallenge) will
have their node select a new, differently-named data directory after upgrading,
since previously all signets shared the unsuffixed signet/ directory. To avoid
a resync, manually rename/move the existing signet/ directory to the new
signet_XXXXXXXX/ path before starting the upgraded node (you can find the suffix in the field magic printed by bitcoin-util -signet -signetchallenge=<hex-script> getchainparams)
This makes me think there should also be a better error message when the user starts on the "wrong" signet:
Error: Invalid or corrupt peers.dat (Invalid network magic number). If you believe this is a bug, please report it to https://github.com/bitcoin/bitcoin/issues. As a workaround, you can move the file ("/Users/matthewzipkin/Library/Application Support/Bitcoin/signet/peers.dat") out of the way (rename, move, or delete) to have a new one created on the next start.
In "refactor: Import signet constant and helper from signet.h" 90c55d6528b52ffe91f6caa2fac490a4e05ff1c6
This line and #include <util/strencodings.h> in signet.h means this is also passed to any .cpp that include or transitively include this header which leaks every _hex operator.
This leak can be seen in src/kernel/chainparams.cpp where the #include <util/strencodings.h> was removed although it still uses the operator _hex and still works but it shouldn't rely on the transitive include coming from signet.h.
Suggest moving SIGNET_DEFAULT_CHALLENGE definition into signet.cpp (with an extern const std::vector<uint8_t> SIGNET_DEFAULT_CHALLENGE; declaration in this signet.h), so signet.h doesn't export any hex-literal operators at all.
ViniciusCestarii
commented at 2:39 PM on July 24, 2026:
contributor
This is a metadata mirror of the GitHub repository
bitcoin/bitcoin.
This site is not affiliated with GitHub.
Content is generated from a GitHub metadata backup.
generated: 2026-07-25 09:50 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me